Skip to content

Commit 121ecd2

Browse files
authored
Merge branch 'main' into main
2 parents 1713918 + 59f6a33 commit 121ecd2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+830
-487
lines changed

ci/code_checks.sh

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,6 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
7070
--format=actions \
7171
-i ES01 `# For now it is ok if docstrings are missing the extended summary` \
7272
-i "pandas.Series.dt PR01" `# Accessors are implemented as classes, but we do not document the Parameters section` \
73-
-i "pandas.Categorical.__array__ SA01" \
74-
-i "pandas.Categorical.codes SA01" \
75-
-i "pandas.Categorical.dtype SA01" \
76-
-i "pandas.Categorical.from_codes SA01" \
77-
-i "pandas.Categorical.ordered SA01" \
78-
-i "pandas.CategoricalDtype.categories SA01" \
79-
-i "pandas.CategoricalDtype.ordered SA01" \
80-
-i "pandas.CategoricalIndex.codes SA01" \
81-
-i "pandas.CategoricalIndex.ordered SA01" \
82-
-i "pandas.DataFrame.__dataframe__ SA01" \
83-
-i "pandas.DataFrame.at_time PR01" \
84-
-i "pandas.DataFrame.kurt RT03,SA01" \
85-
-i "pandas.DataFrame.kurtosis RT03,SA01" \
8673
-i "pandas.DataFrame.max RT03" \
8774
-i "pandas.DataFrame.mean RT03,SA01" \
8875
-i "pandas.DataFrame.median RT03,SA01" \
@@ -98,35 +85,17 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
9885
-i "pandas.DataFrame.swaplevel SA01" \
9986
-i "pandas.DataFrame.to_markdown SA01" \
10087
-i "pandas.DataFrame.var PR01,RT03,SA01" \
101-
-i "pandas.DatetimeIndex.indexer_at_time PR01,RT03" \
102-
-i "pandas.DatetimeIndex.snap PR01,RT03" \
103-
-i "pandas.DatetimeIndex.to_period RT03" \
104-
-i "pandas.DatetimeIndex.to_pydatetime RT03,SA01" \
10588
-i "pandas.Grouper PR02" \
10689
-i "pandas.Index PR07" \
107-
-i "pandas.Index.append PR07,RT03,SA01" \
108-
-i "pandas.Index.difference PR07,RT03,SA01" \
109-
-i "pandas.Index.drop PR07,SA01" \
110-
-i "pandas.Index.duplicated RT03" \
11190
-i "pandas.Index.get_indexer PR07,SA01" \
11291
-i "pandas.Index.get_indexer_for PR01,SA01" \
11392
-i "pandas.Index.get_indexer_non_unique PR07,SA01" \
11493
-i "pandas.Index.get_loc PR07,RT03,SA01" \
115-
-i "pandas.Index.identical PR01,SA01" \
116-
-i "pandas.Index.insert PR07,RT03,SA01" \
117-
-i "pandas.Index.intersection PR07,RT03,SA01" \
11894
-i "pandas.Index.join PR07,RT03,SA01" \
11995
-i "pandas.Index.names GL08" \
120-
-i "pandas.Index.nunique RT03" \
12196
-i "pandas.Index.putmask PR01,RT03" \
12297
-i "pandas.Index.ravel PR01,RT03" \
123-
-i "pandas.Index.reindex PR07" \
124-
-i "pandas.Index.slice_indexer PR07,RT03,SA01" \
12598
-i "pandas.Index.str PR01,SA01" \
126-
-i "pandas.Index.symmetric_difference PR07,RT03,SA01" \
127-
-i "pandas.Index.take PR01,PR07" \
128-
-i "pandas.Index.union PR07,RT03,SA01" \
129-
-i "pandas.Index.view GL08" \
13099
-i "pandas.Int16Dtype SA01" \
131100
-i "pandas.Int32Dtype SA01" \
132101
-i "pandas.Int64Dtype SA01" \
@@ -215,15 +184,13 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
215184
-i "pandas.Series SA01" \
216185
-i "pandas.Series.__iter__ RT03,SA01" \
217186
-i "pandas.Series.add PR07" \
218-
-i "pandas.Series.at_time PR01" \
219187
-i "pandas.Series.backfill PR01,SA01" \
220188
-i "pandas.Series.case_when RT03" \
221189
-i "pandas.Series.cat PR07,SA01" \
222190
-i "pandas.Series.cat.add_categories PR01,PR02" \
223191
-i "pandas.Series.cat.as_ordered PR01" \
224192
-i "pandas.Series.cat.as_unordered PR01" \
225193
-i "pandas.Series.cat.codes SA01" \
226-
-i "pandas.Series.cat.ordered SA01" \
227194
-i "pandas.Series.cat.remove_categories PR01,PR02" \
228195
-i "pandas.Series.cat.remove_unused_categories PR01" \
229196
-i "pandas.Series.cat.rename_categories PR01,PR02" \
@@ -247,7 +214,7 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
247214
-i "pandas.Series.dt.round PR01,PR02" \
248215
-i "pandas.Series.dt.seconds SA01" \
249216
-i "pandas.Series.dt.strftime PR01,PR02" \
250-
-i "pandas.Series.dt.to_period PR01,PR02,RT03" \
217+
-i "pandas.Series.dt.to_period PR01,PR02" \
251218
-i "pandas.Series.dt.total_seconds PR01" \
252219
-i "pandas.Series.dt.tz_convert PR01,PR02" \
253220
-i "pandas.Series.dt.tz_localize PR01,PR02" \
@@ -276,7 +243,6 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
276243
-i "pandas.Series.mode SA01" \
277244
-i "pandas.Series.mul PR07" \
278245
-i "pandas.Series.ne PR07,SA01" \
279-
-i "pandas.Series.nunique RT03" \
280246
-i "pandas.Series.pad PR01,SA01" \
281247
-i "pandas.Series.plot PR02,SA01" \
282248
-i "pandas.Series.pop RT03,SA01" \

