Skip to content

Commit 951d06d

Browse files
authored
Merge branch 'main' into pylint-precommit
2 parents cef817e + c68d053 commit 951d06d

File tree

9 files changed

+114
-41
lines changed

9 files changed

+114
-41
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ repos:
272272
|/_testing/
273273
- id: autotyping
274274
name: autotyping
275-
entry: python -m libcst.tool codemod autotyping.AutotypeCommand --aggressive
275+
entry: python -m scripts.run_autotyping
276276
types_or: [python, pyi]
277277
files: ^pandas
278278
exclude: ^(pandas/tests|pandas/_version.py|pandas/io/clipboard)

doc/source/whatsnew/v1.6.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ Datetimelike
165165
^^^^^^^^^^^^
166166
- Bug in :func:`pandas.infer_freq`, raising ``TypeError`` when inferred on :class:`RangeIndex` (:issue:`47084`)
167167
- Bug in :class:`DatetimeIndex` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``dtype`` or data (:issue:`48659`)
168+
- Bug in subtracting a ``datetime`` scalar from :class:`DatetimeIndex` failing to retain the original ``freq`` attribute (:issue:`48818`)
168169

169170
Timedelta
170171
^^^^^^^^^

pandas/core/arrays/datetimelike.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,13 +1134,12 @@ def _add_datetimelike_scalar(self, other) -> DatetimeArray:
11341134
return DatetimeArray._simple_new(result, dtype=result.dtype)
11351135

11361136
if self._reso != other._reso:
1137-
# Just as with Timestamp/Timedelta, we cast to the lower resolution
1138-
# so long as doing so is lossless.
1137+
# Just as with Timestamp/Timedelta, we cast to the higher resolution
11391138
if self._reso < other._reso:
1140-
other = other._as_unit(self._unit, round_ok=False)
1141-
else:
11421139
unit = npy_unit_to_abbrev(other._reso)
11431140
self = self._as_unit(unit)
1141+
else:
1142+
other = other._as_unit(self._unit)
11441143

11451144
i8 = self.asi8
11461145
result = checked_add_with_arr(i8, other.value, arr_mask=self._isnan)
@@ -1186,7 +1185,16 @@ def _sub_datetimelike_scalar(self, other: datetime | np.datetime64):
11861185

11871186
i8 = self.asi8
11881187
result = checked_add_with_arr(i8, -other.value, arr_mask=self._isnan)
1189-
return result.view("timedelta64[ns]")
1188+
res_m8 = result.view(f"timedelta64[{self._unit}]")
1189+
1190+
new_freq = None
1191+
if isinstance(self.freq, Tick):
1192+
# adding a scalar preserves freq
1193+
new_freq = self.freq
1194+
1195+
from pandas.core.arrays import TimedeltaArray
1196+
1197+
return TimedeltaArray._simple_new(res_m8, dtype=res_m8.dtype, freq=new_freq)
11901198

