Skip to content

Commit 9a678df

Browse files
authored
REF: simplify BlockManager.quantile (pandas-dev#39618)
1 parent a515bdd commit 9a678df

File tree

3 files changed

+45
-91
lines changed

3 files changed

+45
-91
lines changed

pandas/core/frame.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -9414,6 +9414,14 @@ def quantile(
94149414
"""
94159415
validate_percentile(q)
94169416

9417+
if not is_list_like(q):
9418+
# BlockManager.quantile expects listlike, so we wrap and unwrap here
9419+
res = self.quantile(
9420+
[q], axis=axis, numeric_only=numeric_only, interpolation=interpolation
9421+
)
9422+
return res.iloc[0]
9423+
9424+
q = Index(q, dtype=np.float64)
94179425
data = self._get_numeric_data() if numeric_only else self
94189426
axis = self._get_axis_number(axis)
94199427
is_transposed = axis == 1
@@ -9432,10 +9440,7 @@ def quantile(
94329440
qs=q, axis=1, interpolation=interpolation, transposed=is_transposed
94339441
)
94349442

9435-
if result.ndim == 2:
9436-
result = self._constructor(result)
9437-
else:
9438-
result = self._constructor_sliced(result, name=q)
9443+
result = self._constructor(result)
94399444

94409445
if is_transposed:
94419446
result = result.T

pandas/core/internals/blocks.py

+17-18
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
from pandas.core.nanops import nanpercentile
8383

8484
if TYPE_CHECKING:
85-
from pandas import Index
85+
from pandas import Float64Index, Index
8686
from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
8787

8888

@@ -1389,30 +1389,33 @@ def _unstack(self, unstacker, fill_value, new_placement):
13891389
blocks = [make_block(new_values, placement=new_placement)]
13901390
return blocks, mask
13911391

1392-
def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block:
1392+
def quantile(
1393+
self, qs: Float64Index, interpolation="linear", axis: int = 0
1394+
) -> Block:
13931395
"""
13941396
compute the quantiles of the
13951397
13961398
Parameters
13971399
----------
1398-
qs: a scalar or list of the quantiles to be computed
1399-
interpolation: type of interpolation, default 'linear'
1400-
axis: axis to compute, default 0
1400+
qs : Float64Index
1401+
List of the quantiles to be computed.
1402+
interpolation : str, default 'linear'
1403+
Type of interpolation.
1404+
axis : int, default 0
1405+
Axis to compute.
14011406
14021407
Returns
14031408
-------
14041409
Block
14051410
"""
14061411
# We should always have ndim == 2 because Series dispatches to DataFrame
14071412
assert self.ndim == 2
1413+
assert axis == 1 # only ever called this way
1414+
assert is_list_like(qs) # caller is responsible for this
14081415

14091416
values = self.get_values()
14101417

14111418
is_empty = values.shape[axis] == 0
1412-
orig_scalar = not is_list_like(qs)
1413-
if orig_scalar:
1414-
# make list-like, unpack later
1415-
qs = [qs]
14161419

14171420
if is_empty:
14181421
# create the array of na_values
@@ -1436,14 +1439,7 @@ def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block:
14361439
result = np.array(result, copy=False)
14371440
result = result.T
14381441

1439-
if orig_scalar and not lib.is_scalar(result):
1440-
# result could be scalar in case with is_empty and self.ndim == 1
1441-
assert result.shape[-1] == 1, result.shape
1442-
result = result[..., 0]
1443-
result = lib.item_from_zerodim(result)
1444-
1445-
ndim = np.ndim(result)
1446-
return make_block(result, placement=np.arange(len(result)), ndim=ndim)
1442+
return make_block(result, placement=self.mgr_locs, ndim=2)
14471443

14481444
def _replace_coerce(
14491445
self,
@@ -2190,7 +2186,10 @@ def fillna(
21902186
value, limit=limit, inplace=inplace, downcast=downcast
21912187
)
21922188

2193-
def quantile(self, qs, interpolation="linear", axis: int = 0) -> Block:
2189+
def quantile(
2190+
self, qs: Float64Index, interpolation="linear", axis: int = 0
2191+
) -> Block:
2192+
assert axis == 1 # only ever called this way
21942193
naive = self.values.view("M8[ns]")
21952194

21962195
# TODO(EA2D): kludge for 2D block with 1D values

pandas/core/internals/managers.py

+19-69
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
is_extension_array_dtype,
3232
is_list_like,
3333
)
34-
from pandas.core.dtypes.concat import concat_compat
3534
from pandas.core.dtypes.dtypes import ExtensionDtype
3635
from pandas.core.dtypes.generic import ABCDataFrame, ABCPandasArray, ABCSeries
3736
from pandas.core.dtypes.missing import array_equals, isna
@@ -40,7 +39,7 @@
4039
from pandas.core.arrays.sparse import SparseDtype
4140
from pandas.core.construction import extract_array
4241
from pandas.core.indexers import maybe_convert_indices
43-
from pandas.core.indexes.api import Index, ensure_index
42+
from pandas.core.indexes.api import Float64Index, Index, ensure_index
4443
from pandas.core.internals.base import DataManager
4544
from pandas.core.internals.blocks import (
4645
Block,
@@ -441,11 +440,11 @@ def apply(
441440

442441
def quantile(
443442
self,
443+
*,
444+
qs: Float64Index,
444445
axis: int = 0,
445446
transposed: bool = False,
446447
interpolation="linear",
447-
qs=None,
448-
numeric_only=None,
449448
) -> BlockManager:
450449
"""
451450
Iterate over blocks applying quantile reduction.
@@ -460,8 +459,7 @@ def quantile(
460459
transposed: bool, default False
461460
we are holding transposed data
462461
interpolation : type of interpolation, default 'linear'
463-
qs : a scalar or list of the quantiles to be computed
464-
numeric_only : ignored
462+
qs : list of the quantiles to be computed
465463
466464
Returns
467465
-------
@@ -470,73 +468,25 @@ def quantile(
470468
# Series dispatches to DataFrame for quantile, which allows us to
471469
# simplify some of the code here and in the blocks
472470
assert self.ndim >= 2
471+
assert is_list_like(qs) # caller is responsible for this
472+
assert axis == 1 # only ever called this way
473473

474-
def get_axe(block, qs, axes):
475-
# Because Series dispatches to DataFrame, we will always have
476-
# block.ndim == 2
477-
from pandas import Float64Index
478-
479-
if is_list_like(qs):
480-
ax = Float64Index(qs)
481-
else:
482-
ax = axes[0]
483-
return ax
484-
485-
axes, blocks = [], []
486-
for b in self.blocks:
487-
block = b.quantile(axis=axis, qs=qs, interpolation=interpolation)
488-
489-
axe = get_axe(b, qs, axes=self.axes)
490-
491-
axes.append(axe)
492-
blocks.append(block)
493-
494-
# note that some DatetimeTZ, Categorical are always ndim==1
495-
ndim = {b.ndim for b in blocks}
496-
assert 0 not in ndim, ndim
497-
498-
if 2 in ndim:
499-
500-
new_axes = list(self.axes)
501-
502-
# multiple blocks that are reduced
503-
if len(blocks) > 1:
504-
new_axes[1] = axes[0]
505-
506-
# reset the placement to the original
507-
for b, sb in zip(blocks, self.blocks):
508-
b.mgr_locs = sb.mgr_locs
509-
510-
else:
511-
new_axes[axis] = Index(np.concatenate([ax._values for ax in axes]))
512-
513-
if transposed:
514-
new_axes = new_axes[::-1]
515-
blocks = [
516-
b.make_block(b.values.T, placement=np.arange(b.shape[1]))
517-
for b in blocks
518-
]
519-
520-
return type(self)(blocks, new_axes)
521-
522-
# single block, i.e. ndim == {1}
523-
values = concat_compat([b.values for b in blocks])
524-
525-
# compute the orderings of our original data
526-
if len(self.blocks) > 1:
474+
new_axes = list(self.axes)
475+
new_axes[1] = Float64Index(qs)
527476

528-
indexer = np.empty(len(self.axes[0]), dtype=np.intp)
529-
i = 0
530-
for b in self.blocks:
531-
for j in b.mgr_locs:
532-
indexer[j] = i
533-
i = i + 1
477+
blocks = [
478+
blk.quantile(axis=axis, qs=qs, interpolation=interpolation)
479+
for blk in self.blocks
480+
]
534481

535-
values = values.take(indexer)
482+
if transposed:
483+
new_axes = new_axes[::-1]
484+
blocks = [
485+
b.make_block(b.values.T, placement=np.arange(b.shape[1]))
486+
for b in blocks
487+
]
536488

537-
return SingleBlockManager(
538-
make_block(values, ndim=1, placement=np.arange(len(values))), axes[0]
539-
)
489+
return type(self)(blocks, new_axes)
540490

541491
def isna(self, func) -> BlockManager:
542492
return self.apply("apply", func=func)

0 commit comments

Comments
 (0)