doc/source/whatsnew/v3.0.0.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ Other enhancements
5151
- Users can globally disable any ``PerformanceWarning`` by setting the option ``mode.performance_warnings`` to ``False`` (:issue:`56920`)
5252
- :meth:`Styler.format_index_names` can now be used to format the index and column names (:issue:`48936` and :issue:`47489`)
5353
- :class:`.errors.DtypeWarning` improved to include column names when mixed data types are detected (:issue:`58174`)
54+
- :meth:`DataFrame.corrwith` now accepts ``min_periods`` as optional arguments, as in :meth:`DataFrame.corr` and :meth:`Series.corr` (:issue:`9490`)
5455
- :meth:`DataFrame.cummin`, :meth:`DataFrame.cummax`, :meth:`DataFrame.cumprod` and :meth:`DataFrame.cumsum` methods now have a ``numeric_only`` parameter (:issue:`53072`)
5556
- :meth:`DataFrame.fillna` and :meth:`Series.fillna` can now accept ``value=None``; for non-object dtype the corresponding NA value will be used (:issue:`57723`)
5657
- :meth:`Series.cummin` and :meth:`Series.cummax` now supports :class:`CategoricalDtype` (:issue:`52335`)
58+
- :meth:`Series.plot` now correctly handle the ``ylabel`` parameter for pie charts, allowing for explicit control over the y-axis label (:issue:`58239`)
5759

