From 003a42dcd85958829ad332063c32f6e4db82659c Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 4 Feb 2021 20:47:09 -0800 Subject: [PATCH 01/10] BUG: quantile for ExtensionArray --- pandas/core/array_algos/quantile.py | 76 +++++++++++++++++ pandas/core/arrays/datetimelike.py | 3 +- pandas/core/internals/blocks.py | 93 ++++++++++----------- pandas/tests/frame/methods/test_quantile.py | 79 +++++++++++++++++ 4 files changed, 199 insertions(+), 52 deletions(-) create mode 100644 pandas/core/array_algos/quantile.py diff --git a/pandas/core/array_algos/quantile.py b/pandas/core/array_algos/quantile.py new file mode 100644 index 0000000000000..6106a275440b1 --- /dev/null +++ b/pandas/core/array_algos/quantile.py @@ -0,0 +1,76 @@ +from typing import Sequence, Union + +import numpy as np + +from pandas._libs import lib + +from pandas.core.dtypes.common import is_list_like + +from pandas.core.nanops import nanpercentile + + +def quantile_with_mask( + values: np.ndarray, + mask: np.ndarray, + fill_value, + qs: Union[float, Sequence[float]], + interpolation: str, + axis: int, +) -> np.ndarray: + """ + Compute the quantiles of the given values for each quantile in `qs`. + + + Parameters + ---------- + values : np.ndarray + For ExtensionArray, this is _values_for_factorize()[0] + mask : np.ndarray[bool] + mask = isna(values) + For ExtensionArray, this is computed before calling _value_for_factorize + fill_value : Scalar + The value to interpret fill NA entries with + For ExtensionArray, this is _values_for_factorize()[1] + qs : a scalar or list of the quantiles to be computed + interpolation : str + Type of interpolation + axis : int + Axis along which to compute quantiles. + + Notes + ----- + Assumes values is already 2D. For ExtensionArray this means np.atleast_2d + has been called on _values_for_factorize()[0] + """ + is_empty = values.shape[axis] == 0 + orig_scalar = not is_list_like(qs) + if orig_scalar: + # make list-like, unpack later + qs = [qs] + + if is_empty: + # create the array of na_values + # 2d len(values) * len(qs) + flat = np.array([fill_value] * len(qs)) + result = np.repeat(flat, len(values)).reshape(len(values), len(qs)) + else: + # asarray needed for Sparse, see GH#24600 + result = nanpercentile( + values, + np.array(qs) * 100, + axis=axis, + na_value=fill_value, + mask=mask, + ndim=values.ndim, + interpolation=interpolation, + ) + + result = np.array(result, copy=False) + result = result.T + + if orig_scalar: + assert result.shape[-1] == 1, result.shape + result = result[..., 0] + result = lib.item_from_zerodim(result) + + return result diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 5ee7a5715d6af..c0138fa55f483 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -422,7 +422,8 @@ def copy(self: DatetimeLikeArrayT) -> DatetimeLikeArrayT: return new_obj def _values_for_factorize(self): - return self._ndarray, iNaT + # int64 instead of int ensures we have a "view" method + return self._ndarray, np.int64(iNaT) @classmethod def _from_factorized( diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 9314666acdaad..5597c4fa561c3 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -56,6 +56,7 @@ putmask_smart, putmask_without_repeat, ) +from pandas.core.array_algos.quantile import quantile_with_mask from pandas.core.array_algos.replace import ( compare_or_regex_search, replace_regex, @@ -79,7 +80,6 @@ is_scalar_indexer, ) import pandas.core.missing as missing -from pandas.core.nanops import nanpercentile if TYPE_CHECKING: from pandas import Index @@ -1390,8 +1390,10 @@ def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block: Parameters ---------- qs: a scalar or list of the quantiles to be computed - interpolation: type of interpolation, default 'linear' - axis: axis to compute, default 0 + interpolation : str, default "linear" + Type of interpolation + axis : int, default 0 + Axis along which to compute quantiles. Returns ------- @@ -1400,44 +1402,16 @@ def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block: # We should always have ndim == 2 because Series dispatches to DataFrame assert self.ndim == 2 - values = self.get_values() - - is_empty = values.shape[axis] == 0 - orig_scalar = not is_list_like(qs) - if orig_scalar: - # make list-like, unpack later - qs = [qs] - - if is_empty: - # create the array of na_values - # 2d len(values) * len(qs) - result = np.repeat( - np.array([self.fill_value] * len(qs)), len(values) - ).reshape(len(values), len(qs)) - else: - # asarray needed for Sparse, see GH#24600 - mask = np.asarray(isna(values)) - result = nanpercentile( - values, - np.array(qs) * 100, - axis=axis, - na_value=self.fill_value, - mask=mask, - ndim=values.ndim, - interpolation=interpolation, - ) + fill_value = self.fill_value + values = self.values + mask = np.asarray(isna(values)) - result = np.array(result, copy=False) - result = result.T + result = quantile_with_mask(values, mask, fill_value, qs, interpolation, axis) + ndim = np.ndim(result) - if orig_scalar and not lib.is_scalar(result): - # result could be scalar in case with is_empty and self.ndim == 1 - assert result.shape[-1] == 1, result.shape - result = result[..., 0] - result = lib.item_from_zerodim(result) + placement = np.arange(len(result)) - ndim = np.ndim(result) - return make_block(result, placement=np.arange(len(result)), ndim=ndim) + return make_block(result, placement=placement, ndim=ndim) def _replace_coerce( self, @@ -1866,6 +1840,36 @@ def _unstack(self, unstacker, fill_value, new_placement): ] return blocks, mask + def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block: + # asarray needed for Sparse, see GH#24600 + mask = np.asarray(isna(self.values)) + mask = np.atleast_2d(mask) + + values, fill_value = self.values._values_for_factorize() + + values = np.atleast_2d(values) + + result = quantile_with_mask(values, mask, fill_value, qs, interpolation, axis) + ndim = np.ndim(result) + + if not is_sparse(self.dtype): + # shape[0] should be 1 as long as EAs are 1D + + if result.ndim == 1: + # i.e. qs was originally a scalar + assert result.shape == (1,), result.shape + result = type(self.values)._from_factorized(result, self.values) + placement = np.arange(len(result)) + + else: + assert result.shape == (1, len(qs)), result.shape + result = type(self.values)._from_factorized(result[0], self.values) + placement = [0] + else: + placement = np.arange(len(result)) + + return make_block(result, placement=placement, ndim=ndim) + class HybridMixin: """ @@ -2184,19 +2188,6 @@ def fillna( value, limit=limit, inplace=inplace, downcast=downcast ) - def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block: - naive = self.values.view("M8[ns]") - - # TODO(EA2D): kludge for 2D block with 1D values - naive = naive.reshape(self.shape) - - blk = self.make_block(naive) - res_blk = blk.quantile(qs, interpolation=interpolation, axis=axis) - - # TODO(EA2D): ravel is kludge for 2D block with 1D values, assumes column-like - aware = self._holder(res_blk.values.ravel(), dtype=self.dtype) - return self.make_block_same_class(aware, ndim=res_blk.ndim) - def _check_ndim(self, values, ndim): """ ndim inference and validation. diff --git a/pandas/tests/frame/methods/test_quantile.py b/pandas/tests/frame/methods/test_quantile.py index 3f7f2e51add96..cee34648d5e2c 100644 --- a/pandas/tests/frame/methods/test_quantile.py +++ b/pandas/tests/frame/methods/test_quantile.py @@ -78,6 +78,85 @@ def test_quantile(self, datetime_frame): expected = Series([3.0, 4.0], index=[0, 1], name=0.5) tm.assert_series_equal(result, expected) + @pytest.mark.parametrize("as_dt64tz", [True, False]) + def test_quantile_period(self, frame_or_series, as_dt64tz): + pi = pd.period_range("2016-01-01", periods=9, freq="D", name="A") + if as_dt64tz: + pi = pi.to_timestamp("S").tz_localize("US/Central") + + obj = frame_or_series(pi) + + qs = [0.5, 0, 1] + if frame_or_series is Series: + result = obj.quantile(qs) + else: + result = obj.quantile(qs, numeric_only=False) + + expected = Series([pi[4], pi[0], pi[-1]], index=qs, name="A") + expected = frame_or_series(expected) + + tm.assert_equal(result, expected) + + # TODO: tests for axis=1? + # TODO: empty case? might as well do dt64 and td64 here too + @pytest.mark.parametrize("as_dt64tz", [True, False]) + def test_quantile_period_with_nat(self, frame_or_series, as_dt64tz): + pi = pd.period_range("2016-01-01", periods=9, freq="D", name="A") + if as_dt64tz: + pi = pi.to_timestamp("S").tz_localize("US/Central") + + obj = frame_or_series(pi) + + obj.iloc[0] = pd.NaT + obj.iloc[-1] = pd.NaT + + qs = [0.5, 0, 1] + if frame_or_series is Series: + result = obj.quantile(qs) + else: + result = obj.quantile(qs, numeric_only=False) + + expected = Series([pi[4], pi[1], pi[-2]], index=qs, name="A") + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + @pytest.mark.parametrize("as_dt64tz", [True, False]) + def test_quantile_period_all_nat(self, frame_or_series, as_dt64tz): + pi = pd.period_range("2016-01-01", periods=9, freq="D", name="A") + if as_dt64tz: + pi = pi.to_timestamp("S").tz_localize("US/Central") + + obj = frame_or_series(pi) + obj.iloc[:] = pd.NaT + + qs = [0.5, 0, 1] + if frame_or_series is Series: + result = obj.quantile(qs) + else: + result = obj.quantile(qs, numeric_only=False) + + expected = Series([pd.NaT, pd.NaT, pd.NaT], dtype=pi.dtype, index=qs, name="A") + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + def test_quantile_period_scalar(self, frame_or_series): + # scalar qs + pi = pd.period_range("2016-01-01", periods=9, freq="D", name="A") + obj = frame_or_series(pi) + + qs = 0.5 + if frame_or_series is Series: + result = obj.quantile(qs) + else: + result = obj.quantile(qs, numeric_only=False) + + expected = Series({"A": pi[4]}, name=0.5) + if frame_or_series is Series: + expected = expected["A"] + assert result == expected + else: + tm.assert_series_equal(result, expected) + def test_quantile_date_range(self): # GH 2460 From 8dda19bcc818fd8c5d4f41c8a65bb6088eb6fc54 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 4 Feb 2021 20:49:34 -0800 Subject: [PATCH 02/10] docstring fixup --- pandas/core/array_algos/quantile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/core/array_algos/quantile.py b/pandas/core/array_algos/quantile.py index 6106a275440b1..6580350e6b037 100644 --- a/pandas/core/array_algos/quantile.py +++ b/pandas/core/array_algos/quantile.py @@ -20,7 +20,6 @@ def quantile_with_mask( """ Compute the quantiles of the given values for each quantile in `qs`. - Parameters ---------- values : np.ndarray @@ -37,6 +36,10 @@ def quantile_with_mask( axis : int Axis along which to compute quantiles. + Returns + ------- + np.ndarray + Notes ----- Assumes values is already 2D. For ExtensionArray this means np.atleast_2d From 6743b621de283579f02b099b4ad4eee0379ecd23 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 5 Feb 2021 09:45:27 -0800 Subject: [PATCH 03/10] mypy fixup --- pandas/core/array_algos/quantile.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pandas/core/array_algos/quantile.py b/pandas/core/array_algos/quantile.py index 6580350e6b037..8d4dd7be28839 100644 --- a/pandas/core/array_algos/quantile.py +++ b/pandas/core/array_algos/quantile.py @@ -1,5 +1,3 @@ -from typing import Sequence, Union - import numpy as np from pandas._libs import lib @@ -13,7 +11,7 @@ def quantile_with_mask( values: np.ndarray, mask: np.ndarray, fill_value, - qs: Union[float, Sequence[float]], + qs, interpolation: str, axis: int, ) -> np.ndarray: From 149084dba02948de0162d75b466b19701c1a9958 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 5 Feb 2021 10:15:26 -0800 Subject: [PATCH 04/10] TST: fixturize ea quantile tests --- pandas/tests/frame/methods/test_quantile.py | 176 +++++++++++--------- 1 file changed, 97 insertions(+), 79 deletions(-) diff --git a/pandas/tests/frame/methods/test_quantile.py b/pandas/tests/frame/methods/test_quantile.py index cee34648d5e2c..58476a30cf960 100644 --- a/pandas/tests/frame/methods/test_quantile.py +++ b/pandas/tests/frame/methods/test_quantile.py @@ -78,85 +78,6 @@ def test_quantile(self, datetime_frame): expected = Series([3.0, 4.0], index=[0, 1], name=0.5) tm.assert_series_equal(result, expected) - @pytest.mark.parametrize("as_dt64tz", [True, False]) - def test_quantile_period(self, frame_or_series, as_dt64tz): - pi = pd.period_range("2016-01-01", periods=9, freq="D", name="A") - if as_dt64tz: - pi = pi.to_timestamp("S").tz_localize("US/Central") - - obj = frame_or_series(pi) - - qs = [0.5, 0, 1] - if frame_or_series is Series: - result = obj.quantile(qs) - else: - result = obj.quantile(qs, numeric_only=False) - - expected = Series([pi[4], pi[0], pi[-1]], index=qs, name="A") - expected = frame_or_series(expected) - - tm.assert_equal(result, expected) - - # TODO: tests for axis=1? - # TODO: empty case? might as well do dt64 and td64 here too - @pytest.mark.parametrize("as_dt64tz", [True, False]) - def test_quantile_period_with_nat(self, frame_or_series, as_dt64tz): - pi = pd.period_range("2016-01-01", periods=9, freq="D", name="A") - if as_dt64tz: - pi = pi.to_timestamp("S").tz_localize("US/Central") - - obj = frame_or_series(pi) - - obj.iloc[0] = pd.NaT - obj.iloc[-1] = pd.NaT - - qs = [0.5, 0, 1] - if frame_or_series is Series: - result = obj.quantile(qs) - else: - result = obj.quantile(qs, numeric_only=False) - - expected = Series([pi[4], pi[1], pi[-2]], index=qs, name="A") - expected = frame_or_series(expected) - tm.assert_equal(result, expected) - - @pytest.mark.parametrize("as_dt64tz", [True, False]) - def test_quantile_period_all_nat(self, frame_or_series, as_dt64tz): - pi = pd.period_range("2016-01-01", periods=9, freq="D", name="A") - if as_dt64tz: - pi = pi.to_timestamp("S").tz_localize("US/Central") - - obj = frame_or_series(pi) - obj.iloc[:] = pd.NaT - - qs = [0.5, 0, 1] - if frame_or_series is Series: - result = obj.quantile(qs) - else: - result = obj.quantile(qs, numeric_only=False) - - expected = Series([pd.NaT, pd.NaT, pd.NaT], dtype=pi.dtype, index=qs, name="A") - expected = frame_or_series(expected) - tm.assert_equal(result, expected) - - def test_quantile_period_scalar(self, frame_or_series): - # scalar qs - pi = pd.period_range("2016-01-01", periods=9, freq="D", name="A") - obj = frame_or_series(pi) - - qs = 0.5 - if frame_or_series is Series: - result = obj.quantile(qs) - else: - result = obj.quantile(qs, numeric_only=False) - - expected = Series({"A": pi[4]}, name=0.5) - if frame_or_series is Series: - expected = expected["A"] - assert result == expected - else: - tm.assert_series_equal(result, expected) - def test_quantile_date_range(self): # GH 2460 @@ -612,3 +533,100 @@ def test_quantile_item_cache(self): ser.values[0] = 99 assert df.iloc[0, 0] == df["A"][0] + + +class TestQuantileExtensionDtype: + # TODO: tests for axis=1? + # TODO: empty case? might as well do dt64 and td64 here too + + @pytest.fixture( + params=[ + pytest.param( + pd.IntervalIndex.from_breaks(range(10)), + marks=pytest.mark.xfail(reason="raises when trying to add Intervals"), + ), + pd.period_range("2016-01-01", periods=9, freq="D"), + pd.date_range("2016-01-01", periods=9, tz="US/Pacific"), + ], + ids=lambda x: str(x.dtype), + ) + def index(self, request): + idx = request.param + idx.name = "A" + return idx + + def compute_quantile(self, obj, qs): + if isinstance(obj, Series): + result = obj.quantile(qs) + else: + result = obj.quantile(qs, numeric_only=False) + return result + + def test_quantile_ea(self, index, frame_or_series): + obj = frame_or_series(index).copy() + + # shuffle our values + indexer = np.arange(len(index), dtype=np.intp) + np.random.shuffle(indexer) + obj = obj.iloc[indexer] + + qs = [0.5, 0, 1] + result = self.compute_quantile(obj, qs) + + # expected here assumes len(index) == 9 + expected = Series([index[4], index[0], index[-1]], index=qs, name="A") + expected = frame_or_series(expected) + + tm.assert_equal(result, expected) + + def test_quantile_ea_with_na(self, index, frame_or_series): + obj = frame_or_series(index).copy() + + obj.iloc[0] = index._na_value + obj.iloc[-1] = index._na_value + + # shuffle our values + indexer = np.arange(len(index), dtype=np.intp) + np.random.shuffle(indexer) + obj = obj.iloc[indexer] + + qs = [0.5, 0, 1] + result = self.compute_quantile(obj, qs) + + # expected here assumes len(index) == 9 + expected = Series([index[4], index[1], index[-2]], index=qs, name="A") + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + def test_quantile_ea_all_na(self, index, frame_or_series): + + obj = frame_or_series(index).copy() + + obj.iloc[:] = index._na_value + + # shuffle our values + indexer = np.arange(len(index), dtype=np.intp) + np.random.shuffle(indexer) + obj = obj.iloc[indexer] + + qs = [0.5, 0, 1] + result = self.compute_quantile(obj, qs) + + expected = index.take([-1, -1, -1], allow_fill=True, fill_value=index._na_value) + expected = Series(expected, index=qs) + expected = frame_or_series(expected) + tm.assert_equal(result, expected) + + def test_quantile_ea_scalar(self, index, frame_or_series): + # scalar qs + obj = frame_or_series(index).copy() + + qs = 0.5 + result = self.compute_quantile(obj, qs) + + expected = Series({"A": index[4]}, name=0.5) + if frame_or_series is Series: + expected = expected["A"] + assert result == expected + else: + tm.assert_series_equal(result, expected) From 1748b657162bf948f9c6a546e00856be34462d0c Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 5 Feb 2021 10:16:44 -0800 Subject: [PATCH 05/10] shuffle --- pandas/tests/frame/methods/test_quantile.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pandas/tests/frame/methods/test_quantile.py b/pandas/tests/frame/methods/test_quantile.py index 58476a30cf960..c9ceb0baa8ecb 100644 --- a/pandas/tests/frame/methods/test_quantile.py +++ b/pandas/tests/frame/methods/test_quantile.py @@ -565,7 +565,7 @@ def compute_quantile(self, obj, qs): def test_quantile_ea(self, index, frame_or_series): obj = frame_or_series(index).copy() - # shuffle our values + # result should be invariant to shuffling indexer = np.arange(len(index), dtype=np.intp) np.random.shuffle(indexer) obj = obj.iloc[indexer] @@ -585,7 +585,7 @@ def test_quantile_ea_with_na(self, index, frame_or_series): obj.iloc[0] = index._na_value obj.iloc[-1] = index._na_value - # shuffle our values + # result should be invariant to shuffling indexer = np.arange(len(index), dtype=np.intp) np.random.shuffle(indexer) obj = obj.iloc[indexer] @@ -604,7 +604,7 @@ def test_quantile_ea_all_na(self, index, frame_or_series): obj.iloc[:] = index._na_value - # shuffle our values + # result should be invariant to shuffling indexer = np.arange(len(index), dtype=np.intp) np.random.shuffle(indexer) obj = obj.iloc[indexer] @@ -621,6 +621,11 @@ def test_quantile_ea_scalar(self, index, frame_or_series): # scalar qs obj = frame_or_series(index).copy() + # result should be invariant to shuffling + indexer = np.arange(len(index), dtype=np.intp) + np.random.shuffle(indexer) + obj = obj.iloc[indexer] + qs = 0.5 result = self.compute_quantile(obj, qs) From bcd464c5c716a2308bb9b3798263ce9bee111230 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 5 Feb 2021 15:00:24 -0800 Subject: [PATCH 06/10] REF: implement quantile_compat --- pandas/core/array_algos/quantile.py | 89 ++++++++++++++++++++++++++++- pandas/core/internals/blocks.py | 39 ++----------- 2 files changed, 93 insertions(+), 35 deletions(-) diff --git a/pandas/core/array_algos/quantile.py b/pandas/core/array_algos/quantile.py index 8d4dd7be28839..7dad1e34e6531 100644 --- a/pandas/core/array_algos/quantile.py +++ b/pandas/core/array_algos/quantile.py @@ -1,11 +1,45 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + import numpy as np from pandas._libs import lib +from pandas._typing import ArrayLike -from pandas.core.dtypes.common import is_list_like +from pandas.core.dtypes.common import is_list_like, is_sparse +from pandas.core.dtypes.missing import isna, na_value_for_dtype from pandas.core.nanops import nanpercentile +if TYPE_CHECKING: + from pandas.core.arrays import ExtensionArray + + +def quantile_compat(values: ArrayLike, qs, interpolation: str, axis: int) -> ArrayLike: + """ + Compute the quantiles of the given values for each quantile in `qs`. + + Parameters + ---------- + values : np.ndarray or ExtensionArray + qs : a scalar or list of the quantiles to be computed + interpolation : str + axis : int + + Returns + ------- + np.ndarray or ExtensionArray + """ + if isinstance(values, np.ndarray): + fill_value = na_value_for_dtype(values.dtype, compat=False) + mask = isna(values) + result = quantile_with_mask(values, mask, fill_value, qs, interpolation, axis) + placement = None # dummy + else: + result, placement = quantile_ea_compat(values, qs, interpolation, axis) + return result, placement + def quantile_with_mask( values: np.ndarray, @@ -75,3 +109,56 @@ def quantile_with_mask( result = lib.item_from_zerodim(result) return result + + +def quantile_ea_compat( + values: ExtensionArray, qs, interpolation: str, axis: int +) -> ExtensionArray: + """ + ExtensionArray compatibility layer for quantile_with_mask. + + We pretend that an ExtensionArray with shape (N,) is actually (1, N,) + for compatibility with non-EA code. + + Parameters + ---------- + values : ExtensionArray + qs : a scalar or list of the quantiles to be computed + interpolation: str + axis : int + + Returns + ------- + ExtensionArray + """ + # TODO(EA2D): make-believe not needed with 2D EAs + orig = values + + # asarray needed for Sparse, see GH#24600 + mask = np.asarray(values.isna()) + mask = np.atleast_2d(mask) + + values, fill_value = values._values_for_factorize() + values = np.atleast_2d(values) + + result = quantile_with_mask(values, mask, fill_value, qs, interpolation, axis) + + if not is_sparse(orig.dtype): + # shape[0] should be 1 as long as EAs are 1D + + if result.ndim == 1: + # i.e. qs was originally a scalar + assert result.shape == (1,), result.shape + result = type(orig)._from_factorized(result, orig) + placement = np.arange(len(result)) + + else: + assert result.shape == (1, len(qs)), result.shape + result = type(orig)._from_factorized(result[0], orig) + placement = [0] + else: + placement = np.arange(len(result)) + + # returning placement is a bit of a kludge so that we don't have to + # to re-derive it when calling from ExtensionBlock.quantile + return result, placement diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 5597c4fa561c3..a8d53e344523c 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -56,7 +56,7 @@ putmask_smart, putmask_without_repeat, ) -from pandas.core.array_algos.quantile import quantile_with_mask +from pandas.core.array_algos.quantile import quantile_compat from pandas.core.array_algos.replace import ( compare_or_regex_search, replace_regex, @@ -1402,15 +1402,10 @@ def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block: # We should always have ndim == 2 because Series dispatches to DataFrame assert self.ndim == 2 - fill_value = self.fill_value - values = self.values - mask = np.asarray(isna(values)) - - result = quantile_with_mask(values, mask, fill_value, qs, interpolation, axis) - ndim = np.ndim(result) + result, _ = quantile_compat(self.values, qs, interpolation, axis) placement = np.arange(len(result)) - + ndim = 2 if is_list_like(qs) else 1 return make_block(result, placement=placement, ndim=ndim) def _replace_coerce( @@ -1841,33 +1836,9 @@ def _unstack(self, unstacker, fill_value, new_placement): return blocks, mask def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block: - # asarray needed for Sparse, see GH#24600 - mask = np.asarray(isna(self.values)) - mask = np.atleast_2d(mask) - - values, fill_value = self.values._values_for_factorize() - - values = np.atleast_2d(values) - - result = quantile_with_mask(values, mask, fill_value, qs, interpolation, axis) - ndim = np.ndim(result) - - if not is_sparse(self.dtype): - # shape[0] should be 1 as long as EAs are 1D - - if result.ndim == 1: - # i.e. qs was originally a scalar - assert result.shape == (1,), result.shape - result = type(self.values)._from_factorized(result, self.values) - placement = np.arange(len(result)) - - else: - assert result.shape == (1, len(qs)), result.shape - result = type(self.values)._from_factorized(result[0], self.values) - placement = [0] - else: - placement = np.arange(len(result)) + result, placement = quantile_compat(self.values, qs, interpolation, axis) + ndim = 2 if is_list_like(qs) else 1 return make_block(result, placement=placement, ndim=ndim) From 79c2249a505564943f353cd01b7584d8e4c12282 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 5 Feb 2021 15:28:42 -0800 Subject: [PATCH 07/10] CLN: remove unnecessary return value --- pandas/core/array_algos/quantile.py | 13 +++---------- pandas/core/internals/blocks.py | 8 ++++++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/pandas/core/array_algos/quantile.py b/pandas/core/array_algos/quantile.py index 7dad1e34e6531..47384ede8eeca 100644 --- a/pandas/core/array_algos/quantile.py +++ b/pandas/core/array_algos/quantile.py @@ -35,10 +35,9 @@ def quantile_compat(values: ArrayLike, qs, interpolation: str, axis: int) -> Arr fill_value = na_value_for_dtype(values.dtype, compat=False) mask = isna(values) result = quantile_with_mask(values, mask, fill_value, qs, interpolation, axis) - placement = None # dummy else: - result, placement = quantile_ea_compat(values, qs, interpolation, axis) - return result, placement + result = quantile_ea_compat(values, qs, interpolation, axis) + return result def quantile_with_mask( @@ -150,15 +149,9 @@ def quantile_ea_compat( # i.e. qs was originally a scalar assert result.shape == (1,), result.shape result = type(orig)._from_factorized(result, orig) - placement = np.arange(len(result)) else: assert result.shape == (1, len(qs)), result.shape result = type(orig)._from_factorized(result[0], orig) - placement = [0] - else: - placement = np.arange(len(result)) - # returning placement is a bit of a kludge so that we don't have to - # to re-derive it when calling from ExtensionBlock.quantile - return result, placement + return result diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index a8d53e344523c..38576d360095e 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -1402,7 +1402,7 @@ def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block: # We should always have ndim == 2 because Series dispatches to DataFrame assert self.ndim == 2 - result, _ = quantile_compat(self.values, qs, interpolation, axis) + result = quantile_compat(self.values, qs, interpolation, axis) placement = np.arange(len(result)) ndim = 2 if is_list_like(qs) else 1 @@ -1836,9 +1836,13 @@ def _unstack(self, unstacker, fill_value, new_placement): return blocks, mask def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block: - result, placement = quantile_compat(self.values, qs, interpolation, axis) + result = quantile_compat(self.values, qs, interpolation, axis) ndim = 2 if is_list_like(qs) else 1 + if ndim == 1: + placement = np.arange(len(result)) + else: + placement = [0] return make_block(result, placement=placement, ndim=ndim) From 1daa18ea2174d69c75e6596f79fefb67681e751f Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 2 Mar 2021 18:48:23 -0800 Subject: [PATCH 08/10] ENH: ArrayManager.quantile --- pandas/core/frame.py | 8 ++----- pandas/core/internals/array_manager.py | 25 +++++++++++++++++++- pandas/core/internals/managers.py | 7 ------ pandas/tests/frame/methods/test_quantile.py | 7 ++++++ pandas/tests/series/methods/test_quantile.py | 2 -- 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 830a7f4347132..4e28afe1d5774 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -9650,15 +9650,11 @@ def quantile( return self._constructor([], index=q, columns=cols) return self._constructor_sliced([], index=cols, name=q, dtype=np.float64) - result = data._mgr.quantile( + res = data._mgr.quantile( qs=q, axis=1, interpolation=interpolation, transposed=is_transposed ) - result = self._constructor(result) - - if is_transposed: - result = result.T - + result = self._constructor(res) return result @doc(NDFrame.asfreq, **_shared_doc_kwargs) diff --git a/pandas/core/internals/array_manager.py b/pandas/core/internals/array_manager.py index 1b234cd2414a9..2dda6f9544bd3 100644 --- a/pandas/core/internals/array_manager.py +++ b/pandas/core/internals/array_manager.py @@ -55,6 +55,7 @@ ) import pandas.core.algorithms as algos +from pandas.core.array_algos.quantile import quantile_compat from pandas.core.arrays import ( DatetimeArray, ExtensionArray, @@ -75,6 +76,7 @@ from pandas.core.internals.blocks import make_block if TYPE_CHECKING: + from pandas import Float64Index from pandas.core.internals.managers import SingleBlockManager @@ -433,7 +435,28 @@ def apply_with_block(self: T, f, align_keys=None, **kwargs) -> T: return type(self)(result_arrays, self._axes) - # TODO quantile + def quantile( + self, + *, + qs: Float64Index, + axis: int = 0, + transposed: bool = False, + interpolation="linear", + ) -> ArrayManager: + + arrs = [ + x if not isinstance(x, np.ndarray) else np.atleast_2d(x) + for x in self.arrays + ] + assert axis == 1 + new_arrs = [quantile_compat(x, qs, interpolation, axis=axis) for x in arrs] + for i, arr in enumerate(new_arrs): + if arr.ndim == 2: + assert arr.shape[0] == 1, arr.shape + new_arrs[i] = arr[0] + + axes = [qs, self._axes[1]] + return type(self)(new_arrs, axes) def isna(self, func) -> ArrayManager: return self.apply("apply", func=func) diff --git a/pandas/core/internals/managers.py b/pandas/core/internals/managers.py index 2ad7471d6f086..fa8810fd0a389 100644 --- a/pandas/core/internals/managers.py +++ b/pandas/core/internals/managers.py @@ -554,13 +554,6 @@ def quantile( for blk in self.blocks ] - if transposed: - new_axes = new_axes[::-1] - blocks = [ - b.make_block(b.values.T, placement=np.arange(b.shape[1])) - for b in blocks - ] - return type(self)(blocks, new_axes) def isna(self, func) -> BlockManager: diff --git a/pandas/tests/frame/methods/test_quantile.py b/pandas/tests/frame/methods/test_quantile.py index 476c2033c6447..3888b87e9464f 100644 --- a/pandas/tests/frame/methods/test_quantile.py +++ b/pandas/tests/frame/methods/test_quantile.py @@ -524,6 +524,7 @@ def test_quantile_empty_no_columns(self): expected.columns.name = "captain tightpants" tm.assert_frame_equal(result, expected) + @td.skip_array_manager_invalid_test def test_quantile_item_cache(self): # previous behavior incorrect retained an invalid _item_cache entry df = DataFrame(np.random.randn(4, 3), columns=["A", "B", "C"]) @@ -608,12 +609,18 @@ def test_quantile_ea_with_na(self, index, frame_or_series): expected = frame_or_series(expected) tm.assert_equal(result, expected) + # TODO: filtering can be removed after GH#39763 is fixed + @pytest.mark.filterwarnings("ignore:Using .astype to convert:FutureWarning") def test_quantile_ea_all_na(self, index, frame_or_series): obj = frame_or_series(index).copy() obj.iloc[:] = index._na_value + # TODO: this casting should be unnecessary after GH#39763 is fixed + obj[:] = obj.astype(index.dtype) + assert np.all(obj.dtypes == index.dtype) + # result should be invariant to shuffling indexer = np.arange(len(index), dtype=np.intp) np.random.shuffle(indexer) diff --git a/pandas/tests/series/methods/test_quantile.py b/pandas/tests/series/methods/test_quantile.py index e8386a5edee60..461c81bc3b44f 100644 --- a/pandas/tests/series/methods/test_quantile.py +++ b/pandas/tests/series/methods/test_quantile.py @@ -1,8 +1,6 @@ import numpy as np import pytest -import pandas.util._test_decorators as td - from pandas.core.dtypes.common import is_integer import pandas as pd From 34a41d88ea04d6668548755ff804ba7be9efcb4e Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 3 Mar 2021 08:21:57 -0800 Subject: [PATCH 09/10] TST: remove AM skips --- pandas/core/frame.py | 7 ++----- pandas/core/internals/managers.py | 3 --- pandas/tests/frame/methods/test_describe.py | 5 ----- pandas/tests/frame/methods/test_quantile.py | 10 ++++------ pandas/tests/groupby/aggregate/test_aggregate.py | 1 - pandas/tests/groupby/test_apply.py | 1 - pandas/tests/groupby/test_categorical.py | 4 ---- pandas/tests/groupby/test_function.py | 8 -------- pandas/tests/groupby/test_groupby.py | 3 --- pandas/tests/groupby/test_quantile.py | 5 ----- pandas/tests/reductions/test_reductions.py | 3 --- pandas/tests/resample/test_base.py | 3 --- pandas/tests/series/methods/test_describe.py | 5 ----- 13 files changed, 6 insertions(+), 52 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 642fb237b90a1..a9dd15353ccf6 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -9662,9 +9662,8 @@ def quantile( q = Index(q, dtype=np.float64) data = self._get_numeric_data() if numeric_only else self axis = self._get_axis_number(axis) - is_transposed = axis == 1 - if is_transposed: + if axis == 1: data = data.T if len(data.columns) == 0: @@ -9674,9 +9673,7 @@ def quantile( return self._constructor([], index=q, columns=cols) return self._constructor_sliced([], index=cols, name=q, dtype=np.float64) - res = data._mgr.quantile( - qs=q, axis=1, interpolation=interpolation, transposed=is_transposed - ) + res = data._mgr.quantile(qs=q, axis=1, interpolation=interpolation) result = self._constructor(res) return result diff --git a/pandas/core/internals/managers.py b/pandas/core/internals/managers.py index fa8810fd0a389..e1acf8ea16ba0 100644 --- a/pandas/core/internals/managers.py +++ b/pandas/core/internals/managers.py @@ -518,7 +518,6 @@ def quantile( *, qs: Float64Index, axis: int = 0, - transposed: bool = False, interpolation="linear", ) -> BlockManager: """ @@ -531,8 +530,6 @@ def quantile( axis: reduction axis, default 0 consolidate: bool, default True. Join together blocks having same dtype - transposed: bool, default False - we are holding transposed data interpolation : type of interpolation, default 'linear' qs : list of the quantiles to be computed diff --git a/pandas/tests/frame/methods/test_describe.py b/pandas/tests/frame/methods/test_describe.py index 0b4ce0dfa80fc..fa91eb928e35c 100644 --- a/pandas/tests/frame/methods/test_describe.py +++ b/pandas/tests/frame/methods/test_describe.py @@ -1,8 +1,6 @@ import numpy as np import pytest -import pandas.util._test_decorators as td - import pandas as pd from pandas import ( Categorical, @@ -13,9 +11,6 @@ ) import pandas._testing as tm -# TODO(ArrayManager) quantile is needed for describe() -pytestmark = td.skip_array_manager_not_yet_implemented - class TestDataFrameDescribe: def test_describe_bool_in_mixed_frame(self): diff --git a/pandas/tests/frame/methods/test_quantile.py b/pandas/tests/frame/methods/test_quantile.py index 3888b87e9464f..9d56bb6314534 100644 --- a/pandas/tests/frame/methods/test_quantile.py +++ b/pandas/tests/frame/methods/test_quantile.py @@ -1,8 +1,6 @@ import numpy as np import pytest -import pandas.util._test_decorators as td - import pandas as pd from pandas import ( DataFrame, @@ -524,13 +522,13 @@ def test_quantile_empty_no_columns(self): expected.columns.name = "captain tightpants" tm.assert_frame_equal(result, expected) - @td.skip_array_manager_invalid_test - def test_quantile_item_cache(self): + def test_quantile_item_cache(self, using_array_manager): # previous behavior incorrect retained an invalid _item_cache entry df = DataFrame(np.random.randn(4, 3), columns=["A", "B", "C"]) df["D"] = df["A"] * 2 ser = df["A"] - assert len(df._mgr.blocks) == 2 + if not using_array_manager: + assert len(df._mgr.blocks) == 2 df.quantile(numeric_only=False) ser.values[0] = 99 @@ -617,7 +615,7 @@ def test_quantile_ea_all_na(self, index, frame_or_series): obj.iloc[:] = index._na_value - # TODO: this casting should be unnecessary after GH#39763 is fixed + # TODO(ArrayManager): this casting should be unnecessary after GH#39763 is fixed obj[:] = obj.astype(index.dtype) assert np.all(obj.dtypes == index.dtype) diff --git a/pandas/tests/groupby/aggregate/test_aggregate.py b/pandas/tests/groupby/aggregate/test_aggregate.py index ce75d37d2e776..860aa40fe0324 100644 --- a/pandas/tests/groupby/aggregate/test_aggregate.py +++ b/pandas/tests/groupby/aggregate/test_aggregate.py @@ -46,7 +46,6 @@ def test_agg_regression1(tsframe): tm.assert_frame_equal(result, expected) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile/describe def test_agg_must_agg(df): grouped = df.groupby("A")["C"] diff --git a/pandas/tests/groupby/test_apply.py b/pandas/tests/groupby/test_apply.py index 79ec0af267234..eb54887cea277 100644 --- a/pandas/tests/groupby/test_apply.py +++ b/pandas/tests/groupby/test_apply.py @@ -318,7 +318,6 @@ def test_groupby_as_index_apply(df): tm.assert_index_equal(res, ind) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_apply_concat_preserve_names(three_group): grouped = three_group.groupby(["A", "B"]) diff --git a/pandas/tests/groupby/test_categorical.py b/pandas/tests/groupby/test_categorical.py index a7247c2c04761..1c250998c344f 100644 --- a/pandas/tests/groupby/test_categorical.py +++ b/pandas/tests/groupby/test_categorical.py @@ -83,7 +83,6 @@ def get_stats(group): assert result.index.names[0] == "C" -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_basic(): cats = Categorical( @@ -540,7 +539,6 @@ def test_dataframe_categorical_ordered_observed_sort(ordered, observed, sort): assert False, msg -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_datetime(): # GH9049: ensure backward compatibility levels = pd.date_range("2014-01-01", periods=4) @@ -606,7 +604,6 @@ def test_categorical_index(): tm.assert_frame_equal(result, expected) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_describe_categorical_columns(): # GH 11558 cats = CategoricalIndex( @@ -621,7 +618,6 @@ def test_describe_categorical_columns(): tm.assert_categorical_equal(result.stack().columns.values, cats.values) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_unstack_categorical(): # GH11558 (example is taken from the original issue) df = DataFrame( diff --git a/pandas/tests/groupby/test_function.py b/pandas/tests/groupby/test_function.py index 598465a951e0f..cab5417e81445 100644 --- a/pandas/tests/groupby/test_function.py +++ b/pandas/tests/groupby/test_function.py @@ -367,7 +367,6 @@ def test_mad(self, gb, gni): result = gni.mad() tm.assert_frame_equal(result, expected) - @td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_describe(self, df, gb, gni): # describe expected_index = Index([1, 3], name="A") @@ -924,13 +923,11 @@ def test_is_monotonic_decreasing(in_vals, out_vals): # -------------------------------- -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_apply_describe_bug(mframe): grouped = mframe.groupby(level="first") grouped.describe() # it works! -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_series_describe_multikey(): ts = tm.makeTimeSeries() grouped = ts.groupby([lambda x: x.year, lambda x: x.month]) @@ -940,7 +937,6 @@ def test_series_describe_multikey(): tm.assert_series_equal(result["min"], grouped.min(), check_names=False) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_series_describe_single(): ts = tm.makeTimeSeries() grouped = ts.groupby(lambda x: x.month) @@ -955,7 +951,6 @@ def test_series_index_name(df): assert result.index.name == "A" -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_frame_describe_multikey(tsframe): grouped = tsframe.groupby([lambda x: x.year, lambda x: x.month]) result = grouped.describe() @@ -978,7 +973,6 @@ def test_frame_describe_multikey(tsframe): tm.assert_frame_equal(result, expected) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_frame_describe_tupleindex(): # GH 14848 - regression from 0.19.0 to 0.19.1 @@ -998,7 +992,6 @@ def test_frame_describe_tupleindex(): df2.groupby("key").describe() -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_frame_describe_unstacked_format(): # GH 4792 prices = { @@ -1025,7 +1018,6 @@ def test_frame_describe_unstacked_format(): tm.assert_frame_equal(result, expected) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile @pytest.mark.filterwarnings( "ignore:" "indexing past lexsort depth may impact performance:" diff --git a/pandas/tests/groupby/test_groupby.py b/pandas/tests/groupby/test_groupby.py index 8cbb9d2443cb2..afde1daca74c1 100644 --- a/pandas/tests/groupby/test_groupby.py +++ b/pandas/tests/groupby/test_groupby.py @@ -7,7 +7,6 @@ from pandas.compat import IS64 from pandas.errors import PerformanceWarning -import pandas.util._test_decorators as td import pandas as pd from pandas import ( @@ -211,7 +210,6 @@ def f(grp): tm.assert_series_equal(result, e) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_pass_args_kwargs(ts, tsframe): def f(x, q=None, axis=0): return np.percentile(x, q, axis=axis) @@ -366,7 +364,6 @@ def f3(x): df2.groupby("a").apply(f3) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_attr_wrapper(ts): grouped = ts.groupby(lambda x: x.weekday()) diff --git a/pandas/tests/groupby/test_quantile.py b/pandas/tests/groupby/test_quantile.py index 2924348e98b56..9c9d1aa881890 100644 --- a/pandas/tests/groupby/test_quantile.py +++ b/pandas/tests/groupby/test_quantile.py @@ -1,8 +1,6 @@ import numpy as np import pytest -import pandas.util._test_decorators as td - import pandas as pd from pandas import ( DataFrame, @@ -10,9 +8,6 @@ ) import pandas._testing as tm -# TODO(ArrayManager) quantile -pytestmark = td.skip_array_manager_not_yet_implemented - @pytest.mark.parametrize( "interpolation", ["linear", "lower", "higher", "nearest", "midpoint"] diff --git a/pandas/tests/reductions/test_reductions.py b/pandas/tests/reductions/test_reductions.py index e3145e0cc5c9f..decff32baa970 100644 --- a/pandas/tests/reductions/test_reductions.py +++ b/pandas/tests/reductions/test_reductions.py @@ -6,8 +6,6 @@ import numpy as np import pytest -import pandas.util._test_decorators as td - import pandas as pd from pandas import ( Categorical, @@ -293,7 +291,6 @@ def test_numpy_minmax_timedelta64(self): with pytest.raises(ValueError, match=errmsg): np.argmax(td, out=0) - @td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile def test_timedelta_ops(self): # GH#4984 # make sure ops return Timedelta diff --git a/pandas/tests/resample/test_base.py b/pandas/tests/resample/test_base.py index 733a8c0aa58ec..bf3e6d822ab19 100644 --- a/pandas/tests/resample/test_base.py +++ b/pandas/tests/resample/test_base.py @@ -3,8 +3,6 @@ import numpy as np import pytest -import pandas.util._test_decorators as td - from pandas import ( DataFrame, NaT, @@ -247,7 +245,6 @@ def test_resampler_is_iterable(series): tm.assert_series_equal(rv, gv) -@td.skip_array_manager_not_yet_implemented # TODO(ArrayManager) quantile @all_ts def test_resample_quantile(series): # GH 15023 diff --git a/pandas/tests/series/methods/test_describe.py b/pandas/tests/series/methods/test_describe.py index 1113efc972e76..bdb308ddbfd58 100644 --- a/pandas/tests/series/methods/test_describe.py +++ b/pandas/tests/series/methods/test_describe.py @@ -1,7 +1,5 @@ import numpy as np -import pandas.util._test_decorators as td - from pandas import ( Period, Series, @@ -11,9 +9,6 @@ ) import pandas._testing as tm -# TODO(ArrayManager) quantile is needed for describe() -pytestmark = td.skip_array_manager_not_yet_implemented - class TestSeriesDescribe: def test_describe(self): From b0167d2961b2d1b2b7fece6d16da8918f8e0a801 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 3 Mar 2021 11:00:55 -0800 Subject: [PATCH 10/10] isort fixup --- pandas/core/array_algos/quantile.py | 10 ++++++++-- pandas/core/internals/blocks.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pandas/core/array_algos/quantile.py b/pandas/core/array_algos/quantile.py index 47384ede8eeca..802fc4db0a36d 100644 --- a/pandas/core/array_algos/quantile.py +++ b/pandas/core/array_algos/quantile.py @@ -7,8 +7,14 @@ from pandas._libs import lib from pandas._typing import ArrayLike -from pandas.core.dtypes.common import is_list_like, is_sparse -from pandas.core.dtypes.missing import isna, na_value_for_dtype +from pandas.core.dtypes.common import ( + is_list_like, + is_sparse, +) +from pandas.core.dtypes.missing import ( + isna, + na_value_for_dtype, +) from pandas.core.nanops import nanpercentile diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index f8ce3eb05139d..023cf70522d2c 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -81,7 +81,7 @@ setitem_datetimelike_compat, validate_putmask, ) -from pandas.core.array_algos.quantile import quantile_with_mask, quantile_compat +from pandas.core.array_algos.quantile import quantile_compat from pandas.core.array_algos.replace import ( compare_or_regex_search, replace_regex,