11911199
@final
11921200
def _sub_datetime_arraylike(self, other):
@@ -1296,12 +1304,11 @@ def _add_timedelta_arraylike(
12961304
self = cast("DatetimeArray | TimedeltaArray", self)
12971305

12981306
if self._reso != other._reso:
1299-
# Just as with Timestamp/Timedelta, we cast to the lower resolution
1300-
# so long as doing so is lossless.
1307+
# Just as with Timestamp/Timedelta, we cast to the higher resolution
13011308
if self._reso < other._reso:
1302-
other = other._as_unit(self._unit)
1303-
else:
13041309
self = self._as_unit(other._unit)
1310+
else:
1311+
other = other._as_unit(self._unit)
13051312

13061313
self_i8 = self.asi8
13071314
other_i8 = other.asi8
@@ -2039,7 +2046,7 @@ def _unit(self) -> str:
20392046

20402047
def _as_unit(self: TimelikeOpsT, unit: str) -> TimelikeOpsT:
20412048
dtype = np.dtype(f"{self.dtype.kind}8[{unit}]")
2042-
new_values = astype_overflowsafe(self._ndarray, dtype, round_ok=False)
2049+
new_values = astype_overflowsafe(self._ndarray, dtype, round_ok=True)
20432050

20442051
if isinstance(self.dtype, np.dtype):
20452052
new_dtype = new_values.dtype

pandas/tests/arrays/test_datetimes.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,17 @@ def test_compare_mismatched_resolutions(self, comparison_op):
207207
np_res = op(left._ndarray, right._ndarray)
208208
tm.assert_numpy_array_equal(np_res[1:], ~expected[1:])
209209

210+
def test_add_mismatched_reso_doesnt_downcast(self):
211+
# https://github.com/pandas-dev/pandas/pull/48748#issuecomment-1260181008
212+
td = pd.Timedelta(microseconds=1)
213+
dti = pd.date_range("2016-01-01", periods=3) - td
214+
dta = dti._data._as_unit("us")
215+
216+
res = dta + td._as_unit("us")
217+
# even though the result is an even number of days
218+
# (so we _could_ downcast to unit="s"), we do not.
219+
assert res._unit == "us"
220+
210221

211222
class TestDatetimeArrayComparisons:
212223
# TODO: merge this into tests/arithmetic/test_datetime64 once it is

pandas/tests/arrays/test_timedeltas.py

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,13 @@ def test_add_pdnat(self, tda):
104104
def test_add_datetimelike_scalar(self, tda, tz_naive_fixture):
105105
ts = pd.Timestamp("2016-01-01", tz=tz_naive_fixture)
106106

107-
expected = tda + ts._as_unit(tda._unit)
107+
expected = tda._as_unit("ns") + ts
108108
res = tda + ts
109109
tm.assert_extension_array_equal(res, expected)
110110
res = ts + tda
111111
tm.assert_extension_array_equal(res, expected)
112112

113-
ts += Timedelta(1) # so we can't cast losslessly
114-
msg = "Cannot losslessly convert units"
115-
with pytest.raises(ValueError, match=msg):
116-
# mismatched reso -> check that we don't give an incorrect result
117-
tda + ts
118-
with pytest.raises(ValueError, match=msg):
119-
# mismatched reso -> check that we don't give an incorrect result
120-
ts + tda
121-
122-
ts = ts._as_unit(tda._unit)
113+
ts += Timedelta(1) # case where we can't cast losslessly
123114

124115
exp_values = tda._ndarray + ts.asm8
125116
expected = (
@@ -185,35 +176,19 @@ def test_add_timedeltaarraylike(self, tda):
185176
# TODO(2.0): just do `tda_nano = tda.astype("m8[ns]")`
186177
tda_nano = TimedeltaArray(tda._ndarray.astype("m8[ns]"))
187178

188-
msg = "mis-matched resolutions is not yet supported"
189-
expected = tda * 2
179+
expected = tda_nano * 2
190180
res = tda_nano + tda
191181
tm.assert_extension_array_equal(res, expected)
192182
res = tda + tda_nano
193183
tm.assert_extension_array_equal(res, expected)
194184

195-
expected = tda * 0
185+
expected = tda_nano * 0
196186
res = tda - tda_nano
197187
tm.assert_extension_array_equal(res, expected)
198188

199189
res = tda_nano - tda
200190
tm.assert_extension_array_equal(res, expected)
201191

202-
tda_nano[:] = np.timedelta64(1, "ns") # can't round losslessly
203-
msg = "Cannot losslessly cast '-?1 ns' to"
204-
with pytest.raises(ValueError, match=msg):
205-
tda_nano + tda
206-
with pytest.raises(ValueError, match=msg):
207-
tda + tda_nano
208-
with pytest.raises(ValueError, match=msg):
209-
tda - tda_nano
210-
with pytest.raises(ValueError, match=msg):
211-
tda_nano - tda
212-
213-
result = tda_nano + tda_nano
214-
expected = tda_nano * 2
215-
tm.assert_extension_array_equal(result, expected)
216-
217192

218193
class TestTimedeltaArray:
219194
@pytest.mark.parametrize("dtype", [int, np.int32, np.int64, "uint32", "uint64"])

pandas/tests/indexes/datetimes/test_datetime.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,36 @@
1717

1818

1919
class TestDatetimeIndex:
20+
def test_sub_datetime_preserves_freq(self, tz_naive_fixture):
21+
# GH#48818
22+
dti = date_range("2016-01-01", periods=12, tz=tz_naive_fixture)
23+
24+
res = dti - dti[0]
25+
expected = pd.timedelta_range("0 Days", "11 Days")
26+
tm.assert_index_equal(res, expected)
27+
assert res.freq == expected.freq
28+
29+
@pytest.mark.xfail(
30+
reason="The inherited freq is incorrect bc dti.freq is incorrect "
31+
"https://github.com/pandas-dev/pandas/pull/48818/files#r982793461"
32+
)
33+
def test_sub_datetime_preserves_freq_across_dst(self):
34+
# GH#48818
35+
ts = Timestamp("2016-03-11", tz="US/Pacific")
36+
dti = date_range(ts, periods=4)
37+
38+
res = dti - dti[0]
39+
expected = pd.TimedeltaIndex(
40+
[
41+
pd.Timedelta(days=0),
42+
pd.Timedelta(days=1),
43+
pd.Timedelta(days=2),
44+
pd.Timedelta(days=2, hours=23),
45+
]
46+
)
47+
tm.assert_index_equal(res, expected)
48+
assert res.freq == expected.freq
49+
2050
def test_time_overflow_for_32bit_machines(self):
2151
# GH8943. On some machines NumPy defaults to np.int32 (for example,
2252
# 32-bit Linux machines). In the function _generate_regular_range

pandas/tests/plotting/frame/test_frame_color.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,18 @@ def test_if_scatterplot_colorbars_are_next_to_parent_axes(self):
199199
@pytest.mark.parametrize("kw", ["c", "color"])
200200
def test_scatter_with_c_column_name_with_colors(self, cmap, kw):
201201
# https://github.com/pandas-dev/pandas/issues/34316
202+
from pandas.plotting._matplotlib.compat import mpl_ge_3_6_0
203+
202204
df = DataFrame(
203205
[[5.1, 3.5], [4.9, 3.0], [7.0, 3.2], [6.4, 3.2], [5.9, 3.0]],
204206
columns=["length", "width"],
205207
)
206208
df["species"] = ["r", "r", "g", "g", "b"]
207-
ax = df.plot.scatter(x=0, y=1, cmap=cmap, **{kw: "species"})
209+
if mpl_ge_3_6_0() and cmap is not None:
210+
with tm.assert_produces_warning(UserWarning, check_stacklevel=False):
211+
ax = df.plot.scatter(x=0, y=1, cmap=cmap, **{kw: "species"})
212+
else:
213+
ax = df.plot.scatter(x=0, y=1, cmap=cmap, **{kw: "species"})
208214
assert ax.collections[0].colorbar is None
209215

210216
def test_scatter_colors(self):

pandas/tests/scalar/timestamp/test_timestamp.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,13 @@ def test_sub_timedeltalike_mismatched_reso(self, ts_tz):
10301030
assert res == exp
10311031
assert res._reso == max(ts._reso, other._reso)
10321032

1033+
def test_addition_doesnt_downcast_reso(self):
1034+
# https://github.com/pandas-dev/pandas/pull/48748#pullrequestreview-1122635413
1035+
ts = Timestamp(year=2022, month=1, day=1, microsecond=999999)._as_unit("us")
1036+
td = Timedelta(microseconds=1)._as_unit("us")
1037+
res = ts + td
1038+
assert res._reso == ts._reso
1039+
10331040
def test_sub_timedelta64_mismatched_reso(self, ts_tz):
10341041
ts = ts_tz
10351042

scripts/run_autotyping.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Script to run ``autotyping``, to get around the fact that
3+
pre-commit puts ``args`` before the list of files, whereas
4+
``autotyping`` wants the files to come after, see
5+
https://github.com/pandas-dev/pandas/issues/48808#issuecomment-1259711679.
6+
"""
7+
from __future__ import annotations
8+
9+
import argparse
10+
import subprocess
11+
import sys
12+
from typing import Sequence
13+
14+
15+
def main(argv: Sequence[str] | None = None) -> None:
16+
parser = argparse.ArgumentParser()
17+
parser.add_argument("paths", nargs="*")
18+
args = parser.parse_args(argv)
19+
if not args.paths:
20+
sys.exit(0)
21+
output = subprocess.run(
22+
[
23+
"python",
24+
"-m",
25+
"libcst.tool",
26+
"codemod",
27+
"autotyping.AutotypeCommand",
28+
*args.paths,
29+
"--aggressive",
30+
],
31+
)
32+
sys.exit(output.returncode)
33+
34+
35+
if __name__ == "__main__":
36+
main()

0 commit comments

Comments
 (0)