5860
.. ---------------------------------------------------------------------------
5961
.. _whatsnew_300.notable_bug_fixes:
@@ -232,6 +234,7 @@ Removal of prior version deprecations/changes
232234
- :meth:`SeriesGroupBy.agg` no longer pins the name of the group to the input passed to the provided ``func`` (:issue:`51703`)
233235
- All arguments except ``name`` in :meth:`Index.rename` are now keyword only (:issue:`56493`)
234236
- All arguments except the first ``path``-like argument in IO writers are now keyword only (:issue:`54229`)
237+
- Changed behavior of :meth:`Series.__getitem__` and :meth:`Series.__setitem__` to always treat integer keys as labels, never as positional, consistent with :class:`DataFrame` behavior (:issue:`50617`)
235238
- Disallow allowing logical operations (``||``, ``&``, ``^``) between pandas objects and dtype-less sequences (e.g. ``list``, ``tuple``); wrap the objects in :class:`Series`, :class:`Index`, or ``np.array`` first instead (:issue:`52264`)
236239
- Disallow automatic casting to object in :class:`Series` logical operations (``&``, ``^``, ``||``) between series with mismatched indexes and dtypes other than ``object`` or ``bool`` (:issue:`52538`)
237240
- Disallow calling :meth:`Series.replace` or :meth:`DataFrame.replace` without a ``value`` and with non-dict-like ``to_replace`` (:issue:`33302`)
@@ -345,13 +348,17 @@ Performance improvements
345348
- Performance improvement in :meth:`Index.join` by propagating cached attributes in cases where the result matches one of the inputs (:issue:`57023`)
346349
- Performance improvement in :meth:`Index.take` when ``indices`` is a full range indexer from zero to length of index (:issue:`56806`)
347350
- Performance improvement in :meth:`Index.to_frame` returning a :class:`RangeIndex` columns of a :class:`Index` when possible. (:issue:`58018`)
351+
- Performance improvement in :meth:`MultiIndex._engine` to use smaller dtypes if possible (:issue:`58411`)
348352
- Performance improvement in :meth:`MultiIndex.equals` for equal length indexes (:issue:`56990`)
349353
- Performance improvement in :meth:`MultiIndex.memory_usage` to ignore the index engine when it isn't already cached. (:issue:`58385`)
350354
- Performance improvement in :meth:`RangeIndex.__getitem__` with a boolean mask or integers returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57588`)
351355
- Performance improvement in :meth:`RangeIndex.append` when appending the same index (:issue:`57252`)
352356
- Performance improvement in :meth:`RangeIndex.argmin` and :meth:`RangeIndex.argmax` (:issue:`57823`)
353357
- Performance improvement in :meth:`RangeIndex.insert` returning a :class:`RangeIndex` instead of a :class:`Index` when the :class:`RangeIndex` is empty. (:issue:`57833`)
354358
- Performance improvement in :meth:`RangeIndex.round` returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57824`)
359+
- Performance improvement in :meth:`RangeIndex.searchsorted` (:issue:`58376`)
360+
- Performance improvement in :meth:`RangeIndex.to_numpy` when specifying an ``na_value`` (:issue:`58376`)
361+
- Performance improvement in :meth:`RangeIndex.value_counts` (:issue:`58376`)
355362
- Performance improvement in :meth:`RangeIndex.join` returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57651`, :issue:`57752`)
356363
- Performance improvement in :meth:`RangeIndex.reindex` returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57647`, :issue:`57752`)
357364
- Performance improvement in :meth:`RangeIndex.take` returning a :class:`RangeIndex` instead of a :class:`Index` when possible. (:issue:`57445`, :issue:`57752`)
@@ -397,6 +404,7 @@ Numeric
397404

398405
Conversion
399406
^^^^^^^^^^
407+
- Bug in :meth:`DataFrame.astype` not casting ``values`` for Arrow-based dictionary dtype correctly (:issue:`58479`)
400408
- Bug in :meth:`DataFrame.update` bool dtype being converted to object (:issue:`55509`)
401409
- Bug in :meth:`Series.astype` might modify read-only array inplace when casting to a string dtype (:issue:`57212`)
402410
- Bug in :meth:`Series.reindex` not maintaining ``float32`` type when a ``reindex`` introduces a missing value (:issue:`45857`)
@@ -477,6 +485,7 @@ Styler
477485
Other
478486
^^^^^
479487
- Bug in :class:`DataFrame` when passing a ``dict`` with a NA scalar and ``columns`` that would always return ``np.nan`` (:issue:`57205`)
488+
- Bug in :func:`eval` where the names of the :class:`Series` were not preserved when using ``engine="numexpr"``. (:issue:`10239`)
480489
- Bug in :func:`unique` on :class:`Index` not always returning :class:`Index` (:issue:`57043`)
481490
- Bug in :meth:`DataFrame.eval` and :meth:`DataFrame.query` which caused an exception when using NumPy attributes via ``@`` notation, e.g., ``df.eval("@np.floor(a)")``. (:issue:`58041`)
482491
- Bug in :meth:`DataFrame.eval` and :meth:`DataFrame.query` which did not allow to use ``tan`` function. (:issue:`55091`)
@@ -488,6 +497,7 @@ Other
488497
- Bug in :meth:`Series.rank` that doesn't preserve missing values for nullable integers when ``na_option='keep'``. (:issue:`56976`)
489498
- Bug in :meth:`Series.replace` and :meth:`DataFrame.replace` inconsistently replacing matching instances when ``regex=True`` and missing values are present. (:issue:`56599`)
490499
- Bug in Dataframe Interchange Protocol implementation was returning incorrect results for data buffers' associated dtype, for string and datetime columns (:issue:`54781`)
500+
- Bug in ``Series.list`` methods not preserving the original :class:`Index`. (:issue:`58425`)
491501

492502
.. ***DO NOT USE THIS SECTION***
493503

pandas/_libs/hashing.pyx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import numpy as np
1111

1212
from numpy cimport (
1313
import_array,
14+
ndarray,
1415
uint8_t,
1516
uint64_t,
1617
)
@@ -22,7 +23,7 @@ from pandas._libs.util cimport is_nan
2223

