Skip to content

Commit 7ef6b33

Browse files
committed
Merge branch 'master' into nui-regression
2 parents bf40ecd + 12cea49 commit 7ef6b33

File tree

10 files changed

+79
-93
lines changed

10 files changed

+79
-93
lines changed

pandas/core/generic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8858,7 +8858,7 @@ def _where(
88588858
elif len(cond[icond]) == len(other):
88598859

88608860
# try to not change dtype at first
8861-
new_other = np.asarray(self)
8861+
new_other = self._values
88628862
new_other = new_other.copy()
88638863
new_other[icond] = other
88648864
other = new_other

pandas/core/indexes/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2906,8 +2906,13 @@ def difference(self, other, sort=None):
29062906
other, result_name = self._convert_can_do_setop(other)
29072907

29082908
if self.equals(other):
2909+
# Note: we do not (yet) sort even if sort=None GH#24959
29092910
return self[:0].rename(result_name)
29102911

2912+
if len(other) == 0:
2913+
# Note: we do not (yet) sort even if sort=None GH#24959
2914+
return self.rename(result_name)
2915+
29112916
result = self._difference(other, sort=sort)
29122917
return self._wrap_setop_result(other, result)
29132918

pandas/core/indexes/multi.py

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3676,42 +3676,9 @@ def _intersection(self, other, sort=False):
36763676
zip(*uniq_tuples), sortorder=0, names=result_names
36773677
)
36783678

3679-
def difference(self, other, sort=None):
3680-
"""
3681-
Compute set difference of two MultiIndex objects
3682-
3683-
Parameters
3684-
----------
3685-
other : MultiIndex
3686-
sort : False or None, default None
3687-
Sort the resulting MultiIndex if possible
3688-
3689-
.. versionadded:: 0.24.0
3690-
3691-
.. versionchanged:: 0.24.1
3692-
3693-
Changed the default value from ``True`` to ``None``
3694-
(without change in behaviour).
3695-
3696-
Returns
3697-
-------
3698-
diff : MultiIndex
3699-
"""
3700-
self._validate_sort_keyword(sort)
3701-
self._assert_can_do_setop(other)
3679+
def _difference(self, other, sort):
37023680
other, result_names = self._convert_can_do_setop(other)
37033681

3704-
if len(other) == 0:
3705-
return self.rename(result_names)
3706-
3707-
if self.equals(other):
3708-
return MultiIndex(
3709-
levels=self.levels,
3710-
codes=[[]] * self.nlevels,
3711-
names=result_names,
3712-
verify_integrity=False,
3713-
)
3714-
37153682
this = self._get_unique_index()
37163683

37173684
indexer = this.get_indexer(other)

pandas/core/indexes/period.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -635,16 +635,6 @@ def _setop(self, other, sort, opname: str):
635635
def _intersection(self, other, sort=False):
636636
return self._setop(other, sort, opname="intersection")
637637

638-
def difference(self, other, sort=None):
639-
self._validate_sort_keyword(sort)
640-
self._assert_can_do_setop(other)
641-
other, result_name = self._convert_can_do_setop(other)
642-
643-
if self.equals(other):
644-
return self[:0].rename(result_name)
645-
646-
return self._difference(other, sort=sort)
647-
648638
def _difference(self, other, sort):
649639

650640
if is_object_dtype(other):

pandas/core/internals/blocks.py

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from datetime import timedelta
21
import inspect
32
import re
43
from typing import TYPE_CHECKING, Any, List, Optional, Type, Union, cast
@@ -8,7 +7,6 @@
87