2324
@cython.boundscheck(False)
2425
def hash_object_array(
25-
object[:] arr, str key, str encoding="utf8"
26+
ndarray[object, ndim=1] arr, str key, str encoding="utf8"
2627
) -> np.ndarray[np.uint64]:
2728
"""
2829
Parameters

pandas/_libs/index.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,13 @@ class MaskedBoolEngine(MaskedUInt8Engine): ...
7474

7575
class BaseMultiIndexCodesEngine:
7676
levels: list[np.ndarray]
77-
offsets: np.ndarray # ndarray[uint64_t, ndim=1]
77+
offsets: np.ndarray # np.ndarray[..., ndim=1]
7878

7979
def __init__(
8080
self,
8181
levels: list[Index], # all entries hashable
8282
labels: list[np.ndarray], # all entries integer-dtyped
83-
offsets: np.ndarray, # np.ndarray[np.uint64, ndim=1]
83+
offsets: np.ndarray, # np.ndarray[..., ndim=1]
8484
) -> None: ...
8585
def get_indexer(self, target: npt.NDArray[np.object_]) -> npt.NDArray[np.intp]: ...
8686
def _extract_level_codes(self, target: MultiIndex) -> np.ndarray: ...

pandas/_libs/index.pyx

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ from numpy cimport (
99
intp_t,
1010
ndarray,
1111
uint8_t,
12-
uint64_t,
1312
)
1413

1514
cnp.import_array()
@@ -699,16 +698,15 @@ cdef class BaseMultiIndexCodesEngine:
699698
Keys are located by first locating each component against the respective
700699
level, then locating (the integer representation of) codes.
701700
"""
702-
def __init__(self, object levels, object labels,
703-
ndarray[uint64_t, ndim=1] offsets):
701+
def __init__(self, object levels, object labels, ndarray offsets):
704702
"""
705703
Parameters
706704
----------
707705
levels : list-like of numpy arrays
708706
Levels of the MultiIndex.
709707
labels : list-like of numpy arrays of integer dtype
710708
Labels of the MultiIndex.
711-
offsets : numpy array of uint64 dtype
709+
offsets : numpy array of int dtype
712710
Pre-calculated offsets, one for each level of the index.
713711
"""
714712
self.levels = levels
@@ -718,8 +716,9 @@ cdef class BaseMultiIndexCodesEngine:
718716
# with positive integers (-1 for NaN becomes 1). This enables us to
719717
# differentiate between values that are missing in other and matching
720718
# NaNs. We will set values that are not found to 0 later:
721-
labels_arr = np.array(labels, dtype="int64").T + multiindex_nulls_shift
722-
codes = labels_arr.astype("uint64", copy=False)
719+
codes = np.array(labels).T
720+
codes += multiindex_nulls_shift # inplace sum optimisation
721+
723722
self.level_has_nans = [-1 in lab for lab in labels]
724723

725724
# Map each codes combination in the index to an integer unambiguously
@@ -731,8 +730,37 @@ cdef class BaseMultiIndexCodesEngine:
731730
# integers representing labels: we will use its get_loc and get_indexer
732731
self._base.__init__(self, lab_ints)
733732

734-
def _codes_to_ints(self, ndarray[uint64_t] codes) -> np.ndarray:
735-
raise NotImplementedError("Implemented by subclass") # pragma: no cover
733+
def _codes_to_ints(self, ndarray codes) -> np.ndarray:
734+
"""
735+
Transform combination(s) of uint in one uint or Python integer (each), in a
736+
strictly monotonic way (i.e. respecting the lexicographic order of integer
737+
combinations).
738+
739+
Parameters
740+
----------
741+
codes : 1- or 2-dimensional array of dtype uint
742+
Combinations of integers (one per row)
743+
744+
Returns
745+
-------
746+
scalar or 1-dimensional array, of dtype _codes_dtype
747+
Integer(s) representing one combination (each).
748+
"""
749+
# To avoid overflows, first make sure we are working with the right dtype:
750+
codes = codes.astype(self._codes_dtype, copy=False)
751+
752+
# Shift the representation of each level by the pre-calculated number of bits:
753+
codes <<= self.offsets # inplace shift optimisation
754+
755+
# Now sum and OR are in fact interchangeable. This is a simple
756+
# composition of the (disjunct) significant bits of each level (i.e.
757+
# each column in "codes") in a single positive integer (per row):
758+
if codes.ndim == 1:
759+
# Single key
760+
return np.bitwise_or.reduce(codes)
761+
762+
# Multiple keys
763+
return np.bitwise_or.reduce(codes, axis=1)
736764

737765
def _extract_level_codes(self, target) -> np.ndarray:
738766
"""
@@ -757,7 +785,7 @@ cdef class BaseMultiIndexCodesEngine:
757785
codes[codes > 0] += 1
758786
if self.level_has_nans[i]:
759787
codes[target.codes[i] == -1] += 1
760-
return self._codes_to_ints(np.array(level_codes, dtype="uint64").T)
788+
return self._codes_to_ints(np.array(level_codes, dtype=self._codes_dtype).T)
761789

762790
def get_indexer(self, target: np.ndarray) -> np.ndarray:
763791
"""
@@ -788,7 +816,7 @@ cdef class BaseMultiIndexCodesEngine:
788816
raise KeyError(key)
789817

790818
# Transform indices into single integer:
791-
lab_int = self._codes_to_ints(np.array(indices, dtype="uint64"))
819+
lab_int = self._codes_to_ints(np.array(indices, dtype=self._codes_dtype))
792820

793821
return self._base.get_loc(self, lab_int)
794822

pandas/_libs/lib.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2808,14 +2808,14 @@ def maybe_convert_objects(ndarray[object] objects,
28082808
from pandas.core.arrays import IntegerArray
28092809

28102810
# Set these values to 1 to be deterministic, match
2811-
# IntegerArray._internal_fill_value
2811+
# IntegerDtype._internal_fill_value
28122812
result[mask] = 1
28132813
result = IntegerArray(result, mask)
28142814
elif result is floats and convert_to_nullable_dtype:
28152815
from pandas.core.arrays import FloatingArray
28162816

28172817
# Set these values to 1.0 to be deterministic, match
2818-
# FloatingArray._internal_fill_value
2818+
# FloatingDtype._internal_fill_value
28192819
result[mask] = 1.0
28202820
result = FloatingArray(result, mask)
28212821

pandas/core/arrays/arrow/accessors.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ def len(self) -> Series:
110110
from pandas import Series
111111

112112
value_lengths = pc.list_value_length(self._pa_array)
113-
return Series(value_lengths, dtype=ArrowDtype(value_lengths.type))
113+
return Series(
114+
value_lengths, dtype=ArrowDtype(value_lengths.type), index=self._data.index
115+
)
114116

115117
def __getitem__(self, key: int | slice) -> Series:
116118
"""
@@ -149,7 +151,9 @@ def __getitem__(self, key: int | slice) -> Series:
149151
# if key < 0:
150152
# key = pc.add(key, pc.list_value_length(self._pa_array))
151153
element = pc.list_element(self._pa_array, key)
152-
return Series(element, dtype=ArrowDtype(element.type))
154+
return Series(
155+
element, dtype=ArrowDtype(element.type), index=self._data.index
156+
)
153157
elif isinstance(key, slice):
154158
if pa_version_under11p0:
155159
raise NotImplementedError(
@@ -167,7 +171,7 @@ def __getitem__(self, key: int | slice) -> Series:
167171
if step is None:
168172
step = 1
169173
sliced = pc.list_slice(self._pa_array, start, stop, step)
170-
return Series(sliced, dtype=ArrowDtype(sliced.type))
174+
return Series(sliced, dtype=ArrowDtype(sliced.type), index=self._data.index)
171175
else:
172176
raise ValueError(f"key must be an int or slice, got {type(key).__name__}")
173177

@@ -195,15 +199,17 @@ def flatten(self) -> Series:
195199
... )
196200
>>> s.list.flatten()
197201
0 1
198-
1 2
199-
2 3
200-
3 3
202+
0 2
203+
0 3
204+
1 3
201205
dtype: int64[pyarrow]
202206
"""
203207
from pandas import Series
204208

205-
flattened = pc.list_flatten(self._pa_array)
206-
return Series(flattened, dtype=ArrowDtype(flattened.type))
209+
counts = pa.compute.list_value_length(self._pa_array)
210+
flattened = pa.compute.list_flatten(self._pa_array)
211+
index = self._data.index.repeat(counts.fill_null(pa.scalar(0, counts.type)))
212+
return Series(flattened, dtype=ArrowDtype(flattened.type), index=index)
207213

208214

209215
class StructAccessor(ArrowAccessor):

pandas/core/arrays/arrow/array.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,8 @@ def _box_pa_array(
525525
if pa_type is not None and pa_array.type != pa_type:
526526
if pa.types.is_dictionary(pa_type):
527527
pa_array = pa_array.dictionary_encode()
528+
if pa_array.type != pa_type:
529+
pa_array = pa_array.cast(pa_type)
528530
else:
529531
try:
530532
pa_array = pa_array.cast(pa_type)

0 commit comments

Comments
 (0)