98
from pandas._libs import (
109
Interval,
11-
NaT,
1210
Period,
1311
Timestamp,
1412
algos as libalgos,
@@ -86,6 +84,7 @@
8684

8785
if TYPE_CHECKING:
8886
from pandas import Index
87+
from pandas.core.arrays._mixins import NDArrayBackedExtensionArray
8988

9089

9190
class Block(PandasObject):
@@ -919,7 +918,11 @@ def setitem(self, indexer, value):
919918
if self._can_hold_element(value):
920919
# We only get here for non-Extension Blocks, so _try_coerce_args
921920
# is only relevant for DatetimeBlock and TimedeltaBlock
922-
if lib.is_scalar(value):
921+
if self.dtype.kind in ["m", "M"]:
922+
arr = self.array_values().T
923+
arr[indexer] = value
924+
return self
925+
elif lib.is_scalar(value):
923926
value = convert_scalar_for_putitemlike(value, values.dtype)
924927

925928
else:
@@ -1029,9 +1032,7 @@ def _putmask_simple(self, mask: np.ndarray, value: Any):
10291032
# GH#37833 np.putmask is more performant than __setitem__
10301033
np.putmask(values, mask, value)
10311034

1032-
def putmask(
1033-
self, mask, new, inplace: bool = False, axis: int = 0, transpose: bool = False
1034-
) -> List["Block"]:
1035+
def putmask(self, mask, new, axis: int = 0) -> List["Block"]:
10351036
"""
10361037
putmask the data to the block; it is possible that we may create a
10371038
new dtype of block
@@ -1042,16 +1043,13 @@ def putmask(
10421043
----------
10431044
mask : np.ndarray[bool], SparseArray[bool], or BooleanArray
10441045
new : a ndarray/object
1045-
inplace : bool, default False
1046-
Perform inplace modification.
10471046
axis : int
1048-
transpose : bool, default False
1049-
Set to True if self is stored with axes reversed.
10501047
10511048
Returns
10521049
-------
10531050
List[Block]
10541051
"""
1052+
transpose = self.ndim == 2
10551053
mask = _extract_bool_array(mask)
10561054
assert not isinstance(new, (ABCIndex, ABCSeries, ABCDataFrame))
10571055

@@ -1064,6 +1062,17 @@ def putmask(
10641062
if self._can_hold_element(new):
10651063
# We only get here for non-Extension Blocks, so _try_coerce_args
10661064
# is only relevant for DatetimeBlock and TimedeltaBlock
1065+
if self.dtype.kind in ["m", "M"]:
1066+
blk = self
1067+
if not inplace:
1068+
blk = self.copy()
1069+
arr = blk.array_values()
1070+
arr = cast("NDArrayBackedExtensionArray", arr)
1071+
if transpose:
1072+
arr = arr.T
1073+
arr.putmask(mask, new)
1074+
return [blk]
1075+
10671076
if lib.is_scalar(new):
10681077
new = convert_scalar_for_putitemlike(new, self.values.dtype)
10691078

@@ -1077,8 +1086,6 @@ def putmask(
10771086
new = np.repeat(new, new_values.shape[-1]).reshape(self.shape)
10781087
new = new.astype(new_values.dtype)
10791088

1080-
if new_values is self.values and not inplace:
1081-
new_values = new_values.copy()
10821089
# we require exact matches between the len of the
10831090
# values we are setting (or is compat). np.putmask
10841091
# doesn't check this and will simply truncate / pad
@@ -1099,6 +1106,7 @@ def putmask(
10991106
# `np.place` on the other hand uses the ``new`` values at it is
11001107
# to place in the masked locations of ``new_values``
11011108
np.place(new_values, mask, new)
1109+
# i.e. new_values[mask] = new
11021110
elif mask.shape[-1] == len(new) or len(new) == 1:
11031111
np.putmask(new_values, mask, new)
11041112
else:
@@ -1143,18 +1151,10 @@ def f(mask, val, idx):
11431151
nv = _putmask_smart(val, mask, n)
11441152
return nv
11451153

1146-
new_blocks = self.split_and_operate(mask, f, inplace)
1154+
new_blocks = self.split_and_operate(mask, f, True)
11471155
return new_blocks
11481156

1149-
if inplace:
1150-
return [self]
1151-
1152-
if transpose:
1153-
if new_values is None:
1154-
new_values = self.values if inplace else self.values.copy()
1155-
new_values = new_values.T
1156-
1157-
return [self.make_block(new_values)]
1157+
return [self]
11581158

11591159
def coerce_to_target_dtype(self, other):
11601160
"""
@@ -1697,17 +1697,13 @@ def set_inplace(self, locs, values):
16971697
assert locs.tolist() == [0]
16981698
self.values = values
16991699

1700-
def putmask(
1701-
self, mask, new, inplace: bool = False, axis: int = 0, transpose: bool = False
1702-
) -> List["Block"]:
1700+
def putmask(self, mask, new, axis: int = 0) -> List["Block"]:
17031701
"""
17041702
See Block.putmask.__doc__
17051703
"""
1706-
inplace = validate_bool_kwarg(inplace, "inplace")
1707-
17081704
mask = _extract_bool_array(mask)
17091705

1710-
new_values = self.values if inplace else self.values.copy()
1706+
new_values = self.values
17111707

17121708
if isinstance(new, (np.ndarray, ExtensionArray)) and len(new) == len(mask):
17131709
new = new[mask]
@@ -2376,16 +2372,6 @@ def _maybe_coerce_values(self, values):
23762372
def _holder(self):
23772373
return TimedeltaArray
23782374

2379-
def _can_hold_element(self, element: Any) -> bool:
2380-
tipo = maybe_infer_dtype_type(element)
2381-
if tipo is not None:
2382-
return issubclass(tipo.type, np.timedelta64)
2383-
elif element is NaT:
2384-
return True
2385-
elif isinstance(element, (timedelta, np.timedelta64)):
2386-
return True
2387-
return is_valid_nat_for_dtype(element, self.dtype)
2388-
23892375
def fillna(self, value, **kwargs):
23902376
# TODO(EA2D): if we operated on array_values, TDA.fillna would handle
23912377
# raising here.

pandas/core/internals/managers.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,6 @@ def setitem(self, indexer, value) -> "BlockManager":
565565
return self.apply("setitem", indexer=indexer, value=value)
566566

567567
def putmask(self, mask, new, align: bool = True, axis: int = 0):
568-
transpose = self.ndim == 2
569568

570569
if align:
571570
align_keys = ["new", "mask"]
@@ -578,9 +577,7 @@ def putmask(self, mask, new, align: bool = True, axis: int = 0):
578577
align_keys=align_keys,
579578
mask=mask,
580579
new=new,
581-
inplace=True,
582580
axis=axis,
583-
transpose=transpose,
584581
)
585582

586583
def diff(self, n: int, axis: int) -> "BlockManager":

pandas/tests/indexes/datetimes/test_setops.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,8 @@ def test_difference(self, tz, sort):
326326
(rng3, other3, expected3),
327327
]:
328328
result_diff = rng.difference(other, sort)
329-
if sort is None:
329+
if sort is None and len(other):
330+
# We dont sort (yet?) when empty GH#24959
330331
expected = expected.sort_values()
331332
tm.assert_index_equal(result_diff, expected)
332333

pandas/tests/indexes/multi/test_setops.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,9 @@ def test_intersection(idx, sort):
294294
# assert result.equals(tuples)
295295

296296

297-
@pytest.mark.parametrize("method", ["intersection", "union"])
297+
@pytest.mark.parametrize(
298+
"method", ["intersection", "union", "difference", "symmetric_difference"]
299+
)
298300
def test_setop_with_categorical(idx, sort, method):
299301
other = idx.to_flat_index().astype("category")
300302
res_names = [None] * idx.nlevels

pandas/tests/indexes/period/test_setops.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ def test_difference(self, sort):
318318
(rng7, other7, expected7),
319319
]:
320320
result_difference = rng.difference(other, sort=sort)
321-
if sort is None:
321+
if sort is None and len(other):
322+
# We dont sort (yet?) when empty GH#24959
322323
expected = expected.sort_values()
323324
tm.assert_index_equal(result_difference, expected)
324325

pandas/tests/indexing/test_indexing.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from pandas.core.dtypes.common import is_float_dtype, is_integer_dtype
1111

1212
import pandas as pd
13-
from pandas import DataFrame, Index, NaT, Series, date_range
13+
from pandas import DataFrame, Index, NaT, Series, date_range, offsets, timedelta_range
1414
import pandas._testing as tm
1515
from pandas.core.indexing import maybe_numeric_slice, non_reducing_slice
1616
from pandas.tests.indexing.common import _mklbl
@@ -970,19 +970,22 @@ class TestDatetimelikeCoercion:
970970
@pytest.mark.parametrize("indexer", [setitem, loc, iloc])
971971
def test_setitem_dt64_string_scalar(self, tz_naive_fixture, indexer):
972972
# dispatching _can_hold_element to underling DatetimeArray
973-
# TODO(EA2D) use tz_naive_fixture once DatetimeBlock is backed by DTA
974973
tz = tz_naive_fixture
975974

976975
dti = date_range("2016-01-01", periods=3, tz=tz)
977976
ser = Series(dti)
978977

979978
values = ser._values
980979

981-
indexer(ser)[0] = "2018-01-01"
980+
newval = "2018-01-01"
981+
values._validate_setitem_value(newval)
982+
983+
indexer(ser)[0] = newval
982984

983985
if tz is None:
984986
# TODO(EA2D): we can make this no-copy in tz-naive case too
985987
assert ser.dtype == dti.dtype
988+
assert ser._values._data is values._data
986989
else:
987990
assert ser._values is values
988991

@@ -993,7 +996,6 @@ def test_setitem_dt64_string_scalar(self, tz_naive_fixture, indexer):
993996
@pytest.mark.parametrize("indexer", [setitem, loc, iloc])
994997
def test_setitem_dt64_string_values(self, tz_naive_fixture, indexer, key, box):
995998
# dispatching _can_hold_element to underling DatetimeArray
996-
# TODO(EA2D) use tz_naive_fixture once DatetimeBlock is backed by DTA
997999
tz = tz_naive_fixture
9981000

9991001
if isinstance(key, slice) and indexer is loc:
@@ -1012,9 +1014,44 @@ def test_setitem_dt64_string_values(self, tz_naive_fixture, indexer, key, box):
10121014
if tz is None:
10131015
# TODO(EA2D): we can make this no-copy in tz-naive case too
10141016
assert ser.dtype == dti.dtype
1017+
assert ser._values._data is values._data
10151018
else:
10161019
assert ser._values is values
10171020

1021+
@pytest.mark.parametrize("scalar", ["3 Days", offsets.Hour(4)])
1022+
@pytest.mark.parametrize("indexer", [setitem, loc, iloc])
1023+
def test_setitem_td64_scalar(self, indexer, scalar):
1024+
# dispatching _can_hold_element to underling TimedeltaArray
1025+
tdi = timedelta_range("1 Day", periods=3)
1026+
ser = Series(tdi)
1027+
1028+
values = ser._values
1029+
values._validate_setitem_value(scalar)
1030+
1031+
indexer(ser)[0] = scalar
1032+
assert ser._values._data is values._data
1033+
1034+
@pytest.mark.parametrize("box", [list, np.array, pd.array])
1035+
@pytest.mark.parametrize(
1036+
"key", [[0, 1], slice(0, 2), np.array([True, True, False])]
1037+
)
1038+
@pytest.mark.parametrize("indexer", [setitem, loc, iloc])
1039+
def test_setitem_td64_string_values(self, indexer, key, box):
1040+
# dispatching _can_hold_element to underling TimedeltaArray
1041+
if isinstance(key, slice) and indexer is loc:
1042+
key = slice(0, 1)
1043+
1044+
tdi = timedelta_range("1 Day", periods=3)
1045+
ser = Series(tdi)
1046+
1047+
values = ser._values
1048+
1049+
newvals = box(["10 Days", "44 hours"])
1050+
values._validate_setitem_value(newvals)
1051+
1052+
indexer(ser)[key] = newvals
1053+
assert ser._values._data is values._data
1054+
10181055

10191056
def test_extension_array_cross_section():
10201057
# A cross-section of a homogeneous EA should be an EA

0 commit comments

Comments
 (0)