From aeed91c027dd30da25f6641a1784f81f2782b6df Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 25 Nov 2023 13:02:53 -0800 Subject: [PATCH 01/30] TYP: tighter typing in _apply_array (#56083) * TYP: tighter typing in _apply_array * comment * mypy fixup * mypy fixup * mypy fixup * update run_stubtest --- pandas/_libs/tslibs/offsets.pyi | 3 +- pandas/_libs/tslibs/offsets.pyx | 53 +++++++-------------------------- pandas/core/arrays/datetimes.py | 21 ++++++++----- scripts/run_stubtest.py | 1 - 4 files changed, 26 insertions(+), 52 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyi b/pandas/_libs/tslibs/offsets.pyi index aecc8cf681cf8..7eb8dc0813868 100644 --- a/pandas/_libs/tslibs/offsets.pyi +++ b/pandas/_libs/tslibs/offsets.pyi @@ -33,6 +33,7 @@ class ApplyTypeError(TypeError): ... class BaseOffset: n: int + normalize: bool def __init__(self, n: int = ..., normalize: bool = ...) -> None: ... def __eq__(self, other) -> bool: ... def __ne__(self, other) -> bool: ... @@ -85,7 +86,7 @@ class BaseOffset: @property def freqstr(self) -> str: ... def _apply(self, other): ... - def _apply_array(self, dtarr) -> None: ... + def _apply_array(self, dtarr: np.ndarray) -> np.ndarray: ... def rollback(self, dt: datetime) -> datetime: ... def rollforward(self, dt: datetime) -> datetime: ... def is_on_offset(self, dt: datetime) -> bool: ... diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 445330e813d2c..47e507a0150e2 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -110,33 +110,6 @@ cdef bint _is_normalized(datetime dt): return True -def apply_wrapper_core(func, self, other) -> ndarray: - result = func(self, other) - result = np.asarray(result) - - if self.normalize: - # TODO: Avoid circular/runtime import - from .vectorized import normalize_i8_timestamps - reso = get_unit_from_dtype(other.dtype) - result = normalize_i8_timestamps(result.view("i8"), None, reso=reso) - - return result - - -def apply_array_wraps(func): - # Note: normally we would use `@functools.wraps(func)`, but this does - # not play nicely with cython class methods - def wrapper(self, other) -> np.ndarray: - # other is a DatetimeArray - result = apply_wrapper_core(func, self, other) - return result - - # do @functools.wraps(func) manually since it doesn't work on cdef funcs - wrapper.__name__ = func.__name__ - wrapper.__doc__ = func.__doc__ - return wrapper - - def apply_wraps(func): # Note: normally we would use `@functools.wraps(func)`, but this does # not play nicely with cython class methods @@ -644,8 +617,9 @@ cdef class BaseOffset: def _apply(self, other): raise NotImplementedError("implemented by subclasses") - @apply_array_wraps - def _apply_array(self, dtarr): + def _apply_array(self, dtarr: np.ndarray) -> np.ndarray: + # NB: _apply_array does not handle respecting `self.normalize`, the + # caller (DatetimeArray) handles that in post-processing. raise NotImplementedError( f"DateOffset subclass {type(self).__name__} " "does not have a vectorized implementation" @@ -1399,8 +1373,7 @@ cdef class RelativeDeltaOffset(BaseOffset): "applied vectorized" ) - @apply_array_wraps - def _apply_array(self, dtarr): + def _apply_array(self, dtarr: np.ndarray) -> np.ndarray: reso = get_unit_from_dtype(dtarr.dtype) dt64other = np.asarray(dtarr) @@ -1814,8 +1787,7 @@ cdef class BusinessDay(BusinessMixin): days = n + 2 return days - @apply_array_wraps - def _apply_array(self, dtarr): + def _apply_array(self, dtarr: np.ndarray) -> np.ndarray: i8other = dtarr.view("i8") reso = get_unit_from_dtype(dtarr.dtype) res = self._shift_bdays(i8other, reso=reso) @@ -2361,8 +2333,7 @@ cdef class YearOffset(SingleConstructorOffset): months = years * 12 + (self.month - other.month) return shift_month(other, months, self._day_opt) - @apply_array_wraps - def _apply_array(self, dtarr): + def _apply_array(self, dtarr: np.ndarray) -> np.ndarray: reso = get_unit_from_dtype(dtarr.dtype) shifted = shift_quarters( dtarr.view("i8"), self.n, self.month, self._day_opt, modby=12, reso=reso @@ -2613,8 +2584,7 @@ cdef class QuarterOffset(SingleConstructorOffset): months = qtrs * 3 - months_since return shift_month(other, months, self._day_opt) - @apply_array_wraps - def _apply_array(self, dtarr): + def _apply_array(self, dtarr: np.ndarray) -> np.ndarray: reso = get_unit_from_dtype(dtarr.dtype) shifted = shift_quarters( dtarr.view("i8"), @@ -2798,8 +2768,7 @@ cdef class MonthOffset(SingleConstructorOffset): n = roll_convention(other.day, self.n, compare_day) return shift_month(other, n, self._day_opt) - @apply_array_wraps - def _apply_array(self, dtarr): + def _apply_array(self, dtarr: np.ndarray) -> np.ndarray: reso = get_unit_from_dtype(dtarr.dtype) shifted = shift_months(dtarr.view("i8"), self.n, self._day_opt, reso=reso) return shifted @@ -3029,10 +2998,9 @@ cdef class SemiMonthOffset(SingleConstructorOffset): return shift_month(other, months, to_day) - @apply_array_wraps @cython.wraparound(False) @cython.boundscheck(False) - def _apply_array(self, dtarr): + def _apply_array(self, dtarr: np.ndarray) -> np.ndarray: cdef: ndarray i8other = dtarr.view("i8") Py_ssize_t i, count = dtarr.size @@ -3254,8 +3222,7 @@ cdef class Week(SingleConstructorOffset): return other + timedelta(weeks=k) - @apply_array_wraps - def _apply_array(self, dtarr): + def _apply_array(self, dtarr: np.ndarray) -> np.ndarray: if self.weekday is None: td = timedelta(days=7 * self.n) unit = np.datetime_data(dtarr.dtype)[0] diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index d12c2e7c4ae3b..de5832ba31b70 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -790,7 +790,7 @@ def _assert_tzawareness_compat(self, other) -> None: # ----------------------------------------------------------------- # Arithmetic Methods - def _add_offset(self, offset) -> Self: + def _add_offset(self, offset: BaseOffset) -> Self: assert not isinstance(offset, Tick) if self.tz is not None: @@ -799,24 +799,31 @@ def _add_offset(self, offset) -> Self: values = self try: - result = offset._apply_array(values) - if result.dtype.kind == "i": - result = result.view(values.dtype) + res_values = offset._apply_array(values._ndarray) + if res_values.dtype.kind == "i": + # error: Argument 1 to "view" of "ndarray" has incompatible type + # "dtype[datetime64] | DatetimeTZDtype"; expected + # "dtype[Any] | type[Any] | _SupportsDType[dtype[Any]]" + res_values = res_values.view(values.dtype) # type: ignore[arg-type] except NotImplementedError: warnings.warn( "Non-vectorized DateOffset being applied to Series or DatetimeIndex.", PerformanceWarning, stacklevel=find_stack_level(), ) - result = self.astype("O") + offset + res_values = self.astype("O") + offset # TODO(GH#55564): as_unit will be unnecessary - result = type(self)._from_sequence(result).as_unit(self.unit) + result = type(self)._from_sequence(res_values).as_unit(self.unit) if not len(self): # GH#30336 _from_sequence won't be able to infer self.tz return result.tz_localize(self.tz) else: - result = type(self)._simple_new(result, dtype=result.dtype) + result = type(self)._simple_new(res_values, dtype=res_values.dtype) + if offset.normalize: + result = result.normalize() + result._freq = None + if self.tz is not None: result = result.tz_localize(self.tz) diff --git a/scripts/run_stubtest.py b/scripts/run_stubtest.py index 35cbbef08124e..6307afa1bc822 100644 --- a/scripts/run_stubtest.py +++ b/scripts/run_stubtest.py @@ -69,7 +69,6 @@ "pandas._libs.sparse.SparseIndex.to_block_index", "pandas._libs.sparse.SparseIndex.to_int_index", # TODO (decorator changes argument names) - "pandas._libs.tslibs.offsets.BaseOffset._apply_array", "pandas._libs.tslibs.offsets.BusinessHour.rollback", "pandas._libs.tslibs.offsets.BusinessHour.rollforward ", # type alias From 3530b3d21c99ebb57aafb4780f5b91a46bdaf6db Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 25 Nov 2023 13:05:01 -0800 Subject: [PATCH 02/30] TST: parametrize over dt64 unit (#56165) * TST: parametrize over dt64 unit * TST: specify dt64 unit --- pandas/tests/arithmetic/test_datetime64.py | 6 +- pandas/tests/frame/indexing/test_setitem.py | 5 +- pandas/tests/frame/methods/test_quantile.py | 23 ++++-- pandas/tests/frame/test_reductions.py | 20 ++++-- .../methods/test_groupby_shift_diff.py | 5 +- pandas/tests/groupby/test_timegrouper.py | 24 +++++-- .../tests/groupby/transform/test_transform.py | 21 +++--- .../indexes/datetimes/methods/test_astype.py | 5 +- .../tests/indexes/datetimes/test_indexing.py | 72 +++++++++---------- pandas/tests/indexes/datetimes/test_setops.py | 18 +++-- .../tests/indexes/datetimes/test_timezones.py | 31 +++++--- .../tests/indexes/interval/test_indexing.py | 9 ++- .../indexes/interval/test_interval_range.py | 3 + .../period/methods/test_to_timestamp.py | 22 ++++-- .../tests/indexing/multiindex/test_partial.py | 8 ++- .../tests/indexing/multiindex/test_setitem.py | 9 ++- pandas/tests/io/xml/test_xml_dtypes.py | 5 +- .../tests/resample/test_resampler_grouper.py | 7 +- pandas/tests/reshape/merge/test_join.py | 2 +- pandas/tests/series/methods/test_quantile.py | 42 +++++------ 20 files changed, 205 insertions(+), 132 deletions(-) diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index a63bfbf1835a9..9014ba4b6093e 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -905,7 +905,7 @@ def test_dt64arr_add_sub_td64_nat(self, box_with_array, tz_naive_fixture): dti = date_range("1994-04-01", periods=9, tz=tz, freq="QS") other = np.timedelta64("NaT") - expected = DatetimeIndex(["NaT"] * 9, tz=tz) + expected = DatetimeIndex(["NaT"] * 9, tz=tz).as_unit("ns") obj = tm.box_expected(dti, box_with_array) expected = tm.box_expected(expected, box_with_array) @@ -1590,13 +1590,13 @@ class TestDatetime64OverflowHandling: def test_dt64_overflow_masking(self, box_with_array): # GH#25317 - left = Series([Timestamp("1969-12-31")]) + left = Series([Timestamp("1969-12-31")], dtype="M8[ns]") right = Series([NaT]) left = tm.box_expected(left, box_with_array) right = tm.box_expected(right, box_with_array) - expected = TimedeltaIndex([NaT]) + expected = TimedeltaIndex([NaT], dtype="m8[ns]") expected = tm.box_expected(expected, box_with_array) result = left - right diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index bc632209ff7e1..0130820fc3de6 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -1354,7 +1354,8 @@ def test_setitem_frame_dup_cols_dtype(self): def test_frame_setitem_empty_dataframe(self): # GH#28871 - df = DataFrame({"date": [datetime(2000, 1, 1)]}).set_index("date") + dti = DatetimeIndex(["2000-01-01"], dtype="M8[ns]", name="date") + df = DataFrame({"date": dti}).set_index("date") df = df[0:0].copy() df["3010"] = None @@ -1363,6 +1364,6 @@ def test_frame_setitem_empty_dataframe(self): expected = DataFrame( [], columns=["3010", "2010"], - index=Index([], dtype="datetime64[ns]", name="date"), + index=dti[:0], ) tm.assert_frame_equal(df, expected) diff --git a/pandas/tests/frame/methods/test_quantile.py b/pandas/tests/frame/methods/test_quantile.py index dcec68ab3530d..787b77a5c725a 100644 --- a/pandas/tests/frame/methods/test_quantile.py +++ b/pandas/tests/frame/methods/test_quantile.py @@ -623,23 +623,23 @@ def test_quantile_nan(self, interp_method, request, using_array_manager): exp = DataFrame({"a": [3.0, 4.0], "b": [np.nan, np.nan]}, index=[0.5, 0.75]) tm.assert_frame_equal(res, exp) - def test_quantile_nat(self, interp_method, request, using_array_manager): + def test_quantile_nat(self, interp_method, request, using_array_manager, unit): interpolation, method = interp_method if method == "table" and using_array_manager: request.applymarker(pytest.mark.xfail(reason="Axis name incorrectly set.")) # full NaT column - df = DataFrame({"a": [pd.NaT, pd.NaT, pd.NaT]}) + df = DataFrame({"a": [pd.NaT, pd.NaT, pd.NaT]}, dtype=f"M8[{unit}]") res = df.quantile( 0.5, numeric_only=False, interpolation=interpolation, method=method ) - exp = Series([pd.NaT], index=["a"], name=0.5) + exp = Series([pd.NaT], index=["a"], name=0.5, dtype=f"M8[{unit}]") tm.assert_series_equal(res, exp) res = df.quantile( [0.5], numeric_only=False, interpolation=interpolation, method=method ) - exp = DataFrame({"a": [pd.NaT]}, index=[0.5]) + exp = DataFrame({"a": [pd.NaT]}, index=[0.5], dtype=f"M8[{unit}]") tm.assert_frame_equal(res, exp) # mixed non-null / full null column @@ -651,20 +651,29 @@ def test_quantile_nat(self, interp_method, request, using_array_manager): Timestamp("2012-01-03"), ], "b": [pd.NaT, pd.NaT, pd.NaT], - } + }, + dtype=f"M8[{unit}]", ) res = df.quantile( 0.5, numeric_only=False, interpolation=interpolation, method=method ) - exp = Series([Timestamp("2012-01-02"), pd.NaT], index=["a", "b"], name=0.5) + exp = Series( + [Timestamp("2012-01-02"), pd.NaT], + index=["a", "b"], + name=0.5, + dtype=f"M8[{unit}]", + ) tm.assert_series_equal(res, exp) res = df.quantile( [0.5], numeric_only=False, interpolation=interpolation, method=method ) exp = DataFrame( - [[Timestamp("2012-01-02"), pd.NaT]], index=[0.5], columns=["a", "b"] + [[Timestamp("2012-01-02"), pd.NaT]], + index=[0.5], + columns=["a", "b"], + dtype=f"M8[{unit}]", ) tm.assert_frame_equal(res, exp) diff --git a/pandas/tests/frame/test_reductions.py b/pandas/tests/frame/test_reductions.py index 20ad93e6dce4d..0d71fb0926df9 100644 --- a/pandas/tests/frame/test_reductions.py +++ b/pandas/tests/frame/test_reductions.py @@ -598,7 +598,7 @@ def test_sem(self, datetime_frame): "C": [1.0], "D": ["a"], "E": Categorical(["a"], categories=["a"]), - "F": to_datetime(["2000-1-2"]), + "F": pd.DatetimeIndex(["2000-01-02"], dtype="M8[ns]"), "G": to_timedelta(["1 days"]), }, ), @@ -610,7 +610,7 @@ def test_sem(self, datetime_frame): "C": [np.nan], "D": np.array([np.nan], dtype=object), "E": Categorical([np.nan], categories=["a"]), - "F": [pd.NaT], + "F": pd.DatetimeIndex([pd.NaT], dtype="M8[ns]"), "G": to_timedelta([pd.NaT]), }, ), @@ -621,7 +621,9 @@ def test_sem(self, datetime_frame): "I": [8, 9, np.nan, np.nan], "J": [1, np.nan, np.nan, np.nan], "K": Categorical(["a", np.nan, np.nan, np.nan], categories=["a"]), - "L": to_datetime(["2000-1-2", "NaT", "NaT", "NaT"]), + "L": pd.DatetimeIndex( + ["2000-01-02", "NaT", "NaT", "NaT"], dtype="M8[ns]" + ), "M": to_timedelta(["1 days", "nan", "nan", "nan"]), "N": [0, 1, 2, 3], }, @@ -633,7 +635,9 @@ def test_sem(self, datetime_frame): "I": [8, 9, np.nan, np.nan], "J": [1, np.nan, np.nan, np.nan], "K": Categorical([np.nan, "a", np.nan, np.nan], categories=["a"]), - "L": to_datetime(["NaT", "2000-1-2", "NaT", "NaT"]), + "L": pd.DatetimeIndex( + ["NaT", "2000-01-02", "NaT", "NaT"], dtype="M8[ns]" + ), "M": to_timedelta(["nan", "1 days", "nan", "nan"]), "N": [0, 1, 2, 3], }, @@ -648,13 +652,17 @@ def test_mode_dropna(self, dropna, expected): "C": [1, np.nan, np.nan, np.nan], "D": [np.nan, np.nan, "a", np.nan], "E": Categorical([np.nan, np.nan, "a", np.nan]), - "F": to_datetime(["NaT", "2000-1-2", "NaT", "NaT"]), + "F": pd.DatetimeIndex( + ["NaT", "2000-01-02", "NaT", "NaT"], dtype="M8[ns]" + ), "G": to_timedelta(["1 days", "nan", "nan", "nan"]), "H": [8, 8, 9, 9], "I": [9, 9, 8, 8], "J": [1, 1, np.nan, np.nan], "K": Categorical(["a", np.nan, "a", np.nan]), - "L": to_datetime(["2000-1-2", "2000-1-2", "NaT", "NaT"]), + "L": pd.DatetimeIndex( + ["2000-01-02", "2000-01-02", "NaT", "NaT"], dtype="M8[ns]" + ), "M": to_timedelta(["1 days", "nan", "1 days", "nan"]), "N": np.arange(4, dtype="int64"), } diff --git a/pandas/tests/groupby/methods/test_groupby_shift_diff.py b/pandas/tests/groupby/methods/test_groupby_shift_diff.py index f2d40867af03a..0ce6a6462a5d8 100644 --- a/pandas/tests/groupby/methods/test_groupby_shift_diff.py +++ b/pandas/tests/groupby/methods/test_groupby_shift_diff.py @@ -117,10 +117,11 @@ def test_group_diff_real_frame(any_real_numpy_dtype): [Timedelta("5 days"), Timedelta("6 days"), Timedelta("7 days")], ], ) -def test_group_diff_datetimelike(data): +def test_group_diff_datetimelike(data, unit): df = DataFrame({"a": [1, 2, 2], "b": data}) + df["b"] = df["b"].dt.as_unit(unit) result = df.groupby("a")["b"].diff() - expected = Series([NaT, NaT, Timedelta("1 days")], name="b") + expected = Series([NaT, NaT, Timedelta("1 days")], name="b").dt.as_unit(unit) tm.assert_series_equal(result, expected) diff --git a/pandas/tests/groupby/test_timegrouper.py b/pandas/tests/groupby/test_timegrouper.py index be02c7f79ba01..d1faab9cabfba 100644 --- a/pandas/tests/groupby/test_timegrouper.py +++ b/pandas/tests/groupby/test_timegrouper.py @@ -98,11 +98,17 @@ def test_groupby_with_timegrouper(self): for df in [df_original, df_reordered]: df = df.set_index(["Date"]) + exp_dti = date_range( + "20130901", + "20131205", + freq="5D", + name="Date", + inclusive="left", + unit=df.index.unit, + ) expected = DataFrame( {"Buyer": 0, "Quantity": 0}, - index=date_range( - "20130901", "20131205", freq="5D", name="Date", inclusive="left" - ), + index=exp_dti, ) # Cast to object to avoid implicit cast when setting entry to "CarlCarlCarl" expected = expected.astype({"Buyer": object}) @@ -514,6 +520,7 @@ def test_groupby_groups_datetimeindex(self): groups = grouped.groups assert isinstance(next(iter(groups.keys())), datetime) + def test_groupby_groups_datetimeindex2(self): # GH#11442 index = date_range("2015/01/01", periods=5, name="date") df = DataFrame({"A": [5, 6, 7, 8, 9], "B": [1, 2, 3, 4, 5]}, index=index) @@ -876,7 +883,9 @@ def test_groupby_apply_timegrouper_with_nat_dict_returns( res = gb["Quantity"].apply(lambda x: {"foo": len(x)}) - dti = date_range("2013-09-01", "2013-10-01", freq="5D", name="Date") + df = gb.obj + unit = df["Date"]._values.unit + dti = date_range("2013-09-01", "2013-10-01", freq="5D", name="Date", unit=unit) mi = MultiIndex.from_arrays([dti, ["foo"] * len(dti)]) expected = Series([3, 0, 0, 0, 0, 0, 2], index=mi, name="Quantity") tm.assert_series_equal(res, expected) @@ -890,7 +899,9 @@ def test_groupby_apply_timegrouper_with_nat_scalar_returns( res = gb["Quantity"].apply(lambda x: x.iloc[0] if len(x) else np.nan) - dti = date_range("2013-09-01", "2013-10-01", freq="5D", name="Date") + df = gb.obj + unit = df["Date"]._values.unit + dti = date_range("2013-09-01", "2013-10-01", freq="5D", name="Date", unit=unit) expected = Series( [18, np.nan, np.nan, np.nan, np.nan, np.nan, 5], index=dti._with_freq(None), @@ -919,9 +930,10 @@ def test_groupby_apply_timegrouper_with_nat_apply_squeeze( with tm.assert_produces_warning(FutureWarning, match=msg): res = gb.apply(lambda x: x["Quantity"] * 2) + dti = Index([Timestamp("2013-12-31")], dtype=df["Date"].dtype, name="Date") expected = DataFrame( [[36, 6, 6, 10, 2]], - index=Index([Timestamp("2013-12-31")], name="Date"), + index=dti, columns=Index([0, 1, 5, 2, 3], name="Quantity"), ) tm.assert_frame_equal(res, expected) diff --git a/pandas/tests/groupby/transform/test_transform.py b/pandas/tests/groupby/transform/test_transform.py index cf122832d86b4..a6f160d92fb66 100644 --- a/pandas/tests/groupby/transform/test_transform.py +++ b/pandas/tests/groupby/transform/test_transform.py @@ -115,12 +115,15 @@ def test_transform_fast2(): ) result = df.groupby("grouping").transform("first") - dates = [ - Timestamp("2014-1-1"), - Timestamp("2014-1-2"), - Timestamp("2014-1-2"), - Timestamp("2014-1-4"), - ] + dates = pd.Index( + [ + Timestamp("2014-1-1"), + Timestamp("2014-1-2"), + Timestamp("2014-1-2"), + Timestamp("2014-1-4"), + ], + dtype="M8[ns]", + ) expected = DataFrame( {"f": [1.1, 2.1, 2.1, 4.5], "d": dates, "i": [1, 2, 2, 4]}, columns=["f", "i", "d"], @@ -532,7 +535,7 @@ def test_series_fast_transform_date(): Timestamp("2014-1-2"), Timestamp("2014-1-4"), ] - expected = Series(dates, name="d") + expected = Series(dates, name="d", dtype="M8[ns]") tm.assert_series_equal(result, expected) @@ -1204,7 +1207,9 @@ def test_groupby_transform_with_datetimes(func, values): result = stocks.groupby(stocks["week_id"])["price"].transform(func) - expected = Series(data=pd.to_datetime(values), index=dates, name="price") + expected = Series( + data=pd.to_datetime(values).as_unit("ns"), index=dates, name="price" + ) tm.assert_series_equal(result, expected) diff --git a/pandas/tests/indexes/datetimes/methods/test_astype.py b/pandas/tests/indexes/datetimes/methods/test_astype.py index 9ee6250feeac6..c0bc6601769b1 100644 --- a/pandas/tests/indexes/datetimes/methods/test_astype.py +++ b/pandas/tests/indexes/datetimes/methods/test_astype.py @@ -292,7 +292,7 @@ def test_integer_index_astype_datetime(self, tz, dtype): # GH 20997, 20964, 24559 val = [Timestamp("2018-01-01", tz=tz).as_unit("ns")._value] result = Index(val, name="idx").astype(dtype) - expected = DatetimeIndex(["2018-01-01"], tz=tz, name="idx") + expected = DatetimeIndex(["2018-01-01"], tz=tz, name="idx").as_unit("ns") tm.assert_index_equal(result, expected) def test_dti_astype_period(self): @@ -312,8 +312,9 @@ class TestAstype: def test_astype_category(self, tz): obj = date_range("2000", periods=2, tz=tz, name="idx") result = obj.astype("category") + dti = DatetimeIndex(["2000-01-01", "2000-01-02"], tz=tz).as_unit("ns") expected = pd.CategoricalIndex( - [Timestamp("2000-01-01", tz=tz), Timestamp("2000-01-02", tz=tz)], + dti, name="idx", ) tm.assert_index_equal(result, expected) diff --git a/pandas/tests/indexes/datetimes/test_indexing.py b/pandas/tests/indexes/datetimes/test_indexing.py index 73de33607ca0b..b7932715c3ac7 100644 --- a/pandas/tests/indexes/datetimes/test_indexing.py +++ b/pandas/tests/indexes/datetimes/test_indexing.py @@ -35,46 +35,43 @@ def test_getitem_slice_keeps_name(self): dr = date_range(st, et, freq="h", name="timebucket") assert dr[1:].name == dr.name - def test_getitem(self): - idx1 = date_range("2011-01-01", "2011-01-31", freq="D", name="idx") - idx2 = date_range( - "2011-01-01", "2011-01-31", freq="D", tz="Asia/Tokyo", name="idx" - ) + @pytest.mark.parametrize("tz", [None, "Asia/Tokyo"]) + def test_getitem(self, tz): + idx = date_range("2011-01-01", "2011-01-31", freq="D", tz=tz, name="idx") - for idx in [idx1, idx2]: - result = idx[0] - assert result == Timestamp("2011-01-01", tz=idx.tz) + result = idx[0] + assert result == Timestamp("2011-01-01", tz=idx.tz) - result = idx[0:5] - expected = date_range( - "2011-01-01", "2011-01-05", freq="D", tz=idx.tz, name="idx" - ) - tm.assert_index_equal(result, expected) - assert result.freq == expected.freq + result = idx[0:5] + expected = date_range( + "2011-01-01", "2011-01-05", freq="D", tz=idx.tz, name="idx" + ) + tm.assert_index_equal(result, expected) + assert result.freq == expected.freq - result = idx[0:10:2] - expected = date_range( - "2011-01-01", "2011-01-09", freq="2D", tz=idx.tz, name="idx" - ) - tm.assert_index_equal(result, expected) - assert result.freq == expected.freq + result = idx[0:10:2] + expected = date_range( + "2011-01-01", "2011-01-09", freq="2D", tz=idx.tz, name="idx" + ) + tm.assert_index_equal(result, expected) + assert result.freq == expected.freq - result = idx[-20:-5:3] - expected = date_range( - "2011-01-12", "2011-01-24", freq="3D", tz=idx.tz, name="idx" - ) - tm.assert_index_equal(result, expected) - assert result.freq == expected.freq + result = idx[-20:-5:3] + expected = date_range( + "2011-01-12", "2011-01-24", freq="3D", tz=idx.tz, name="idx" + ) + tm.assert_index_equal(result, expected) + assert result.freq == expected.freq - result = idx[4::-1] - expected = DatetimeIndex( - ["2011-01-05", "2011-01-04", "2011-01-03", "2011-01-02", "2011-01-01"], - freq="-1D", - tz=idx.tz, - name="idx", - ) - tm.assert_index_equal(result, expected) - assert result.freq == expected.freq + result = idx[4::-1] + expected = DatetimeIndex( + ["2011-01-05", "2011-01-04", "2011-01-03", "2011-01-02", "2011-01-01"], + dtype=idx.dtype, + freq="-1D", + name="idx", + ) + tm.assert_index_equal(result, expected) + assert result.freq == expected.freq @pytest.mark.parametrize("freq", ["B", "C"]) def test_dti_business_getitem(self, freq): @@ -264,8 +261,8 @@ def test_take(self): result = idx.take([3, 2, 5]) expected = DatetimeIndex( ["2011-01-04", "2011-01-03", "2011-01-06"], + dtype=idx.dtype, freq=None, - tz=idx.tz, name="idx", ) tm.assert_index_equal(result, expected) @@ -274,6 +271,7 @@ def test_take(self): result = idx.take([-3, 2, 5]) expected = DatetimeIndex( ["2011-01-29", "2011-01-03", "2011-01-06"], + dtype=idx.dtype, freq=None, tz=idx.tz, name="idx", @@ -314,7 +312,7 @@ def test_take2(self, tz): tz=tz, name="idx", ) - expected = DatetimeIndex(dates, freq=None, name="idx", tz=tz) + expected = DatetimeIndex(dates, freq=None, name="idx", dtype=idx.dtype) taken1 = idx.take([5, 6, 8, 12]) taken2 = idx[[5, 6, 8, 12]] diff --git a/pandas/tests/indexes/datetimes/test_setops.py b/pandas/tests/indexes/datetimes/test_setops.py index 78c23e47897cf..993f88db38ea6 100644 --- a/pandas/tests/indexes/datetimes/test_setops.py +++ b/pandas/tests/indexes/datetimes/test_setops.py @@ -249,19 +249,23 @@ def test_intersection(self, tz, sort): # non-monotonic base = DatetimeIndex( ["2011-01-05", "2011-01-04", "2011-01-02", "2011-01-03"], tz=tz, name="idx" - ) + ).as_unit("ns") rng2 = DatetimeIndex( ["2011-01-04", "2011-01-02", "2011-02-02", "2011-02-03"], tz=tz, name="idx" - ) - expected2 = DatetimeIndex(["2011-01-04", "2011-01-02"], tz=tz, name="idx") + ).as_unit("ns") + expected2 = DatetimeIndex( + ["2011-01-04", "2011-01-02"], tz=tz, name="idx" + ).as_unit("ns") rng3 = DatetimeIndex( ["2011-01-04", "2011-01-02", "2011-02-02", "2011-02-03"], tz=tz, name="other", - ) - expected3 = DatetimeIndex(["2011-01-04", "2011-01-02"], tz=tz, name=None) + ).as_unit("ns") + expected3 = DatetimeIndex( + ["2011-01-04", "2011-01-02"], tz=tz, name=None + ).as_unit("ns") # GH 7880 rng4 = date_range("7/1/2000", "7/31/2000", freq="D", tz=tz, name="idx") @@ -350,7 +354,7 @@ def test_difference_freq(self, sort): index = date_range("20160920", "20160925", freq="D") other = date_range("20160921", "20160924", freq="D") - expected = DatetimeIndex(["20160920", "20160925"], freq=None) + expected = DatetimeIndex(["20160920", "20160925"], dtype="M8[ns]", freq=None) idx_diff = index.difference(other, sort) tm.assert_index_equal(idx_diff, expected) tm.assert_attr_equal("freq", idx_diff, expected) @@ -359,7 +363,7 @@ def test_difference_freq(self, sort): # subset of the original range other = date_range("20160922", "20160925", freq="D") idx_diff = index.difference(other, sort) - expected = DatetimeIndex(["20160920", "20160921"], freq="D") + expected = DatetimeIndex(["20160920", "20160921"], dtype="M8[ns]", freq="D") tm.assert_index_equal(idx_diff, expected) tm.assert_attr_equal("freq", idx_diff, expected) diff --git a/pandas/tests/indexes/datetimes/test_timezones.py b/pandas/tests/indexes/datetimes/test_timezones.py index 379c727f4ed0f..daa5b346eb4ec 100644 --- a/pandas/tests/indexes/datetimes/test_timezones.py +++ b/pandas/tests/indexes/datetimes/test_timezones.py @@ -92,7 +92,7 @@ def test_drop_dst_boundary(self): "201710290245", "201710290300", ], - tz=tz, + dtype="M8[ns, Europe/Brussels]", freq=freq, ambiguous=[ True, @@ -112,10 +112,14 @@ def test_drop_dst_boundary(self): result = index.drop(index[0]) tm.assert_index_equal(result, expected) - def test_date_range_localize(self): - rng = date_range("3/11/2012 03:00", periods=15, freq="h", tz="US/Eastern") - rng2 = DatetimeIndex(["3/11/2012 03:00", "3/11/2012 04:00"], tz="US/Eastern") - rng3 = date_range("3/11/2012 03:00", periods=15, freq="h") + def test_date_range_localize(self, unit): + rng = date_range( + "3/11/2012 03:00", periods=15, freq="h", tz="US/Eastern", unit=unit + ) + rng2 = DatetimeIndex( + ["3/11/2012 03:00", "3/11/2012 04:00"], dtype=f"M8[{unit}, US/Eastern]" + ) + rng3 = date_range("3/11/2012 03:00", periods=15, freq="h", unit=unit) rng3 = rng3.tz_localize("US/Eastern") tm.assert_index_equal(rng._with_freq(None), rng3) @@ -129,10 +133,15 @@ def test_date_range_localize(self): assert val == exp # same UTC value tm.assert_index_equal(rng[:2], rng2) + def test_date_range_localize2(self, unit): # Right before the DST transition - rng = date_range("3/11/2012 00:00", periods=2, freq="h", tz="US/Eastern") + rng = date_range( + "3/11/2012 00:00", periods=2, freq="h", tz="US/Eastern", unit=unit + ) rng2 = DatetimeIndex( - ["3/11/2012 00:00", "3/11/2012 01:00"], tz="US/Eastern", freq="h" + ["3/11/2012 00:00", "3/11/2012 01:00"], + dtype=f"M8[{unit}, US/Eastern]", + freq="h", ) tm.assert_index_equal(rng, rng2) exp = Timestamp("3/11/2012 00:00", tz="US/Eastern") @@ -142,7 +151,9 @@ def test_date_range_localize(self): assert exp.hour == 1 assert rng[1] == exp - rng = date_range("3/11/2012 00:00", periods=10, freq="h", tz="US/Eastern") + rng = date_range( + "3/11/2012 00:00", periods=10, freq="h", tz="US/Eastern", unit=unit + ) assert rng[2].hour == 3 def test_timestamp_equality_different_timezones(self): @@ -231,10 +242,10 @@ def test_dti_convert_tz_aware_datetime_datetime(self, tz): dates = [datetime(2000, 1, 1), datetime(2000, 1, 2), datetime(2000, 1, 3)] dates_aware = [conversion.localize_pydatetime(x, tz) for x in dates] - result = DatetimeIndex(dates_aware) + result = DatetimeIndex(dates_aware).as_unit("ns") assert timezones.tz_compare(result.tz, tz) - converted = to_datetime(dates_aware, utc=True) + converted = to_datetime(dates_aware, utc=True).as_unit("ns") ex_vals = np.array([Timestamp(x).as_unit("ns")._value for x in dates_aware]) tm.assert_numpy_array_equal(converted.asi8, ex_vals) assert converted.tz is timezone.utc diff --git a/pandas/tests/indexes/interval/test_indexing.py b/pandas/tests/indexes/interval/test_indexing.py index 2007a793843c9..fd03047b2c127 100644 --- a/pandas/tests/indexes/interval/test_indexing.py +++ b/pandas/tests/indexes/interval/test_indexing.py @@ -363,15 +363,18 @@ def test_get_indexer_categorical_with_nans(self): def test_get_indexer_datetime(self): ii = IntervalIndex.from_breaks(date_range("2018-01-01", periods=4)) - result = ii.get_indexer(DatetimeIndex(["2018-01-02"])) + # TODO: with mismatched resolution get_indexer currently raises; + # this should probably coerce? + target = DatetimeIndex(["2018-01-02"], dtype="M8[ns]") + result = ii.get_indexer(target) expected = np.array([0], dtype=np.intp) tm.assert_numpy_array_equal(result, expected) - result = ii.get_indexer(DatetimeIndex(["2018-01-02"]).astype(str)) + result = ii.get_indexer(target.astype(str)) tm.assert_numpy_array_equal(result, expected) # https://github.com/pandas-dev/pandas/issues/47772 - result = ii.get_indexer(DatetimeIndex(["2018-01-02"]).asi8) + result = ii.get_indexer(target.asi8) expected = np.array([-1], dtype=np.intp) tm.assert_numpy_array_equal(result, expected) diff --git a/pandas/tests/indexes/interval/test_interval_range.py b/pandas/tests/indexes/interval/test_interval_range.py index 37606bda9efca..d4d4a09c44d13 100644 --- a/pandas/tests/indexes/interval/test_interval_range.py +++ b/pandas/tests/indexes/interval/test_interval_range.py @@ -184,6 +184,9 @@ def test_no_invalid_float_truncation(self, start, end, freq): def test_linspace_dst_transition(self, start, mid, end): # GH 20976: linspace behavior defined from start/end/periods # accounts for the hour gained/lost during DST transition + start = start.as_unit("ns") + mid = mid.as_unit("ns") + end = end.as_unit("ns") result = interval_range(start=start, end=end, periods=2) expected = IntervalIndex.from_breaks([start, mid, end]) tm.assert_index_equal(result, expected) diff --git a/pandas/tests/indexes/period/methods/test_to_timestamp.py b/pandas/tests/indexes/period/methods/test_to_timestamp.py index 7be2602135578..3867f9e3245dc 100644 --- a/pandas/tests/indexes/period/methods/test_to_timestamp.py +++ b/pandas/tests/indexes/period/methods/test_to_timestamp.py @@ -58,7 +58,9 @@ def test_to_timestamp_pi_nat(self): result = index.to_timestamp("D") expected = DatetimeIndex( - [NaT, datetime(2011, 1, 1), datetime(2011, 2, 1)], name="idx" + [NaT, datetime(2011, 1, 1), datetime(2011, 2, 1)], + dtype="M8[ns]", + name="idx", ) tm.assert_index_equal(result, expected) assert result.name == "idx" @@ -98,11 +100,15 @@ def test_to_timestamp_pi_mult(self): idx = PeriodIndex(["2011-01", "NaT", "2011-02"], freq="2M", name="idx") result = idx.to_timestamp() - expected = DatetimeIndex(["2011-01-01", "NaT", "2011-02-01"], name="idx") + expected = DatetimeIndex( + ["2011-01-01", "NaT", "2011-02-01"], dtype="M8[ns]", name="idx" + ) tm.assert_index_equal(result, expected) result = idx.to_timestamp(how="E") - expected = DatetimeIndex(["2011-02-28", "NaT", "2011-03-31"], name="idx") + expected = DatetimeIndex( + ["2011-02-28", "NaT", "2011-03-31"], dtype="M8[ns]", name="idx" + ) expected = expected + Timedelta(1, "D") - Timedelta(1, "ns") tm.assert_index_equal(result, expected) @@ -110,18 +116,22 @@ def test_to_timestamp_pi_combined(self): idx = period_range(start="2011", periods=2, freq="1D1h", name="idx") result = idx.to_timestamp() - expected = DatetimeIndex(["2011-01-01 00:00", "2011-01-02 01:00"], name="idx") + expected = DatetimeIndex( + ["2011-01-01 00:00", "2011-01-02 01:00"], dtype="M8[ns]", name="idx" + ) tm.assert_index_equal(result, expected) result = idx.to_timestamp(how="E") expected = DatetimeIndex( - ["2011-01-02 00:59:59", "2011-01-03 01:59:59"], name="idx" + ["2011-01-02 00:59:59", "2011-01-03 01:59:59"], name="idx", dtype="M8[ns]" ) expected = expected + Timedelta(1, "s") - Timedelta(1, "ns") tm.assert_index_equal(result, expected) result = idx.to_timestamp(how="E", freq="h") - expected = DatetimeIndex(["2011-01-02 00:00", "2011-01-03 01:00"], name="idx") + expected = DatetimeIndex( + ["2011-01-02 00:00", "2011-01-03 01:00"], dtype="M8[ns]", name="idx" + ) expected = expected + Timedelta(1, "h") - Timedelta(1, "ns") tm.assert_index_equal(result, expected) diff --git a/pandas/tests/indexing/multiindex/test_partial.py b/pandas/tests/indexing/multiindex/test_partial.py index 9cf11b4602cb2..7be3d8c657766 100644 --- a/pandas/tests/indexing/multiindex/test_partial.py +++ b/pandas/tests/indexing/multiindex/test_partial.py @@ -5,9 +5,9 @@ from pandas import ( DataFrame, + DatetimeIndex, MultiIndex, date_range, - to_datetime, ) import pandas._testing as tm @@ -219,7 +219,11 @@ def test_setitem_multiple_partial(self, multiindex_dataframe_random_data): @pytest.mark.parametrize( "indexer, exp_idx, exp_values", [ - (slice("2019-2", None), [to_datetime("2019-02-01")], [2, 3]), + ( + slice("2019-2", None), + DatetimeIndex(["2019-02-01"], dtype="M8[ns]"), + [2, 3], + ), ( slice(None, "2019-2"), date_range("2019", periods=2, freq="MS"), diff --git a/pandas/tests/indexing/multiindex/test_setitem.py b/pandas/tests/indexing/multiindex/test_setitem.py index e31b675efb69a..9870868a3e1e9 100644 --- a/pandas/tests/indexing/multiindex/test_setitem.py +++ b/pandas/tests/indexing/multiindex/test_setitem.py @@ -9,7 +9,6 @@ DataFrame, MultiIndex, Series, - Timestamp, date_range, isna, notna, @@ -91,11 +90,11 @@ def test_setitem_multiindex3(self): np.random.default_rng(2).random((12, 4)), index=idx, columns=cols ) - subidx = MultiIndex.from_tuples( - [("A", Timestamp("2015-01-01")), ("A", Timestamp("2015-02-01"))] + subidx = MultiIndex.from_arrays( + [["A", "A"], date_range("2015-01-01", "2015-02-01", freq="MS")] ) - subcols = MultiIndex.from_tuples( - [("foo", Timestamp("2016-01-01")), ("foo", Timestamp("2016-02-01"))] + subcols = MultiIndex.from_arrays( + [["foo", "foo"], date_range("2016-01-01", "2016-02-01", freq="MS")] ) vals = DataFrame( diff --git a/pandas/tests/io/xml/test_xml_dtypes.py b/pandas/tests/io/xml/test_xml_dtypes.py index fb24902efc0f5..a85576ff13f5c 100644 --- a/pandas/tests/io/xml/test_xml_dtypes.py +++ b/pandas/tests/io/xml/test_xml_dtypes.py @@ -9,6 +9,7 @@ from pandas import ( DataFrame, + DatetimeIndex, Series, to_datetime, ) @@ -146,7 +147,9 @@ def test_dtypes_with_names(parser): "Col1": ["square", "circle", "triangle"], "Col2": Series(["00360", "00360", "00180"]).astype("string"), "Col3": Series([4.0, float("nan"), 3.0]).astype("Int64"), - "Col4": to_datetime(["2020-01-01", "2021-01-01", "2022-01-01"]), + "Col4": DatetimeIndex( + ["2020-01-01", "2021-01-01", "2022-01-01"], dtype="M8[ns]" + ), } ) diff --git a/pandas/tests/resample/test_resampler_grouper.py b/pandas/tests/resample/test_resampler_grouper.py index 4afd3b477c3ee..3cf5201d573d4 100644 --- a/pandas/tests/resample/test_resampler_grouper.py +++ b/pandas/tests/resample/test_resampler_grouper.py @@ -118,12 +118,11 @@ def test_getitem_multiple(): df = DataFrame(data, index=date_range("2016-01-01", periods=2)) r = df.groupby("id").resample("1D") result = r["buyer"].count() + + exp_mi = pd.MultiIndex.from_arrays([[1, 2], df.index], names=("id", None)) expected = Series( [1, 1], - index=pd.MultiIndex.from_tuples( - [(1, Timestamp("2016-01-01")), (2, Timestamp("2016-01-02"))], - names=["id", None], - ), + index=exp_mi, name="buyer", ) tm.assert_series_equal(result, expected) diff --git a/pandas/tests/reshape/merge/test_join.py b/pandas/tests/reshape/merge/test_join.py index a38e4ffe2eaf7..c4c83e2046b76 100644 --- a/pandas/tests/reshape/merge/test_join.py +++ b/pandas/tests/reshape/merge/test_join.py @@ -787,7 +787,7 @@ def test_join_datetime_string(self): index=[2, 4], columns=["x", "y", "z", "a"], ) - expected["x"] = expected["x"].dt.as_unit("ns") + expected["x"] = expected["x"].astype("M8[ns]") tm.assert_frame_equal(result, expected) def test_join_with_categorical_index(self): diff --git a/pandas/tests/series/methods/test_quantile.py b/pandas/tests/series/methods/test_quantile.py index 016635a50fdf4..fa0563271d7df 100644 --- a/pandas/tests/series/methods/test_quantile.py +++ b/pandas/tests/series/methods/test_quantile.py @@ -105,8 +105,8 @@ def test_quantile_interpolation_dtype(self): def test_quantile_nan(self): # GH 13098 - s = Series([1, 2, 3, 4, np.nan]) - result = s.quantile(0.5) + ser = Series([1, 2, 3, 4, np.nan]) + result = ser.quantile(0.5) expected = 2.5 assert result == expected @@ -114,14 +114,14 @@ def test_quantile_nan(self): s1 = Series([], dtype=object) cases = [s1, Series([np.nan, np.nan])] - for s in cases: - res = s.quantile(0.5) + for ser in cases: + res = ser.quantile(0.5) assert np.isnan(res) - res = s.quantile([0.5]) + res = ser.quantile([0.5]) tm.assert_series_equal(res, Series([np.nan], index=[0.5])) - res = s.quantile([0.2, 0.3]) + res = ser.quantile([0.2, 0.3]) tm.assert_series_equal(res, Series([np.nan, np.nan], index=[0.2, 0.3])) @pytest.mark.parametrize( @@ -160,11 +160,11 @@ def test_quantile_nan(self): ], ) def test_quantile_box(self, case): - s = Series(case, name="XXX") - res = s.quantile(0.5) + ser = Series(case, name="XXX") + res = ser.quantile(0.5) assert res == case[1] - res = s.quantile([0.5]) + res = ser.quantile([0.5]) exp = Series([case[1]], index=[0.5], name="XXX") tm.assert_series_equal(res, exp) @@ -190,35 +190,37 @@ def test_quantile_sparse(self, values, dtype): expected = Series(np.asarray(ser)).quantile([0.5]).astype("Sparse[float]") tm.assert_series_equal(result, expected) - def test_quantile_empty(self): + def test_quantile_empty_float64(self): # floats - s = Series([], dtype="float64") + ser = Series([], dtype="float64") - res = s.quantile(0.5) + res = ser.quantile(0.5) assert np.isnan(res) - res = s.quantile([0.5]) + res = ser.quantile([0.5]) exp = Series([np.nan], index=[0.5]) tm.assert_series_equal(res, exp) + def test_quantile_empty_int64(self): # int - s = Series([], dtype="int64") + ser = Series([], dtype="int64") - res = s.quantile(0.5) + res = ser.quantile(0.5) assert np.isnan(res) - res = s.quantile([0.5]) + res = ser.quantile([0.5]) exp = Series([np.nan], index=[0.5]) tm.assert_series_equal(res, exp) + def test_quantile_empty_dt64(self): # datetime - s = Series([], dtype="datetime64[ns]") + ser = Series([], dtype="datetime64[ns]") - res = s.quantile(0.5) + res = ser.quantile(0.5) assert res is pd.NaT - res = s.quantile([0.5]) - exp = Series([pd.NaT], index=[0.5]) + res = ser.quantile([0.5]) + exp = Series([pd.NaT], index=[0.5], dtype=ser.dtype) tm.assert_series_equal(res, exp) @pytest.mark.parametrize("dtype", [int, float, "Int64"]) From 9ac15629534f9ce4cf85e1340a9ddf99635ffe58 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 25 Nov 2023 20:13:39 -0800 Subject: [PATCH 03/30] DEPR: dtype inference in value_counts (#56161) * DEPR: dtype inference in value_counts * GH ref --- doc/source/whatsnew/v2.2.0.rst | 1 + pandas/core/algorithms.py | 22 ++++++++++++++++++++-- pandas/core/arrays/interval.py | 12 +++++++++++- pandas/tests/base/test_value_counts.py | 13 +++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 8893fe0ecd398..9318e1d9ffaff 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -396,6 +396,7 @@ Other Deprecations - Deprecated the ``errors="ignore"`` option in :func:`to_datetime`, :func:`to_timedelta`, and :func:`to_numeric`; explicitly catch exceptions instead (:issue:`54467`) - Deprecated the ``fastpath`` keyword in the :class:`Series` constructor (:issue:`20110`) - Deprecated the ``ordinal`` keyword in :class:`PeriodIndex`, use :meth:`PeriodIndex.from_ordinals` instead (:issue:`55960`) +- Deprecated the behavior of :meth:`Series.value_counts` and :meth:`Index.value_counts` with object dtype; in a future version these will not perform dtype inference on the resulting :class:`Index`, do ``result.index = result.index.infer_objects()`` to retain the old behavior (:issue:`56161`) - Deprecated the extension test classes ``BaseNoReduceTests``, ``BaseBooleanReduceTests``, and ``BaseNumericReduceTests``, use ``BaseReduceTests`` instead (:issue:`54663`) - Deprecated the option ``mode.data_manager`` and the ``ArrayManager``; only the ``BlockManager`` will be available in future versions (:issue:`55043`) - Deprecated the previous implementation of :class:`DataFrame.stack`; specify ``future_stack=True`` to adopt the future version (:issue:`53515`) diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py index b38a27a9f6d0a..82de8ae96160f 100644 --- a/pandas/core/algorithms.py +++ b/pandas/core/algorithms.py @@ -932,6 +932,16 @@ def value_counts_internal( idx = Index(keys) if idx.dtype == bool and keys.dtype == object: idx = idx.astype(object) + elif idx.dtype != keys.dtype: + warnings.warn( + # GH#56161 + "The behavior of value_counts with object-dtype is deprecated. " + "In a future version, this will *not* perform dtype inference " + "on the resulting index. To retain the old behavior, use " + "`result.index = result.index.infer_objects()`", + FutureWarning, + stacklevel=find_stack_level(), + ) idx.name = index_name result = Series(counts, index=idx, name=name, copy=False) @@ -1712,8 +1722,16 @@ def union_with_duplicates( """ from pandas import Series - l_count = value_counts_internal(lvals, dropna=False) - r_count = value_counts_internal(rvals, dropna=False) + with warnings.catch_warnings(): + # filter warning from object dtype inference; we will end up discarding + # the index here, so the deprecation does not affect the end result here. + warnings.filterwarnings( + "ignore", + "The behavior of value_counts with object-dtype is deprecated", + category=FutureWarning, + ) + l_count = value_counts_internal(lvals, dropna=False) + r_count = value_counts_internal(rvals, dropna=False) l_count, r_count = l_count.align(r_count, fill_value=0) final_count = np.maximum(l_count.values, r_count.values) final_count = Series(final_count, index=l_count.index, dtype="int", copy=False) diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py index 2f1363f180d08..126484ed4a2a0 100644 --- a/pandas/core/arrays/interval.py +++ b/pandas/core/arrays/interval.py @@ -12,6 +12,7 @@ Union, overload, ) +import warnings import numpy as np @@ -1226,7 +1227,16 @@ def value_counts(self, dropna: bool = True) -> Series: Series.value_counts """ # TODO: implement this is a non-naive way! - return value_counts(np.asarray(self), dropna=dropna) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "The behavior of value_counts with object-dtype is deprecated", + category=FutureWarning, + ) + result = value_counts(np.asarray(self), dropna=dropna) + # Once the deprecation is enforced, we will need to do + # `result.index = result.index.astype(self.dtype)` + return result # --------------------------------------------------------------------- # Rendering Methods diff --git a/pandas/tests/base/test_value_counts.py b/pandas/tests/base/test_value_counts.py index c42d064c476bb..75915b7c67548 100644 --- a/pandas/tests/base/test_value_counts.py +++ b/pandas/tests/base/test_value_counts.py @@ -336,3 +336,16 @@ def test_value_counts_with_nan(dropna, index_or_series): else: expected = Series([1, 1, 1], index=[True, pd.NA, np.nan], name="count") tm.assert_series_equal(res, expected) + + +def test_value_counts_object_inference_deprecated(): + # GH#56161 + dti = pd.date_range("2016-01-01", periods=3, tz="UTC") + + idx = dti.astype(object) + msg = "The behavior of value_counts with object-dtype is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + res = idx.value_counts() + + exp = dti.value_counts() + tm.assert_series_equal(res, exp) From f2c8715245f3b1a5b55f144116f24221535414c6 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 25 Nov 2023 20:23:37 -0800 Subject: [PATCH 04/30] BUG: round with non-nanosecond raising OverflowError (#56158) * BUG: round with non-nanosecond raising OverflowError * GH ref * woops --- doc/source/whatsnew/v2.2.0.rst | 1 + pandas/_libs/tslibs/fields.pyx | 26 ++++++++++++++++++++++- pandas/tests/series/methods/test_round.py | 11 +++++----- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 9318e1d9ffaff..5df932304e757 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -453,6 +453,7 @@ Datetimelike - Bug in :meth:`DatetimeIndex.union` returning object dtype for tz-aware indexes with the same timezone but different units (:issue:`55238`) - Bug in :meth:`Index.is_monotonic_increasing` and :meth:`Index.is_monotonic_decreasing` always caching :meth:`Index.is_unique` as ``True`` when first value in index is ``NaT`` (:issue:`55755`) - Bug in :meth:`Index.view` to a datetime64 dtype with non-supported resolution incorrectly raising (:issue:`55710`) +- Bug in :meth:`Series.dt.round` with non-nanosecond resolution and ``NaT`` entries incorrectly raising ``OverflowError`` (:issue:`56158`) - Bug in :meth:`Tick.delta` with very large ticks raising ``OverflowError`` instead of ``OutOfBoundsTimedelta`` (:issue:`55503`) - Bug in ``.astype`` converting from a higher-resolution ``datetime64`` dtype to a lower-resolution ``datetime64`` dtype (e.g. ``datetime64[us]->datetim64[ms]``) silently overflowing with values near the lower implementation bound (:issue:`55979`) - Bug in adding or subtracting a :class:`Week` offset to a ``datetime64`` :class:`Series`, :class:`Index`, or :class:`DataFrame` column with non-nanosecond resolution returning incorrect results (:issue:`55583`) diff --git a/pandas/_libs/tslibs/fields.pyx b/pandas/_libs/tslibs/fields.pyx index a726c735bf9a1..ff4fb4d635d17 100644 --- a/pandas/_libs/tslibs/fields.pyx +++ b/pandas/_libs/tslibs/fields.pyx @@ -746,7 +746,31 @@ cdef ndarray[int64_t] _ceil_int64(const int64_t[:] values, int64_t unit): cdef ndarray[int64_t] _rounddown_int64(values, int64_t unit): - return _ceil_int64(values - unit // 2, unit) + cdef: + Py_ssize_t i, n = len(values) + ndarray[int64_t] result = np.empty(n, dtype="i8") + int64_t res, value, remainder, half + + half = unit // 2 + + with cython.overflowcheck(True): + for i in range(n): + value = values[i] + + if value == NPY_NAT: + res = NPY_NAT + else: + # This adjustment is the only difference between rounddown_int64 + # and _ceil_int64 + value = value - half + remainder = value % unit + if remainder == 0: + res = value + else: + res = value + (unit - remainder) + + result[i] = res + return result cdef ndarray[int64_t] _roundup_int64(values, int64_t unit): diff --git a/pandas/tests/series/methods/test_round.py b/pandas/tests/series/methods/test_round.py index 6c40e36419551..7f60c94f10e4f 100644 --- a/pandas/tests/series/methods/test_round.py +++ b/pandas/tests/series/methods/test_round.py @@ -56,9 +56,10 @@ def test_round_builtin(self, any_float_dtype): @pytest.mark.parametrize("method", ["round", "floor", "ceil"]) @pytest.mark.parametrize("freq", ["s", "5s", "min", "5min", "h", "5h"]) - def test_round_nat(self, method, freq): - # GH14940 - ser = Series([pd.NaT]) - expected = Series(pd.NaT) + def test_round_nat(self, method, freq, unit): + # GH14940, GH#56158 + ser = Series([pd.NaT], dtype=f"M8[{unit}]") + expected = Series(pd.NaT, dtype=f"M8[{unit}]") round_method = getattr(ser.dt, method) - tm.assert_series_equal(round_method(freq), expected) + result = round_method(freq) + tm.assert_series_equal(result, expected) From c07563e557511fa9ecbf139926fa45d0f60fbb6e Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 05:30:18 +0100 Subject: [PATCH 05/30] Adjust tests in resample folder for new string option (#56150) --- pandas/tests/resample/test_base.py | 3 ++- pandas/tests/resample/test_resample_api.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pandas/tests/resample/test_base.py b/pandas/tests/resample/test_base.py index fee52780585b8..6ecbdff0018fa 100644 --- a/pandas/tests/resample/test_base.py +++ b/pandas/tests/resample/test_base.py @@ -5,6 +5,7 @@ from pandas import ( DataFrame, + Index, MultiIndex, NaT, PeriodIndex, @@ -255,7 +256,7 @@ def test_resample_count_empty_dataframe(freq, empty_frame_dti): index = _asfreq_compat(empty_frame_dti.index, freq) - expected = DataFrame({"a": []}, dtype="int64", index=index) + expected = DataFrame(dtype="int64", index=index, columns=Index(["a"], dtype=object)) tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/resample/test_resample_api.py b/pandas/tests/resample/test_resample_api.py index 81a054bbbc3df..7e8779ab48b7e 100644 --- a/pandas/tests/resample/test_resample_api.py +++ b/pandas/tests/resample/test_resample_api.py @@ -197,7 +197,7 @@ def tests_raises_on_nuisance(test_frame): tm.assert_frame_equal(result, expected) expected = r[["A", "B", "C"]].mean() - msg = re.escape("agg function failed [how->mean,dtype->object]") + msg = re.escape("agg function failed [how->mean,dtype->") with pytest.raises(TypeError, match=msg): r.mean() result = r.mean(numeric_only=True) @@ -948,7 +948,7 @@ def test_frame_downsample_method(method, numeric_only, expected_data): if isinstance(expected_data, str): if method in ("var", "mean", "median", "prod"): klass = TypeError - msg = re.escape(f"agg function failed [how->{method},dtype->object]") + msg = re.escape(f"agg function failed [how->{method},dtype->") else: klass = ValueError msg = expected_data @@ -998,7 +998,7 @@ def test_series_downsample_method(method, numeric_only, expected_data): with pytest.raises(TypeError, match=msg): func(**kwargs) elif method == "prod": - msg = re.escape("agg function failed [how->prod,dtype->object]") + msg = re.escape("agg function failed [how->prod,dtype->") with pytest.raises(TypeError, match=msg): func(**kwargs) else: From bd27a3e022c2b32087902b49ae727d5d738e8f1b Mon Sep 17 00:00:00 2001 From: Richard Shadrach <45562402+rhshadrach@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:35:43 -0500 Subject: [PATCH 06/30] DEPR: Some Grouper and Grouping attributes (#56149) * DEPR: Some Grouper and Grouping attributes * GH# * GH# * Rework _group_index --- doc/source/whatsnew/v2.2.0.rst | 2 + pandas/core/groupby/generic.py | 4 +- pandas/core/groupby/groupby.py | 6 +-- pandas/core/groupby/grouper.py | 53 +++++++++++++++++++----- pandas/core/groupby/ops.py | 50 +++++++++++++++------- pandas/tests/groupby/test_groupby.py | 10 +++++ pandas/tests/groupby/test_grouping.py | 10 +++++ pandas/tests/groupby/test_timegrouper.py | 4 +- 8 files changed, 109 insertions(+), 30 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 5df932304e757..fbff38aeefc51 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -393,6 +393,8 @@ Other Deprecations - Deprecated strings ``H``, ``S``, ``U``, and ``N`` denoting units in :func:`to_timedelta` (:issue:`52536`) - Deprecated strings ``H``, ``T``, ``S``, ``L``, ``U``, and ``N`` denoting units in :class:`Timedelta` (:issue:`52536`) - Deprecated strings ``T``, ``S``, ``L``, ``U``, and ``N`` denoting frequencies in :class:`Minute`, :class:`Second`, :class:`Milli`, :class:`Micro`, :class:`Nano` (:issue:`52536`) +- Deprecated the :class:`.BaseGrouper` attributes ``group_keys_seq`` and ``reconstructed_codes``; these will be removed in a future version of pandas (:issue:`56148`) +- Deprecated the :class:`.Grouping` attributes ``group_index``, ``result_index``, and ``group_arraylike``; these will be removed in a future version of pandas (:issue:`56148`) - Deprecated the ``errors="ignore"`` option in :func:`to_datetime`, :func:`to_timedelta`, and :func:`to_numeric`; explicitly catch exceptions instead (:issue:`54467`) - Deprecated the ``fastpath`` keyword in the :class:`Series` constructor (:issue:`20110`) - Deprecated the ``ordinal`` keyword in :class:`PeriodIndex`, use :meth:`PeriodIndex.from_ordinals` instead (:issue:`55960`) diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py index 1fb412e17c4ba..55ab4c2113fd7 100644 --- a/pandas/core/groupby/generic.py +++ b/pandas/core/groupby/generic.py @@ -819,9 +819,9 @@ def value_counts( rep = partial(np.repeat, repeats=np.add.reduceat(inc, idx)) # multi-index components - codes = self.grouper.reconstructed_codes + codes = self.grouper._reconstructed_codes codes = [rep(level_codes) for level_codes in codes] + [llab(lab, inc)] - levels = [ping.group_index for ping in self.grouper.groupings] + [lev] + levels = [ping._group_index for ping in self.grouper.groupings] + [lev] if dropna: mask = codes[-1] != -1 diff --git a/pandas/core/groupby/groupby.py b/pandas/core/groupby/groupby.py index 2d530b64e104b..7d284db4eba2c 100644 --- a/pandas/core/groupby/groupby.py +++ b/pandas/core/groupby/groupby.py @@ -2820,7 +2820,7 @@ def _value_counts( and not grouping._observed for grouping in groupings ): - levels_list = [ping.result_index for ping in groupings] + levels_list = [ping._result_index for ping in groupings] multi_index = MultiIndex.from_product( levels_list, names=[ping.name for ping in groupings] ) @@ -5573,7 +5573,7 @@ def _reindex_output( ): return output - levels_list = [ping.group_index for ping in groupings] + levels_list = [ping._group_index for ping in groupings] names = self.grouper.names if qs is not None: # error: Argument 1 to "append" of "list" has incompatible type @@ -5795,7 +5795,7 @@ def _idxmax_idxmin( ping._passed_categorical for ping in self.grouper.groupings ): expected_len = np.prod( - [len(ping.group_index) for ping in self.grouper.groupings] + [len(ping._group_index) for ping in self.grouper.groupings] ) if len(self.grouper.groupings) == 1: result_len = len(self.grouper.groupings[0].grouping_vector.unique()) diff --git a/pandas/core/groupby/grouper.py b/pandas/core/groupby/grouper.py index fd0479e17d2bd..fc914831b7a72 100644 --- a/pandas/core/groupby/grouper.py +++ b/pandas/core/groupby/grouper.py @@ -523,7 +523,6 @@ class Grouping: """ _codes: npt.NDArray[np.signedinteger] | None = None - _group_index: Index | None = None _all_grouper: Categorical | None _orig_cats: Index | None _index: Index @@ -679,7 +678,7 @@ def _ilevel(self) -> int | None: @property def ngroups(self) -> int: - return len(self.group_index) + return len(self._group_index) @cache_readonly def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]: @@ -695,34 +694,58 @@ def codes(self) -> npt.NDArray[np.signedinteger]: return self._codes_and_uniques[0] @cache_readonly - def group_arraylike(self) -> ArrayLike: + def _group_arraylike(self) -> ArrayLike: """ Analogous to result_index, but holding an ArrayLike to ensure we can retain ExtensionDtypes. """ if self._all_grouper is not None: # retain dtype for categories, including unobserved ones - return self.result_index._values + return self._result_index._values elif self._passed_categorical: - return self.group_index._values + return self._group_index._values return self._codes_and_uniques[1] + @property + def group_arraylike(self) -> ArrayLike: + """ + Analogous to result_index, but holding an ArrayLike to ensure + we can retain ExtensionDtypes. + """ + warnings.warn( + "group_arraylike is deprecated and will be removed in a future " + "version of pandas", + category=FutureWarning, + stacklevel=find_stack_level(), + ) + return self._group_arraylike + @cache_readonly - def result_index(self) -> Index: + def _result_index(self) -> Index: # result_index retains dtype for categories, including unobserved ones, # which group_index does not if self._all_grouper is not None: - group_idx = self.group_index + group_idx = self._group_index assert isinstance(group_idx, CategoricalIndex) cats = self._orig_cats # set_categories is dynamically added return group_idx.set_categories(cats) # type: ignore[attr-defined] - return self.group_index + return self._group_index + + @property + def result_index(self) -> Index: + warnings.warn( + "result_index is deprecated and will be removed in a future " + "version of pandas", + category=FutureWarning, + stacklevel=find_stack_level(), + ) + return self._result_index @cache_readonly - def group_index(self) -> Index: + def _group_index(self) -> Index: codes, uniques = self._codes_and_uniques if not self._dropna and self._passed_categorical: assert isinstance(uniques, Categorical) @@ -744,6 +767,16 @@ def group_index(self) -> Index: ) return Index._with_infer(uniques, name=self.name) + @property + def group_index(self) -> Index: + warnings.warn( + "group_index is deprecated and will be removed in a future " + "version of pandas", + category=FutureWarning, + stacklevel=find_stack_level(), + ) + return self._group_index + @cache_readonly def _codes_and_uniques(self) -> tuple[npt.NDArray[np.signedinteger], ArrayLike]: uniques: ArrayLike @@ -809,7 +842,7 @@ def _codes_and_uniques(self) -> tuple[npt.NDArray[np.signedinteger], ArrayLike]: @cache_readonly def groups(self) -> dict[Hashable, np.ndarray]: - cats = Categorical.from_codes(self.codes, self.group_index, validate=False) + cats = Categorical.from_codes(self.codes, self._group_index, validate=False) return self._index.groupby(cats) diff --git a/pandas/core/groupby/ops.py b/pandas/core/groupby/ops.py index 466bbac641077..f3579e6c13a19 100644 --- a/pandas/core/groupby/ops.py +++ b/pandas/core/groupby/ops.py @@ -15,6 +15,7 @@ Generic, final, ) +import warnings import numpy as np @@ -32,6 +33,7 @@ ) from pandas.errors import AbstractMethodError from pandas.util._decorators import cache_readonly +from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.base import ExtensionDtype from pandas.core.dtypes.cast import ( @@ -616,7 +618,7 @@ def get_iterator( for each group """ splitter = self._get_splitter(data, axis=axis) - keys = self.group_keys_seq + keys = self._group_keys_seq yield from zip(keys, splitter) @final @@ -638,7 +640,7 @@ def _get_splitter(self, data: NDFrame, axis: AxisInt = 0) -> DataSplitter: @final @cache_readonly - def group_keys_seq(self): + def _group_keys_seq(self): if len(self.groupings) == 1: return self.levels[0] else: @@ -647,6 +649,16 @@ def group_keys_seq(self): # provide "flattened" iterator for multi-group setting return get_flattened_list(ids, ngroups, self.levels, self.codes) + @property + def group_keys_seq(self): + warnings.warn( + "group_keys_seq is deprecated and will be removed in a future " + "version of pandas", + category=FutureWarning, + stacklevel=find_stack_level(), + ) + return self._group_keys_seq + @cache_readonly def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]: """dict {group name -> group indices}""" @@ -654,7 +666,7 @@ def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]: # This shows unused categories in indices GH#38642 return self.groupings[0].indices codes_list = [ping.codes for ping in self.groupings] - keys = [ping.group_index for ping in self.groupings] + keys = [ping._group_index for ping in self.groupings] return get_indexer_dict(codes_list, keys) @final @@ -691,7 +703,7 @@ def codes(self) -> list[npt.NDArray[np.signedinteger]]: @property def levels(self) -> list[Index]: - return [ping.group_index for ping in self.groupings] + return [ping._group_index for ping in self.groupings] @property def names(self) -> list[Hashable]: @@ -766,7 +778,7 @@ def _get_compressed_codes( # FIXME: compress_group_index's second return value is int64, not intp ping = self.groupings[0] - return ping.codes, np.arange(len(ping.group_index), dtype=np.intp) + return ping.codes, np.arange(len(ping._group_index), dtype=np.intp) @final @cache_readonly @@ -774,18 +786,28 @@ def ngroups(self) -> int: return len(self.result_index) @property - def reconstructed_codes(self) -> list[npt.NDArray[np.intp]]: + def _reconstructed_codes(self) -> list[npt.NDArray[np.intp]]: codes = self.codes ids, obs_ids, _ = self.group_info return decons_obs_group_ids(ids, obs_ids, self.shape, codes, xnull=True) + @property + def reconstructed_codes(self) -> list[npt.NDArray[np.intp]]: + warnings.warn( + "reconstructed_codes is deprecated and will be removed in a future " + "version of pandas", + category=FutureWarning, + stacklevel=find_stack_level(), + ) + return self._reconstructed_codes + @cache_readonly def result_index(self) -> Index: if len(self.groupings) == 1: - return self.groupings[0].result_index.rename(self.names[0]) + return self.groupings[0]._result_index.rename(self.names[0]) - codes = self.reconstructed_codes - levels = [ping.result_index for ping in self.groupings] + codes = self._reconstructed_codes + levels = [ping._result_index for ping in self.groupings] return MultiIndex( levels=levels, codes=codes, verify_integrity=False, names=self.names ) @@ -795,12 +817,12 @@ def get_group_levels(self) -> list[ArrayLike]: # Note: only called from _insert_inaxis_grouper, which # is only called for BaseGrouper, never for BinGrouper if len(self.groupings) == 1: - return [self.groupings[0].group_arraylike] + return [self.groupings[0]._group_arraylike] name_list = [] - for ping, codes in zip(self.groupings, self.reconstructed_codes): + for ping, codes in zip(self.groupings, self._reconstructed_codes): codes = ensure_platform_int(codes) - levels = ping.group_arraylike.take(codes) + levels = ping._group_arraylike.take(codes) name_list.append(levels) @@ -907,7 +929,7 @@ def apply_groupwise( ) -> tuple[list, bool]: mutated = False splitter = self._get_splitter(data, axis=axis) - group_keys = self.group_keys_seq + group_keys = self._group_keys_seq result_values = [] # This calls DataSplitter.__iter__ @@ -1087,7 +1109,7 @@ def group_info(self) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp], int]: ) @cache_readonly - def reconstructed_codes(self) -> list[np.ndarray]: + def _reconstructed_codes(self) -> list[np.ndarray]: # get unique result indices, and prepend 0 as groupby starts from the first return [np.r_[0, np.flatnonzero(self.bins[1:] != self.bins[:-1]) + 1]] diff --git a/pandas/tests/groupby/test_groupby.py b/pandas/tests/groupby/test_groupby.py index c61d9fab0435e..5b17484de9c93 100644 --- a/pandas/tests/groupby/test_groupby.py +++ b/pandas/tests/groupby/test_groupby.py @@ -3303,3 +3303,13 @@ def test_groupby_ffill_with_duplicated_index(): result = df.groupby(level=0).ffill() expected = DataFrame({"a": [1, 2, 3, 4, 2, 3]}, index=[0, 1, 2, 0, 1, 2]) tm.assert_frame_equal(result, expected, check_dtype=False) + + +@pytest.mark.parametrize("attr", ["group_keys_seq", "reconstructed_codes"]) +def test_depr_grouper_attrs(attr): + # GH#56148 + df = DataFrame({"a": [1, 1, 2], "b": [3, 4, 5]}) + gb = df.groupby("a") + msg = f"{attr} is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + getattr(gb.grouper, attr) diff --git a/pandas/tests/groupby/test_grouping.py b/pandas/tests/groupby/test_grouping.py index e3cc41afa4679..3c1a35c984031 100644 --- a/pandas/tests/groupby/test_grouping.py +++ b/pandas/tests/groupby/test_grouping.py @@ -1211,3 +1211,13 @@ def test_grouper_groups(): msg = "Grouper.indexer is deprecated" with tm.assert_produces_warning(FutureWarning, match=msg): grper.indexer + + +@pytest.mark.parametrize("attr", ["group_index", "result_index", "group_arraylike"]) +def test_depr_grouping_attrs(attr): + # GH#56148 + df = DataFrame({"a": [1, 1, 2], "b": [3, 4, 5]}) + gb = df.groupby("a") + msg = f"{attr} is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + getattr(gb.grouper.groupings[0], attr) diff --git a/pandas/tests/groupby/test_timegrouper.py b/pandas/tests/groupby/test_timegrouper.py index d1faab9cabfba..b79bed32e7fa4 100644 --- a/pandas/tests/groupby/test_timegrouper.py +++ b/pandas/tests/groupby/test_timegrouper.py @@ -67,7 +67,9 @@ def groupby_with_truncated_bingrouper(frame_for_truncated_bingrouper): gb = df.groupby(tdg) # check we're testing the case we're interested in - assert len(gb.grouper.result_index) != len(gb.grouper.group_keys_seq) + msg = "group_keys_seq is deprecated" + with tm.assert_produces_warning(FutureWarning, match=msg): + assert len(gb.grouper.result_index) != len(gb.grouper.group_keys_seq) return gb From 472516c937e3daeb1b0b68bff4ca822b183390e5 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 05:38:07 +0100 Subject: [PATCH 07/30] Adjust reduction folder tests for string option (#56143) --- pandas/tests/reductions/test_reductions.py | 40 +++++++++++++--------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/pandas/tests/reductions/test_reductions.py b/pandas/tests/reductions/test_reductions.py index 17227e7cfabc7..5a6d1af6257bb 100644 --- a/pandas/tests/reductions/test_reductions.py +++ b/pandas/tests/reductions/test_reductions.py @@ -28,6 +28,7 @@ ) import pandas._testing as tm from pandas.core import nanops +from pandas.core.arrays.string_arrow import ArrowStringArrayNumpySemantics def get_objs(): @@ -57,7 +58,11 @@ class TestReductions: def test_ops(self, opname, obj): result = getattr(obj, opname)() if not isinstance(obj, PeriodIndex): - expected = getattr(obj.values, opname)() + if isinstance(obj.values, ArrowStringArrayNumpySemantics): + # max not on the interface + expected = getattr(np.array(obj.values), opname)() + else: + expected = getattr(obj.values, opname)() else: expected = Period(ordinal=getattr(obj.asi8, opname)(), freq=obj.freq) @@ -1165,7 +1170,7 @@ def test_assert_idxminmax_empty_raises(self, test_input, error_type): with pytest.raises(ValueError, match=msg): test_input.idxmax(skipna=False) - def test_idxminmax_object_dtype(self): + def test_idxminmax_object_dtype(self, using_infer_string): # pre-2.1 object-dtype was disallowed for argmin/max ser = Series(["foo", "bar", "baz"]) assert ser.idxmax() == 0 @@ -1179,18 +1184,19 @@ def test_idxminmax_object_dtype(self): assert ser2.idxmin() == 0 assert ser2.idxmin(skipna=False) == 0 - # attempting to compare np.nan with string raises - ser3 = Series(["foo", "foo", "bar", "bar", None, np.nan, "baz"]) - msg = "'>' not supported between instances of 'float' and 'str'" - with pytest.raises(TypeError, match=msg): - ser3.idxmax() - with pytest.raises(TypeError, match=msg): - ser3.idxmax(skipna=False) - msg = "'<' not supported between instances of 'float' and 'str'" - with pytest.raises(TypeError, match=msg): - ser3.idxmin() - with pytest.raises(TypeError, match=msg): - ser3.idxmin(skipna=False) + if not using_infer_string: + # attempting to compare np.nan with string raises + ser3 = Series(["foo", "foo", "bar", "bar", None, np.nan, "baz"]) + msg = "'>' not supported between instances of 'float' and 'str'" + with pytest.raises(TypeError, match=msg): + ser3.idxmax() + with pytest.raises(TypeError, match=msg): + ser3.idxmax(skipna=False) + msg = "'<' not supported between instances of 'float' and 'str'" + with pytest.raises(TypeError, match=msg): + ser3.idxmin() + with pytest.raises(TypeError, match=msg): + ser3.idxmin(skipna=False) def test_idxminmax_object_frame(self): # GH#4279 @@ -1445,14 +1451,14 @@ def test_mode_str_obj(self, dropna, expected1, expected2, expected3): s = Series(data, dtype=object) result = s.mode(dropna) - expected2 = Series(expected2, dtype=object) + expected2 = Series(expected2, dtype=None if expected2 == ["bar"] else object) tm.assert_series_equal(result, expected2) data = ["foo", "bar", "bar", np.nan, np.nan, np.nan] s = Series(data, dtype=object).astype(str) result = s.mode(dropna) - expected3 = Series(expected3, dtype=str) + expected3 = Series(expected3) tm.assert_series_equal(result, expected3) @pytest.mark.parametrize( @@ -1467,7 +1473,7 @@ def test_mode_mixeddtype(self, dropna, expected1, expected2): s = Series([1, "foo", "foo", np.nan, np.nan, np.nan]) result = s.mode(dropna) - expected = Series(expected2, dtype=object) + expected = Series(expected2, dtype=None if expected2 == ["foo"] else object) tm.assert_series_equal(result, expected) @pytest.mark.parametrize( From 22d3c9c66037440fc1fbc99b7e16f3f7c8d152c9 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 05:40:37 +0100 Subject: [PATCH 08/30] Adjust tests in internals folder for arrow string option (#56140) --- pandas/tests/internals/test_internals.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pandas/tests/internals/test_internals.py b/pandas/tests/internals/test_internals.py index 792d1323ec730..a9dbdb21f59fb 100644 --- a/pandas/tests/internals/test_internals.py +++ b/pandas/tests/internals/test_internals.py @@ -591,7 +591,7 @@ def test_astype(self, t): else: assert tmgr.iget(3).dtype.type == t - def test_convert(self): + def test_convert(self, using_infer_string): def _compare(old_mgr, new_mgr): """compare the blocks, numeric compare ==, object don't""" old_blocks = set(old_mgr.blocks) @@ -626,9 +626,10 @@ def _compare(old_mgr, new_mgr): mgr.iset(1, np.array(["2."] * N, dtype=np.object_)) mgr.iset(2, np.array(["foo."] * N, dtype=np.object_)) new_mgr = mgr.convert(copy=True) - assert new_mgr.iget(0).dtype == np.object_ - assert new_mgr.iget(1).dtype == np.object_ - assert new_mgr.iget(2).dtype == np.object_ + dtype = "string[pyarrow_numpy]" if using_infer_string else np.object_ + assert new_mgr.iget(0).dtype == dtype + assert new_mgr.iget(1).dtype == dtype + assert new_mgr.iget(2).dtype == dtype assert new_mgr.iget(3).dtype == np.int64 assert new_mgr.iget(4).dtype == np.float64 @@ -639,9 +640,9 @@ def _compare(old_mgr, new_mgr): mgr.iset(1, np.array(["2."] * N, dtype=np.object_)) mgr.iset(2, np.array(["foo."] * N, dtype=np.object_)) new_mgr = mgr.convert(copy=True) - assert new_mgr.iget(0).dtype == np.object_ - assert new_mgr.iget(1).dtype == np.object_ - assert new_mgr.iget(2).dtype == np.object_ + assert new_mgr.iget(0).dtype == dtype + assert new_mgr.iget(1).dtype == dtype + assert new_mgr.iget(2).dtype == dtype assert new_mgr.iget(3).dtype == np.int32 assert new_mgr.iget(4).dtype == np.bool_ assert new_mgr.iget(5).dtype.type, np.datetime64 From 8ba1ab08e81ff4147cffe6e624a294c82b3fb01f Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 05:41:57 +0100 Subject: [PATCH 09/30] Adjust tests in generic folder for arrow string option (#56139) --- pandas/tests/generic/test_to_xarray.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pandas/tests/generic/test_to_xarray.py b/pandas/tests/generic/test_to_xarray.py index d6eacf4f9079b..5fb432f849643 100644 --- a/pandas/tests/generic/test_to_xarray.py +++ b/pandas/tests/generic/test_to_xarray.py @@ -29,7 +29,7 @@ def df(self): } ) - def test_to_xarray_index_types(self, index_flat, df): + def test_to_xarray_index_types(self, index_flat, df, using_infer_string): index = index_flat # MultiIndex is tested in test_to_xarray_with_multiindex if len(index) == 0: @@ -51,7 +51,9 @@ def test_to_xarray_index_types(self, index_flat, df): # datetimes w/tz are preserved # column names are lost expected = df.copy() - expected["f"] = expected["f"].astype(object) + expected["f"] = expected["f"].astype( + object if not using_infer_string else "string[pyarrow_numpy]" + ) expected.columns.name = None tm.assert_frame_equal(result.to_dataframe(), expected) @@ -63,7 +65,7 @@ def test_to_xarray_empty(self, df): assert result.dims["foo"] == 0 assert isinstance(result, Dataset) - def test_to_xarray_with_multiindex(self, df): + def test_to_xarray_with_multiindex(self, df, using_infer_string): from xarray import Dataset # MultiIndex @@ -78,7 +80,9 @@ def test_to_xarray_with_multiindex(self, df): result = result.to_dataframe() expected = df.copy() - expected["f"] = expected["f"].astype(object) + expected["f"] = expected["f"].astype( + object if not using_infer_string else "string[pyarrow_numpy]" + ) expected.columns.name = None tm.assert_frame_equal(result, expected) From 2ed994f289e2a491c17a225ceaa7cb3430f16894 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 05:44:11 +0100 Subject: [PATCH 10/30] Adjust tests in dtypes folder for arrow string option (#56125) Adjust tests in base folder for arrow string option --- pandas/tests/computation/test_eval.py | 2 +- pandas/tests/dtypes/cast/test_construct_ndarray.py | 10 ++++++++-- pandas/tests/dtypes/cast/test_infer_dtype.py | 12 ++++++++++-- pandas/tests/dtypes/test_common.py | 8 ++++---- pandas/tests/dtypes/test_dtypes.py | 5 +++-- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/pandas/tests/computation/test_eval.py b/pandas/tests/computation/test_eval.py index fe49446424de1..45215cd3b5e96 100644 --- a/pandas/tests/computation/test_eval.py +++ b/pandas/tests/computation/test_eval.py @@ -1832,7 +1832,7 @@ def test_bool_ops_fails_on_scalars(lhs, cmp, rhs, engine, parser): ], ) def test_equals_various(other): - df = DataFrame({"A": ["a", "b", "c"]}) + df = DataFrame({"A": ["a", "b", "c"]}, dtype=object) result = df.eval(f"A == {other}") expected = Series([False, False, False], name="A") if USE_NUMEXPR: diff --git a/pandas/tests/dtypes/cast/test_construct_ndarray.py b/pandas/tests/dtypes/cast/test_construct_ndarray.py index 10085ddde5c8f..ab468c81124bc 100644 --- a/pandas/tests/dtypes/cast/test_construct_ndarray.py +++ b/pandas/tests/dtypes/cast/test_construct_ndarray.py @@ -1,6 +1,7 @@ import numpy as np import pytest +import pandas as pd import pandas._testing as tm from pandas.core.construction import sanitize_array @@ -15,9 +16,14 @@ ([1, 2, None], np.dtype("str"), np.array(["1", "2", None])), ], ) -def test_construct_1d_ndarray_preserving_na(values, dtype, expected): +def test_construct_1d_ndarray_preserving_na( + values, dtype, expected, using_infer_string +): result = sanitize_array(values, index=None, dtype=dtype) - tm.assert_numpy_array_equal(result, expected) + if using_infer_string and expected.dtype == object and dtype is None: + tm.assert_extension_array_equal(result, pd.array(expected)) + else: + tm.assert_numpy_array_equal(result, expected) @pytest.mark.parametrize("dtype", ["m8[ns]", "M8[ns]"]) diff --git a/pandas/tests/dtypes/cast/test_infer_dtype.py b/pandas/tests/dtypes/cast/test_infer_dtype.py index 50eaa1f4d8713..679031a625c2d 100644 --- a/pandas/tests/dtypes/cast/test_infer_dtype.py +++ b/pandas/tests/dtypes/cast/test_infer_dtype.py @@ -159,8 +159,10 @@ def test_infer_dtype_from_scalar_errors(): (Timestamp("20160101", tz="UTC"), "datetime64[s, UTC]"), ], ) -def test_infer_dtype_from_scalar(value, expected): +def test_infer_dtype_from_scalar(value, expected, using_infer_string): dtype, _ = infer_dtype_from_scalar(value) + if using_infer_string and value == "foo": + expected = "string" assert is_dtype_equal(dtype, expected) with pytest.raises(TypeError, match="must be list-like"): @@ -189,8 +191,14 @@ def test_infer_dtype_from_scalar(value, expected): ), ], ) -def test_infer_dtype_from_array(arr, expected): +def test_infer_dtype_from_array(arr, expected, using_infer_string): dtype, _ = infer_dtype_from_array(arr) + if ( + using_infer_string + and isinstance(arr, Series) + and arr.tolist() == ["a", "b", "c"] + ): + expected = "string" assert is_dtype_equal(dtype, expected) diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index d946b8da01fad..42043f95a7ace 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -676,9 +676,9 @@ def test_is_complex_dtype(): (np.dtype("float64"), np.dtype("float64")), (str, np.dtype(str)), (pd.Series([1, 2], dtype=np.dtype("int16")), np.dtype("int16")), - (pd.Series(["a", "b"]), np.dtype(object)), + (pd.Series(["a", "b"], dtype=object), np.dtype(object)), (pd.Index([1, 2]), np.dtype("int64")), - (pd.Index(["a", "b"]), np.dtype(object)), + (pd.Index(["a", "b"], dtype=object), np.dtype(object)), ("category", "category"), (pd.Categorical(["a", "b"]).dtype, CategoricalDtype(["a", "b"])), (pd.Categorical(["a", "b"]), CategoricalDtype(["a", "b"])), @@ -727,9 +727,9 @@ def test_get_dtype_fails(input_param, expected_error_message): (np.dtype("float64"), np.float64), (str, np.dtype(str).type), (pd.Series([1, 2], dtype=np.dtype("int16")), np.int16), - (pd.Series(["a", "b"]), np.object_), + (pd.Series(["a", "b"], dtype=object), np.object_), (pd.Index([1, 2], dtype="int64"), np.int64), - (pd.Index(["a", "b"]), np.object_), + (pd.Index(["a", "b"], dtype=object), np.object_), ("category", CategoricalDtypeType), (pd.Categorical(["a", "b"]).dtype, CategoricalDtypeType), (pd.Categorical(["a", "b"]), CategoricalDtypeType), diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index 1c5514eff46d5..4e8f375b31674 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -1055,13 +1055,14 @@ def test_from_categorical_dtype_both(self): ) assert result == CategoricalDtype([1, 2], ordered=False) - def test_str_vs_repr(self, ordered): + def test_str_vs_repr(self, ordered, using_infer_string): c1 = CategoricalDtype(["a", "b"], ordered=ordered) assert str(c1) == "category" # Py2 will have unicode prefixes + dtype = "string" if using_infer_string else "object" pat = ( r"CategoricalDtype\(categories=\[.*\], ordered={ordered}, " - r"categories_dtype=object\)" + rf"categories_dtype={dtype}\)" ) assert re.match(pat.format(ordered=ordered), repr(c1)) From ac45f880fcde415ffea3d96280ff39ed4f7dac9e Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 05:46:58 +0100 Subject: [PATCH 11/30] Adjust tests in base folder for arrow string option (#56124) --- pandas/tests/base/test_constructors.py | 9 +++++++-- pandas/tests/base/test_conversion.py | 17 +++++++++++++---- pandas/tests/base/test_misc.py | 11 +++++++++-- pandas/tests/base/test_unique.py | 3 +++ pandas/tests/base/test_value_counts.py | 15 ++++++++++----- 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/pandas/tests/base/test_constructors.py b/pandas/tests/base/test_constructors.py index 4e954891c2d98..f3ac60f672ee1 100644 --- a/pandas/tests/base/test_constructors.py +++ b/pandas/tests/base/test_constructors.py @@ -138,7 +138,9 @@ class TestConstruction: "object-string", ], ) - def test_constructor_datetime_outofbound(self, a, constructor): + def test_constructor_datetime_outofbound( + self, a, constructor, request, using_infer_string + ): # GH-26853 (+ bug GH-26206 out of bound non-ns unit) # No dtype specified (dtype inference) @@ -150,7 +152,10 @@ def test_constructor_datetime_outofbound(self, a, constructor): assert result.dtype == "M8[s]" else: result = constructor(a) - assert result.dtype == "object" + if using_infer_string and "object-string" in request.node.callspec.id: + assert result.dtype == "string" + else: + assert result.dtype == "object" tm.assert_numpy_array_equal(result.to_numpy(), a) # Explicit dtype specified diff --git a/pandas/tests/base/test_conversion.py b/pandas/tests/base/test_conversion.py index 2fc6e786e3198..4f3e4d3365179 100644 --- a/pandas/tests/base/test_conversion.py +++ b/pandas/tests/base/test_conversion.py @@ -20,6 +20,7 @@ SparseArray, TimedeltaArray, ) +from pandas.core.arrays.string_arrow import ArrowStringArrayNumpySemantics class TestToIterable: @@ -215,7 +216,9 @@ def test_iter_box_period(self): ), ], ) -def test_values_consistent(arr, expected_type, dtype): +def test_values_consistent(arr, expected_type, dtype, using_infer_string): + if using_infer_string and dtype == "object": + expected_type = ArrowStringArrayNumpySemantics l_values = Series(arr)._values r_values = pd.Index(arr)._values assert type(l_values) is expected_type @@ -358,17 +361,23 @@ def test_to_numpy(arr, expected, index_or_series_or_array, request): @pytest.mark.parametrize( "arr", [np.array([1, 2, 3], dtype="int64"), np.array(["a", "b", "c"], dtype=object)] ) -def test_to_numpy_copy(arr, as_series): +def test_to_numpy_copy(arr, as_series, using_infer_string): obj = pd.Index(arr, copy=False) if as_series: obj = Series(obj.values, copy=False) # no copy by default result = obj.to_numpy() - assert np.shares_memory(arr, result) is True + if using_infer_string and arr.dtype == object: + assert np.shares_memory(arr, result) is False + else: + assert np.shares_memory(arr, result) is True result = obj.to_numpy(copy=False) - assert np.shares_memory(arr, result) is True + if using_infer_string and arr.dtype == object: + assert np.shares_memory(arr, result) is False + else: + assert np.shares_memory(arr, result) is True # copy=True result = obj.to_numpy(copy=True) diff --git a/pandas/tests/base/test_misc.py b/pandas/tests/base/test_misc.py index c6fd4955d2d63..15daca86b14ee 100644 --- a/pandas/tests/base/test_misc.py +++ b/pandas/tests/base/test_misc.py @@ -3,6 +3,8 @@ import numpy as np import pytest +from pandas._config import using_pyarrow_string_dtype + from pandas.compat import PYPY from pandas.core.dtypes.common import ( @@ -80,7 +82,10 @@ def test_ndarray_compat_properties(index_or_series_obj): assert Series([1]).item() == 1 -@pytest.mark.skipif(PYPY, reason="not relevant for PyPy") +@pytest.mark.skipif( + PYPY or using_pyarrow_string_dtype(), + reason="not relevant for PyPy doesn't work properly for arrow strings", +) def test_memory_usage(index_or_series_memory_obj): obj = index_or_series_memory_obj # Clear index caches so that len(obj) == 0 report 0 memory usage @@ -175,7 +180,9 @@ def test_access_by_position(index_flat): assert index[-1] == index[size - 1] msg = f"index {size} is out of bounds for axis 0 with size {size}" - if is_dtype_equal(index.dtype, "string[pyarrow]"): + if is_dtype_equal(index.dtype, "string[pyarrow]") or is_dtype_equal( + index.dtype, "string[pyarrow_numpy]" + ): msg = "index out of bounds" with pytest.raises(IndexError, match=msg): index[size] diff --git a/pandas/tests/base/test_unique.py b/pandas/tests/base/test_unique.py index 4c845d8f24d01..d3fe144f70cfc 100644 --- a/pandas/tests/base/test_unique.py +++ b/pandas/tests/base/test_unique.py @@ -1,6 +1,8 @@ import numpy as np import pytest +from pandas._config import using_pyarrow_string_dtype + import pandas as pd import pandas._testing as tm from pandas.tests.base.common import allow_na_ops @@ -98,6 +100,7 @@ def test_nunique_null(null_obj, index_or_series_obj): @pytest.mark.single_cpu +@pytest.mark.xfail(using_pyarrow_string_dtype(), reason="decoding fails") def test_unique_bad_unicode(index_or_series): # regression test for #34550 uval = "\ud83d" # smiley emoji diff --git a/pandas/tests/base/test_value_counts.py b/pandas/tests/base/test_value_counts.py index 75915b7c67548..2729666398877 100644 --- a/pandas/tests/base/test_value_counts.py +++ b/pandas/tests/base/test_value_counts.py @@ -14,6 +14,7 @@ Series, Timedelta, TimedeltaIndex, + array, ) import pandas._testing as tm from pandas.tests.base.common import allow_na_ops @@ -113,7 +114,7 @@ def test_value_counts_null(null_obj, index_or_series_obj): tm.assert_series_equal(result, expected) -def test_value_counts_inferred(index_or_series): +def test_value_counts_inferred(index_or_series, using_infer_string): klass = index_or_series s_values = ["a", "b", "b", "b", "b", "c", "d", "d", "a", "a"] s = klass(s_values) @@ -125,7 +126,9 @@ def test_value_counts_inferred(index_or_series): tm.assert_index_equal(s.unique(), exp) else: exp = np.unique(np.array(s_values, dtype=np.object_)) - tm.assert_numpy_array_equal(s.unique(), exp) + if using_infer_string: + exp = array(exp) + tm.assert_equal(s.unique(), exp) assert s.nunique() == 4 # don't sort, have to sort after the fact as not sorting is @@ -147,7 +150,7 @@ def test_value_counts_inferred(index_or_series): tm.assert_series_equal(hist, expected) -def test_value_counts_bins(index_or_series): +def test_value_counts_bins(index_or_series, using_infer_string): klass = index_or_series s_values = ["a", "b", "b", "b", "b", "c", "d", "d", "a", "a"] s = klass(s_values) @@ -201,7 +204,9 @@ def test_value_counts_bins(index_or_series): tm.assert_index_equal(s.unique(), exp) else: exp = np.array(["a", "b", np.nan, "d"], dtype=object) - tm.assert_numpy_array_equal(s.unique(), exp) + if using_infer_string: + exp = array(exp) + tm.assert_equal(s.unique(), exp) assert s.nunique() == 3 s = klass({}) if klass is dict else klass({}, dtype=object) @@ -246,7 +251,7 @@ def test_value_counts_datetime64(index_or_series, unit): expected_s = Series([3, 2, 1], index=idx, name="count") tm.assert_series_equal(s.value_counts(), expected_s) - expected = pd.array( + expected = array( np.array( ["2010-01-01 00:00:00", "2009-01-01 00:00:00", "2008-09-09 00:00:00"], dtype=f"datetime64[{unit}]", From b8d04712e4953bf69b68a1911a9f56c571b24720 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 05:50:54 +0100 Subject: [PATCH 12/30] Fix arithmetic test folder for arrow string option (#56122) --- pandas/tests/arithmetic/test_object.py | 29 +++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/pandas/tests/arithmetic/test_object.py b/pandas/tests/arithmetic/test_object.py index 5ffbf1a38e845..33953900c2006 100644 --- a/pandas/tests/arithmetic/test_object.py +++ b/pandas/tests/arithmetic/test_object.py @@ -8,6 +8,8 @@ import numpy as np import pytest +from pandas._config import using_pyarrow_string_dtype + import pandas as pd from pandas import ( Series, @@ -172,7 +174,12 @@ def test_objarr_add_invalid(self, op, box_with_array): obj_ser = tm.box_expected(obj_ser, box) msg = "|".join( - ["can only concatenate str", "unsupported operand type", "must be str"] + [ + "can only concatenate str", + "unsupported operand type", + "must be str", + "has no kernel", + ] ) with pytest.raises(Exception, match=msg): op(obj_ser, 1) @@ -290,6 +297,7 @@ def test_iadd_string(self): index += "_x" assert "a_x" in index + @pytest.mark.xfail(using_pyarrow_string_dtype(), reason="add doesn't work") def test_add(self): index = tm.makeStringIndex(100) expected = pd.Index(index.values * 2) @@ -304,17 +312,24 @@ def test_add(self): expected = pd.Index(["1a", "1b", "1c"]) tm.assert_index_equal("1" + index, expected) - def test_sub_fail(self): + def test_sub_fail(self, using_infer_string): index = tm.makeStringIndex(100) - msg = "unsupported operand type|Cannot broadcast" - with pytest.raises(TypeError, match=msg): + if using_infer_string: + import pyarrow as pa + + err = pa.lib.ArrowNotImplementedError + msg = "has no kernel" + else: + err = TypeError + msg = "unsupported operand type|Cannot broadcast" + with pytest.raises(err, match=msg): index - "a" - with pytest.raises(TypeError, match=msg): + with pytest.raises(err, match=msg): index - index - with pytest.raises(TypeError, match=msg): + with pytest.raises(err, match=msg): index - index.tolist() - with pytest.raises(TypeError, match=msg): + with pytest.raises(err, match=msg): index.tolist() - index def test_sub_object(self): From 24219311ec72eae247125e09411fd536a4551a44 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 25 Nov 2023 21:03:18 -0800 Subject: [PATCH 13/30] PERF: array_strptime (#55898) * PERF: array_strptime avoid object path * creso fixup * Fixup leftover assertion * object instead of raise * post-merge fixup * post-merge fixup --- pandas/_libs/tslibs/strptime.pyx | 249 ++++++++++++++++++++++++++++--- pandas/core/tools/datetimes.py | 66 ++------ 2 files changed, 238 insertions(+), 77 deletions(-) diff --git a/pandas/_libs/tslibs/strptime.pyx b/pandas/_libs/tslibs/strptime.pyx index f6400ce6bae56..5616bc17045ad 100644 --- a/pandas/_libs/tslibs/strptime.pyx +++ b/pandas/_libs/tslibs/strptime.pyx @@ -47,7 +47,10 @@ from numpy cimport ( ) from pandas._libs.missing cimport checknull_with_nat_and_na -from pandas._libs.tslibs.conversion cimport get_datetime64_nanos +from pandas._libs.tslibs.conversion cimport ( + get_datetime64_nanos, + parse_pydatetime, +) from pandas._libs.tslibs.dtypes cimport ( get_supported_reso, npy_unit_to_abbrev, @@ -55,6 +58,7 @@ from pandas._libs.tslibs.dtypes cimport ( ) from pandas._libs.tslibs.nattype cimport ( NPY_NAT, + c_NaT as NaT, c_nat_strings as nat_strings, ) from pandas._libs.tslibs.np_datetime cimport ( @@ -65,7 +69,6 @@ from pandas._libs.tslibs.np_datetime cimport ( npy_datetimestruct, npy_datetimestruct_to_datetime, pydate_to_dt64, - pydatetime_to_dt64, string_to_dts, ) @@ -82,6 +85,8 @@ from pandas._libs.util cimport ( from pandas._libs.tslibs.timestamps import Timestamp +from pandas._libs.tslibs.tzconversion cimport tz_localize_to_utc_single + cnp.import_array() @@ -314,11 +319,13 @@ def array_strptime( Py_ssize_t i, n = len(values) npy_datetimestruct dts int64_t[::1] iresult - object[::1] result_timezone object val, tz + bint seen_datetime_offset = False bint is_raise = errors=="raise" bint is_ignore = errors=="ignore" bint is_coerce = errors=="coerce" + bint is_same_offsets + set out_tzoffset_vals = set() tzinfo tz_out = None bint iso_format = format_is_iso(fmt) NPY_DATETIMEUNIT out_bestunit, item_reso @@ -338,7 +345,6 @@ def array_strptime( abbrev = npy_unit_to_abbrev(creso) result = np.empty(n, dtype=f"M8[{abbrev}]") iresult = result.view("i8") - result_timezone = np.empty(n, dtype="object") dts.us = dts.ps = dts.as = 0 @@ -361,16 +367,10 @@ def array_strptime( if infer_reso: creso = state.creso tz_out = state.process_datetime(val, tz_out, utc) - if isinstance(val, _Timestamp): - val = (<_Timestamp>val)._as_creso(creso) - iresult[i] = val.tz_localize(None)._value - else: - iresult[i] = pydatetime_to_dt64( - val.replace(tzinfo=None), &dts, reso=creso - ) - result_timezone[i] = val.tzinfo + iresult[i] = parse_pydatetime(val, &dts, state.creso) continue elif PyDate_Check(val): + state.found_other = True item_reso = NPY_DATETIMEUNIT.NPY_FR_s state.update_creso(item_reso) if infer_reso: @@ -378,6 +378,7 @@ def array_strptime( iresult[i] = pydate_to_dt64(val, &dts, reso=creso) continue elif cnp.is_datetime64_object(val): + state.found_other = True item_reso = get_supported_reso(get_datetime64_unit(val)) state.update_creso(item_reso) if infer_reso: @@ -418,13 +419,17 @@ def array_strptime( f"Out of bounds {attrname} timestamp: {val}" ) from err if out_local == 1: - # Store the out_tzoffset in seconds - # since we store the total_seconds of - # dateutil.tz.tzoffset objects + nsecs = out_tzoffset * 60 + out_tzoffset_vals.add(nsecs) + seen_datetime_offset = True tz = timezone(timedelta(minutes=out_tzoffset)) - result_timezone[i] = tz - out_local = 0 - out_tzoffset = 0 + value = tz_localize_to_utc_single( + value, tz, ambiguous="raise", nonexistent=None, creso=creso + ) + else: + tz = None + out_tzoffset_vals.add("naive") + state.found_naive_str = True iresult[i] = value continue @@ -450,6 +455,7 @@ def array_strptime( state.update_creso(item_reso) if infer_reso: creso = state.creso + try: iresult[i] = npy_datetimestruct_to_datetime(creso, &dts) except OverflowError as err: @@ -457,7 +463,26 @@ def array_strptime( raise OutOfBoundsDatetime( f"Out of bounds {attrname} timestamp: {val}" ) from err - result_timezone[i] = tz + + if tz is not None: + ival = iresult[i] + iresult[i] = tz_localize_to_utc_single( + ival, tz, ambiguous="raise", nonexistent=None, creso=creso + ) + nsecs = (ival - iresult[i]) + if creso == NPY_FR_ns: + nsecs = nsecs // 10**9 + elif creso == NPY_DATETIMEUNIT.NPY_FR_us: + nsecs = nsecs // 10**6 + elif creso == NPY_DATETIMEUNIT.NPY_FR_ms: + nsecs = nsecs // 10**3 + + out_tzoffset_vals.add(nsecs) + seen_datetime_offset = True + else: + state.found_naive_str = True + tz = None + out_tzoffset_vals.add("naive") except (ValueError, OutOfBoundsDatetime) as ex: ex.args = ( @@ -474,7 +499,37 @@ def array_strptime( continue elif is_raise: raise - return values, [] + return values, None + + if seen_datetime_offset and not utc: + is_same_offsets = len(out_tzoffset_vals) == 1 + if not is_same_offsets or (state.found_naive or state.found_other): + result2 = _array_strptime_object_fallback( + values, fmt=fmt, exact=exact, errors=errors, utc=utc + ) + return result2, None + elif tz_out is not None: + # GH#55693 + tz_offset = out_tzoffset_vals.pop() + tz_out2 = timezone(timedelta(seconds=tz_offset)) + if not tz_compare(tz_out, tz_out2): + # e.g. test_to_datetime_mixed_offsets_with_utc_false_deprecated + result2 = _array_strptime_object_fallback( + values, fmt=fmt, exact=exact, errors=errors, utc=utc + ) + return result2, None + # e.g. test_guess_datetime_format_with_parseable_formats + else: + # e.g. test_to_datetime_iso8601_with_timezone_valid + tz_offset = out_tzoffset_vals.pop() + tz_out = timezone(timedelta(seconds=tz_offset)) + elif not utc: + if tz_out and (state.found_other or state.found_naive_str): + # found_other indicates a tz-naive int, float, dt64, or date + result2 = _array_strptime_object_fallback( + values, fmt=fmt, exact=exact, errors=errors, utc=utc + ) + return result2, None if infer_reso: if state.creso_ever_changed: @@ -488,7 +543,6 @@ def array_strptime( utc=utc, creso=state.creso, ) - elif state.creso == NPY_DATETIMEUNIT.NPY_FR_GENERIC: # i.e. we never encountered anything non-NaT, default to "s". This # ensures that insert and concat-like operations with NaT @@ -499,7 +553,7 @@ def array_strptime( # a second pass. abbrev = npy_unit_to_abbrev(state.creso) result = iresult.base.view(f"M8[{abbrev}]") - return result, result_timezone.base + return result, tz_out cdef tzinfo _parse_with_format( @@ -737,6 +791,157 @@ cdef tzinfo _parse_with_format( return tz +def _array_strptime_object_fallback( + ndarray[object] values, + str fmt, + bint exact=True, + errors="raise", + bint utc=False, +): + + cdef: + Py_ssize_t i, n = len(values) + npy_datetimestruct dts + int64_t iresult + object val + tzinfo tz + bint is_raise = errors=="raise" + bint is_ignore = errors=="ignore" + bint is_coerce = errors=="coerce" + bint iso_format = format_is_iso(fmt) + NPY_DATETIMEUNIT creso, out_bestunit, item_reso + int out_local = 0, out_tzoffset = 0 + bint string_to_dts_succeeded = 0 + + assert is_raise or is_ignore or is_coerce + + item_reso = NPY_DATETIMEUNIT.NPY_FR_GENERIC + format_regex, locale_time = _get_format_regex(fmt) + + result = np.empty(n, dtype=object) + + dts.us = dts.ps = dts.as = 0 + + for i in range(n): + val = values[i] + try: + if isinstance(val, str): + if len(val) == 0 or val in nat_strings: + result[i] = NaT + continue + elif checknull_with_nat_and_na(val): + result[i] = NaT + continue + elif PyDateTime_Check(val): + result[i] = Timestamp(val) + continue + elif PyDate_Check(val): + result[i] = Timestamp(val) + continue + elif cnp.is_datetime64_object(val): + result[i] = Timestamp(val) + continue + elif ( + (is_integer_object(val) or is_float_object(val)) + and (val != val or val == NPY_NAT) + ): + result[i] = NaT + continue + else: + val = str(val) + + if fmt == "ISO8601": + string_to_dts_succeeded = not string_to_dts( + val, &dts, &out_bestunit, &out_local, + &out_tzoffset, False, None, False + ) + elif iso_format: + string_to_dts_succeeded = not string_to_dts( + val, &dts, &out_bestunit, &out_local, + &out_tzoffset, False, fmt, exact + ) + if string_to_dts_succeeded: + # No error reported by string_to_dts, pick back up + # where we left off + creso = get_supported_reso(out_bestunit) + try: + value = npy_datetimestruct_to_datetime(creso, &dts) + except OverflowError as err: + raise OutOfBoundsDatetime( + f"Out of bounds nanosecond timestamp: {val}" + ) from err + if out_local == 1: + tz = timezone(timedelta(minutes=out_tzoffset)) + value = tz_localize_to_utc_single( + value, tz, ambiguous="raise", nonexistent=None, creso=creso + ) + else: + tz = None + ts = Timestamp._from_value_and_reso(value, creso, tz) + result[i] = ts + continue + + if parse_today_now(val, &iresult, utc, NPY_FR_ns): + result[i] = Timestamp(val) + continue + + # Some ISO formats can't be parsed by string_to_dts + # For example, 6-digit YYYYMD. So, if there's an error, and a format + # was specified, then try the string-matching code below. If the format + # specified was 'ISO8601', then we need to error, because + # only string_to_dts handles mixed ISO8601 formats. + if not string_to_dts_succeeded and fmt == "ISO8601": + raise ValueError(f"Time data {val} is not ISO8601 format") + + tz = _parse_with_format( + val, fmt, exact, format_regex, locale_time, &dts, &item_reso + ) + try: + iresult = npy_datetimestruct_to_datetime(item_reso, &dts) + except OverflowError as err: + raise OutOfBoundsDatetime( + f"Out of bounds nanosecond timestamp: {val}" + ) from err + if tz is not None: + iresult = tz_localize_to_utc_single( + iresult, tz, ambiguous="raise", nonexistent=None, creso=item_reso + ) + ts = Timestamp._from_value_and_reso(iresult, item_reso, tz) + result[i] = ts + + except (ValueError, OutOfBoundsDatetime) as ex: + ex.args = ( + f"{str(ex)}, at position {i}. You might want to try:\n" + " - passing `format` if your strings have a consistent format;\n" + " - passing `format='ISO8601'` if your strings are " + "all ISO8601 but not necessarily in exactly the same format;\n" + " - passing `format='mixed'`, and the format will be " + "inferred for each element individually. " + "You might want to use `dayfirst` alongside this.", + ) + if is_coerce: + result[i] = NaT + continue + elif is_raise: + raise + return values + + import warnings + + from pandas.util._exceptions import find_stack_level + warnings.warn( + "In a future version of pandas, parsing datetimes with mixed time " + "zones will raise an error unless `utc=True`. Please specify `utc=True` " + "to opt in to the new behaviour and silence this warning. " + "To create a `Series` with mixed offsets and `object` dtype, " + "please use `apply` and `datetime.datetime.strptime`", + FutureWarning, + stacklevel=find_stack_level(), + ) + + return result + + class TimeRE(_TimeRE): """ Handle conversion from format directives to regexes. diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index e42e89b76e6f2..149bc2d932f0e 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -312,56 +312,6 @@ def _convert_and_box_cache( return _box_as_indexlike(result._values, utc=False, name=name) -def _return_parsed_timezone_results( - result: np.ndarray, timezones, utc: bool, name: str -) -> Index: - """ - Return results from array_strptime if a %z or %Z directive was passed. - - Parameters - ---------- - result : ndarray[int64] - int64 date representations of the dates - timezones : ndarray - pytz timezone objects - utc : bool - Whether to convert/localize timestamps to UTC. - name : string, default None - Name for a DatetimeIndex - - Returns - ------- - tz_result : Index-like of parsed dates with timezone - """ - tz_results = np.empty(len(result), dtype=object) - non_na_timezones = set() - for zone in unique(timezones): - # GH#50168 looping over unique timezones is faster than - # operating pointwise. - mask = timezones == zone - dta = DatetimeArray(result[mask]).tz_localize(zone) - if utc: - if dta.tzinfo is None: - dta = dta.tz_localize("utc") - else: - dta = dta.tz_convert("utc") - else: - if not dta.isna().all(): - non_na_timezones.add(zone) - tz_results[mask] = dta - if len(non_na_timezones) > 1: - warnings.warn( - "In a future version of pandas, parsing datetimes with mixed time " - "zones will raise an error unless `utc=True`. Please specify `utc=True` " - "to opt in to the new behaviour and silence this warning. " - "To create a `Series` with mixed offsets and `object` dtype, " - "please use `apply` and `datetime.datetime.strptime`", - FutureWarning, - stacklevel=find_stack_level(), - ) - return Index(tz_results, name=name) - - def _convert_listlike_datetimes( arg, format: str | None, @@ -515,11 +465,17 @@ def _array_strptime_with_fallback( """ Call array_strptime, with fallback behavior depending on 'errors'. """ - result, timezones = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc) - if any(tz is not None for tz in timezones): - return _return_parsed_timezone_results(result, timezones, utc, name) - - return _box_as_indexlike(result, utc=utc, name=name) + result, tz_out = array_strptime(arg, fmt, exact=exact, errors=errors, utc=utc) + if tz_out is not None: + dtype = DatetimeTZDtype(tz=tz_out) + dta = DatetimeArray._simple_new(result, dtype=dtype) + if utc: + dta = dta.tz_convert("UTC") + return Index(dta, name=name) + elif result.dtype != object and utc: + res = Index(result, dtype="M8[ns, UTC]", name=name) + return res + return Index(result, dtype=result.dtype, name=name) def _to_datetime_with_unit(arg, unit, name, utc: bool, errors: str) -> Index: From e84bd1ce90944158c0d90b14fdd42c6dada1777d Mon Sep 17 00:00:00 2001 From: smij720 <122238526+smij720@users.noreply.github.com> Date: Sat, 25 Nov 2023 21:05:51 -0800 Subject: [PATCH 14/30] DOC: Fix broken link for mamba installation (#56176) Fix broken link for mamba installation --- doc/source/development/contributing_environment.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/development/contributing_environment.rst b/doc/source/development/contributing_environment.rst index 0cc1fe2629e46..7fc42f6021f00 100644 --- a/doc/source/development/contributing_environment.rst +++ b/doc/source/development/contributing_environment.rst @@ -86,7 +86,7 @@ Before we begin, please: Option 1: using mamba (recommended) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* Install `mamba `_ +* Install `mamba `_ * Make sure your mamba is up to date (``mamba update mamba``) .. code-block:: none From 6971c9ca62c47785414bd5ddcc7a895147d07f51 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:07:41 -1000 Subject: [PATCH 15/30] CLN/TST: Remove old data creation methods in _testing/__init__.py (#56144) * Clean up sparely used index generating methods * Remove makePeriodSeries * Remove from all * Remove makeIntervalIndex * Remove makeIntervalIndex * Cast as object --- pandas/_testing/__init__.py | 74 ---------------------- pandas/conftest.py | 3 +- pandas/tests/dtypes/test_missing.py | 23 +++++-- pandas/tests/frame/methods/test_shift.py | 8 ++- pandas/tests/indexes/test_base.py | 19 +++++- pandas/tests/plotting/test_datetimelike.py | 2 +- pandas/tests/plotting/test_series.py | 11 ++-- pandas/tests/resample/test_base.py | 14 ++-- pandas/tests/util/test_make_objects.py | 15 ----- 9 files changed, 54 insertions(+), 115 deletions(-) delete mode 100644 pandas/tests/util/test_make_objects.py diff --git a/pandas/_testing/__init__.py b/pandas/_testing/__init__.py index 6f9766f32b894..3a3c98f253fcc 100644 --- a/pandas/_testing/__init__.py +++ b/pandas/_testing/__init__.py @@ -43,7 +43,6 @@ DataFrame, DatetimeIndex, Index, - IntervalIndex, MultiIndex, RangeIndex, Series, @@ -106,8 +105,6 @@ from pandas.core.construction import extract_array if TYPE_CHECKING: - from collections.abc import Iterable - from pandas._typing import ( Dtype, Frequency, @@ -388,12 +385,6 @@ def makeCategoricalIndex( ) -def makeIntervalIndex(k: int = 10, name=None, **kwargs) -> IntervalIndex: - """make a length k IntervalIndex""" - x = np.linspace(0, 100, num=(k + 1)) - return IntervalIndex.from_breaks(x, name=name, **kwargs) - - def makeBoolIndex(k: int = 10, name=None) -> Index: if k == 1: return Index([True], name=name) @@ -465,45 +456,6 @@ def makePeriodIndex(k: int = 10, name=None, **kwargs) -> PeriodIndex: return pi -def makeMultiIndex(k: int = 10, names=None, **kwargs): - N = (k // 2) + 1 - rng = range(N) - mi = MultiIndex.from_product([("foo", "bar"), rng], names=names, **kwargs) - assert len(mi) >= k # GH#38795 - return mi[:k] - - -def index_subclass_makers_generator(): - make_index_funcs = [ - makeDateIndex, - makePeriodIndex, - makeTimedeltaIndex, - makeRangeIndex, - makeIntervalIndex, - makeCategoricalIndex, - makeMultiIndex, - ] - yield from make_index_funcs - - -def all_timeseries_index_generator(k: int = 10) -> Iterable[Index]: - """ - Generator which can be iterated over to get instances of all the classes - which represent time-series. - - Parameters - ---------- - k: length of each of the index instances - """ - make_index_funcs: list[Callable[..., Index]] = [ - makeDateIndex, - makePeriodIndex, - makeTimedeltaIndex, - ] - for make_index_func in make_index_funcs: - yield make_index_func(k=k) - - # make series def make_rand_series(name=None, dtype=np.float64) -> Series: index = makeStringIndex(_N) @@ -546,24 +498,10 @@ def makeTimeSeries(nper=None, freq: Frequency = "B", name=None) -> Series: ) -def makePeriodSeries(nper=None, name=None) -> Series: - if nper is None: - nper = _N - return Series( - np.random.default_rng(2).standard_normal(nper), - index=makePeriodIndex(nper), - name=name, - ) - - def getTimeSeriesData(nper=None, freq: Frequency = "B") -> dict[str, Series]: return {c: makeTimeSeries(nper, freq) for c in getCols(_K)} -def getPeriodData(nper=None) -> dict[str, Series]: - return {c: makePeriodSeries(nper) for c in getCols(_K)} - - # make frame def makeTimeDataFrame(nper=None, freq: Frequency = "B") -> DataFrame: data = getTimeSeriesData(nper, freq) @@ -592,11 +530,6 @@ def makeMixedDataFrame() -> DataFrame: return DataFrame(getMixedTypeDict()[1]) -def makePeriodFrame(nper=None) -> DataFrame: - data = getPeriodData(nper) - return DataFrame(data) - - def makeCustomIndex( nentries, nlevels, @@ -1080,7 +1013,6 @@ def shares_memory(left, right) -> bool: "ALL_INT_NUMPY_DTYPES", "ALL_NUMPY_DTYPES", "ALL_REAL_NUMPY_DTYPES", - "all_timeseries_index_generator", "assert_almost_equal", "assert_attr_equal", "assert_categorical_equal", @@ -1129,12 +1061,10 @@ def shares_memory(left, right) -> bool: "get_finest_unit", "get_obj", "get_op_from_name", - "getPeriodData", "getSeriesData", "getTimeSeriesData", "iat", "iloc", - "index_subclass_makers_generator", "loc", "makeBoolIndex", "makeCategoricalIndex", @@ -1144,15 +1074,11 @@ def shares_memory(left, right) -> bool: "makeDateIndex", "makeFloatIndex", "makeFloatSeries", - "makeIntervalIndex", "makeIntIndex", "makeMixedDataFrame", - "makeMultiIndex", "makeNumericIndex", "makeObjectSeries", - "makePeriodFrame", "makePeriodIndex", - "makePeriodSeries", "make_rand_series", "makeRangeIndex", "makeStringIndex", diff --git a/pandas/conftest.py b/pandas/conftest.py index b24606f8007d2..4faf7faa6aa5d 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -61,6 +61,7 @@ from pandas import ( DataFrame, Interval, + IntervalIndex, Period, Series, Timedelta, @@ -630,7 +631,7 @@ def _create_mi_with_dt64tz_level(): "complex64": tm.makeNumericIndex(100, dtype="float64").astype("complex64"), "complex128": tm.makeNumericIndex(100, dtype="float64").astype("complex128"), "categorical": tm.makeCategoricalIndex(100), - "interval": tm.makeIntervalIndex(100), + "interval": IntervalIndex.from_breaks(np.linspace(0, 100, num=101)), "empty": Index([]), "tuples": MultiIndex.from_tuples(zip(["foo", "bar", "baz"], [1, 2, 3])), "mi-with-dt64tz-level": _create_mi_with_dt64tz_level(), diff --git a/pandas/tests/dtypes/test_missing.py b/pandas/tests/dtypes/test_missing.py index 1cc1e2725b9c7..d008249db1a3f 100644 --- a/pandas/tests/dtypes/test_missing.py +++ b/pandas/tests/dtypes/test_missing.py @@ -40,6 +40,7 @@ Series, TimedeltaIndex, date_range, + period_range, ) import pandas._testing as tm @@ -81,7 +82,7 @@ def test_notna_notnull(notna_f): tm.makeStringSeries(), tm.makeObjectSeries(), tm.makeTimeSeries(), - tm.makePeriodSeries(), + Series(range(5), period_range("2020-01-01", periods=5)), ], ) def test_null_check_is_series(null_func, ser): @@ -124,15 +125,25 @@ def test_isna_isnull(self, isna_f): @pytest.mark.parametrize("isna_f", [isna, isnull]) @pytest.mark.parametrize( - "df", + "data", [ - tm.makeTimeDataFrame(), - tm.makePeriodFrame(), - tm.makeMixedDataFrame(), + np.arange(4, dtype=float), + [0.0, 1.0, 0.0, 1.0], + Series(list("abcd"), dtype=object), + date_range("2020-01-01", periods=4), ], ) - def test_isna_isnull_frame(self, isna_f, df): + @pytest.mark.parametrize( + "index", + [ + date_range("2020-01-01", periods=4), + range(4), + period_range("2020-01-01", periods=4), + ], + ) + def test_isna_isnull_frame(self, isna_f, data, index): # frame + df = pd.DataFrame(data, index=index) result = isna_f(df) expected = df.apply(isna_f) tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/frame/methods/test_shift.py b/pandas/tests/frame/methods/test_shift.py index 98edd44e9f700..b21aa2d687682 100644 --- a/pandas/tests/frame/methods/test_shift.py +++ b/pandas/tests/frame/methods/test_shift.py @@ -241,7 +241,9 @@ def test_shift_by_offset(self, datetime_frame, frame_or_series): def test_shift_with_periodindex(self, frame_or_series): # Shifting with PeriodIndex - ps = tm.makePeriodFrame() + ps = DataFrame( + np.arange(4, dtype=float), index=pd.period_range("2020-01-01", periods=4) + ) ps = tm.get_obj(ps, frame_or_series) shifted = ps.shift(1) @@ -492,7 +494,7 @@ def test_shift_axis1_multiple_blocks_with_int_fill(self): tm.assert_frame_equal(result, expected) def test_period_index_frame_shift_with_freq(self, frame_or_series): - ps = tm.makePeriodFrame() + ps = DataFrame(range(4), index=pd.period_range("2020-01-01", periods=4)) ps = tm.get_obj(ps, frame_or_series) shifted = ps.shift(1, freq="infer") @@ -529,7 +531,7 @@ def test_datetime_frame_shift_with_freq(self, datetime_frame, frame_or_series): tm.assert_equal(unshifted, inferred_ts) def test_period_index_frame_shift_with_freq_error(self, frame_or_series): - ps = tm.makePeriodFrame() + ps = DataFrame(range(4), index=pd.period_range("2020-01-01", periods=4)) ps = tm.get_obj(ps, frame_or_series) msg = "Given freq M does not match PeriodIndex freq D" with pytest.raises(ValueError, match=msg): diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 5360f1c6ea6d9..8e11fc28cc387 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -1,5 +1,6 @@ from collections import defaultdict from datetime import datetime +from functools import partial import math import operator import re @@ -1596,11 +1597,23 @@ def test_generated_op_names(opname, index): assert method.__name__ == opname -@pytest.mark.parametrize("index_maker", tm.index_subclass_makers_generator()) -def test_index_subclass_constructor_wrong_kwargs(index_maker): +@pytest.mark.parametrize( + "klass", + [ + partial(CategoricalIndex, data=[1]), + partial(DatetimeIndex, data=["2020-01-01"]), + partial(PeriodIndex, data=["2020-01-01"]), + partial(TimedeltaIndex, data=["1 day"]), + partial(RangeIndex, data=range(1)), + partial(IntervalIndex, data=[pd.Interval(0, 1)]), + partial(Index, data=["a"], dtype=object), + partial(MultiIndex, levels=[1], codes=[0]), + ], +) +def test_index_subclass_constructor_wrong_kwargs(klass): # GH #19348 with pytest.raises(TypeError, match="unexpected keyword argument"): - index_maker(foo="bar") + klass(foo="bar") def test_deprecated_fastpath(): diff --git a/pandas/tests/plotting/test_datetimelike.py b/pandas/tests/plotting/test_datetimelike.py index 0f66d94535053..bfb9a5a9a1647 100644 --- a/pandas/tests/plotting/test_datetimelike.py +++ b/pandas/tests/plotting/test_datetimelike.py @@ -347,7 +347,7 @@ def test_irregular_datetime64_repr_bug(self): assert rs == xp def test_business_freq(self): - bts = tm.makePeriodSeries() + bts = Series(range(5), period_range("2020-01-01", periods=5)) msg = r"PeriodDtype\[B\] is deprecated" dt = bts.index[0].to_timestamp() with tm.assert_produces_warning(FutureWarning, match=msg): diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index f78f5c4879b9f..7d543d29c034d 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -14,6 +14,7 @@ DataFrame, Series, date_range, + period_range, plotting, ) import pandas._testing as tm @@ -45,11 +46,6 @@ def series(): return tm.makeStringSeries(name="series") -@pytest.fixture -def iseries(): - return tm.makePeriodSeries(name="iseries") - - class TestSeriesPlots: @pytest.mark.slow @pytest.mark.parametrize("kwargs", [{"label": "foo"}, {"use_index": False}]) @@ -82,8 +78,9 @@ def test_plot_ts_bar(self, ts): def test_plot_ts_area_stacked(self, ts): _check_plot_works(ts.plot.area, stacked=False) - def test_plot_iseries(self, iseries): - _check_plot_works(iseries.plot) + def test_plot_iseries(self): + ser = Series(range(5), period_range("2020-01-01", periods=5)) + _check_plot_works(ser.plot) @pytest.mark.parametrize( "kind", diff --git a/pandas/tests/resample/test_base.py b/pandas/tests/resample/test_base.py index 6ecbdff0018fa..7176cdf6ff9e4 100644 --- a/pandas/tests/resample/test_base.py +++ b/pandas/tests/resample/test_base.py @@ -5,6 +5,7 @@ from pandas import ( DataFrame, + DatetimeIndex, Index, MultiIndex, NaT, @@ -288,16 +289,19 @@ def test_resample_size_empty_dataframe(freq, empty_frame_dti): tm.assert_series_equal(result, expected) -@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning") -@pytest.mark.parametrize("index", tm.all_timeseries_index_generator(0)) +@pytest.mark.parametrize( + "index", + [ + PeriodIndex([], freq="M", name="a"), + DatetimeIndex([], name="a"), + TimedeltaIndex([], name="a"), + ], +) @pytest.mark.parametrize("dtype", [float, int, object, "datetime64[ns]"]) def test_resample_empty_dtypes(index, dtype, resample_method): # Empty series were sometimes causing a segfault (for the functions # with Cython bounds-checking disabled) or an IndexError. We just run # them to ensure they no longer do. (GH #10228) - if isinstance(index, PeriodIndex): - # GH#53511 - index = PeriodIndex([], freq="B", name=index.name) empty_series_dti = Series([], index, dtype) rs = empty_series_dti.resample("d", group_keys=False) try: diff --git a/pandas/tests/util/test_make_objects.py b/pandas/tests/util/test_make_objects.py deleted file mode 100644 index 74e2366db2a1c..0000000000000 --- a/pandas/tests/util/test_make_objects.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Tests for tm.makeFoo functions. -""" - - -import numpy as np - -import pandas._testing as tm - - -def test_make_multiindex_respects_k(): - # GH#38795 respect 'k' arg - N = np.random.default_rng(2).integers(0, 100) - mi = tm.makeMultiIndex(k=N) - assert len(mi) == N From 6d1b07f11b3f383bb8e4f895cfead3125e89379d Mon Sep 17 00:00:00 2001 From: Thomas Li <47963215+lithomas1@users.noreply.github.com> Date: Sun, 26 Nov 2023 11:46:36 -0500 Subject: [PATCH 16/30] CLN: Get rid of PyArray_GetCastFunc (#56114) * CLN: Get rid of PyArray_GetCastFunc * remove old code * simplify as per suggestion * delete dead code * simplify again * Update objToJSON.c --- .../_libs/src/vendored/ujson/python/objToJSON.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/pandas/_libs/src/vendored/ujson/python/objToJSON.c b/pandas/_libs/src/vendored/ujson/python/objToJSON.c index 22ffec51c82f1..89f101964aff7 100644 --- a/pandas/_libs/src/vendored/ujson/python/objToJSON.c +++ b/pandas/_libs/src/vendored/ujson/python/objToJSON.c @@ -1280,13 +1280,7 @@ char **NpyArr_encodeLabels(PyArrayObject *labels, PyObjectEncoder *enc, NPY_DATETIMEUNIT dateUnit = NPY_FR_ns; if (PyTypeNum_ISDATETIME(type_num)) { is_datetimelike = 1; - PyArray_VectorUnaryFunc *castfunc = - PyArray_GetCastFunc(PyArray_DescrFromType(type_num), NPY_INT64); - if (!castfunc) { - PyErr_Format(PyExc_ValueError, "Cannot cast numpy dtype %d to long", - enc->npyType); - } - castfunc(dataptr, &i8date, 1, NULL, NULL); + i8date = *(npy_int64 *)dataptr; dateUnit = get_datetime_metadata_from_dtype(dtype).base; } else if (PyDate_Check(item) || PyDelta_Check(item)) { is_datetimelike = 1; @@ -1425,13 +1419,8 @@ void Object_beginTypeContext(JSOBJ _obj, JSONTypeContext *tc) { if (PyTypeNum_ISDATETIME(enc->npyType)) { int64_t longVal; - PyArray_VectorUnaryFunc *castfunc = - PyArray_GetCastFunc(PyArray_DescrFromType(enc->npyType), NPY_INT64); - if (!castfunc) { - PyErr_Format(PyExc_ValueError, "Cannot cast numpy dtype %d to long", - enc->npyType); - } - castfunc(enc->npyValue, &longVal, 1, NULL, NULL); + + longVal = *(npy_int64 *)enc->npyValue; if (longVal == get_nat()) { tc->type = JT_NULL; } else { From 45b937d64f6b7b6971856a47e379c7c87af7e00a Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 19:21:54 +0100 Subject: [PATCH 17/30] BUG: scatter discarding string columns (#56142) * BUG: scatter discarding string columns * Add test --- doc/source/whatsnew/v2.2.0.rst | 2 +- pandas/plotting/_matplotlib/core.py | 2 +- pandas/tests/plotting/frame/test_frame.py | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index fbff38aeefc51..d252c19a95d4a 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -546,8 +546,8 @@ Period Plotting ^^^^^^^^ - Bug in :meth:`DataFrame.plot.box` with ``vert=False`` and a matplotlib ``Axes`` created with ``sharey=True`` (:issue:`54941`) +- Bug in :meth:`DataFrame.plot.scatter` discaring string columns (:issue:`56142`) - Bug in :meth:`Series.plot` when reusing an ``ax`` object failing to raise when a ``how`` keyword is passed (:issue:`55953`) -- Groupby/resample/rolling ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py index 7096c1281a1b6..99314e60b7c00 100644 --- a/pandas/plotting/_matplotlib/core.py +++ b/pandas/plotting/_matplotlib/core.py @@ -688,7 +688,7 @@ def _compute_plot_data(self): # GH 18755, include object and category type for scatter plot if self._kind == "scatter": - include_type.extend(["object", "category"]) + include_type.extend(["object", "category", "string"]) numeric_data = data.select_dtypes(include=include_type, exclude=exclude_type) diff --git a/pandas/tests/plotting/frame/test_frame.py b/pandas/tests/plotting/frame/test_frame.py index f1fc1174416ca..2a864abc5ea4a 100644 --- a/pandas/tests/plotting/frame/test_frame.py +++ b/pandas/tests/plotting/frame/test_frame.py @@ -12,6 +12,8 @@ import numpy as np import pytest +import pandas.util._test_decorators as td + from pandas.core.dtypes.api import is_list_like import pandas as pd @@ -22,6 +24,7 @@ Series, bdate_range, date_range, + option_context, plotting, ) import pandas._testing as tm @@ -794,13 +797,17 @@ def test_scatterplot_datetime_data(self, x, y): _check_plot_works(df.plot.scatter, x=x, y=y) + @pytest.mark.parametrize( + "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))] + ) @pytest.mark.parametrize("x, y", [("a", "b"), (0, 1)]) @pytest.mark.parametrize("b_col", [[2, 3, 4], ["a", "b", "c"]]) - def test_scatterplot_object_data(self, b_col, x, y): + def test_scatterplot_object_data(self, b_col, x, y, infer_string): # GH 18755 - df = DataFrame({"a": ["A", "B", "C"], "b": b_col}) + with option_context("future.infer_string", infer_string): + df = DataFrame({"a": ["A", "B", "C"], "b": b_col}) - _check_plot_works(df.plot.scatter, x=x, y=y) + _check_plot_works(df.plot.scatter, x=x, y=y) @pytest.mark.parametrize("ordered", [True, False]) @pytest.mark.parametrize( From 99bcf6bac6324bc631318de7c2ef2a5827bddfa0 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 19:22:57 +0100 Subject: [PATCH 18/30] BUG: translate losing object dtype with new string dtype (#56152) * BUG: translate losing object dtype with new string dtype * Fix * Update accessor.py --- doc/source/whatsnew/v2.1.4.rst | 2 +- pandas/core/strings/accessor.py | 22 ++++++++++++---------- pandas/tests/strings/test_find_replace.py | 6 +++++- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/doc/source/whatsnew/v2.1.4.rst b/doc/source/whatsnew/v2.1.4.rst index 543a9864ced26..77ce303dc1bfe 100644 --- a/doc/source/whatsnew/v2.1.4.rst +++ b/doc/source/whatsnew/v2.1.4.rst @@ -25,7 +25,7 @@ Bug fixes - Bug in :meth:`Index.__getitem__` returning wrong result for Arrow dtypes and negative stepsize (:issue:`55832`) - Fixed bug in :meth:`DataFrame.__setitem__` casting :class:`Index` with object-dtype to PyArrow backed strings when ``infer_string`` option is set (:issue:`55638`) - Fixed bug in :meth:`Index.insert` casting object-dtype to PyArrow backed strings when ``infer_string`` option is set (:issue:`55638`) -- +- Fixed bug in :meth:`Series.str.translate` losing object dtype when string option is set (:issue:`56152`) .. --------------------------------------------------------------------------- .. _whatsnew_214.other: diff --git a/pandas/core/strings/accessor.py b/pandas/core/strings/accessor.py index 58b904fd31b6a..9fa6e9973291d 100644 --- a/pandas/core/strings/accessor.py +++ b/pandas/core/strings/accessor.py @@ -259,6 +259,7 @@ def _wrap_result( fill_value=np.nan, returns_string: bool = True, returns_bool: bool = False, + dtype=None, ): from pandas import ( Index, @@ -379,29 +380,29 @@ def cons_row(x): out = out.get_level_values(0) return out else: - return Index(result, name=name) + return Index(result, name=name, dtype=dtype) else: index = self._orig.index # This is a mess. - dtype: DtypeObj | str | None + _dtype: DtypeObj | str | None = dtype vdtype = getattr(result, "dtype", None) if self._is_string: if is_bool_dtype(vdtype): - dtype = result.dtype + _dtype = result.dtype elif returns_string: - dtype = self._orig.dtype + _dtype = self._orig.dtype else: - dtype = vdtype - else: - dtype = vdtype + _dtype = vdtype + elif vdtype is not None: + _dtype = vdtype if expand: cons = self._orig._constructor_expanddim - result = cons(result, columns=name, index=index, dtype=dtype) + result = cons(result, columns=name, index=index, dtype=_dtype) else: # Must be a Series cons = self._orig._constructor - result = cons(result, name=name, index=index, dtype=dtype) + result = cons(result, name=name, index=index, dtype=_dtype) result = result.__finalize__(self._orig, method="str") if name is not None and result.ndim == 1: # __finalize__ might copy over the original name, but we may @@ -2317,7 +2318,8 @@ def translate(self, table): dtype: object """ result = self._data.array._str_translate(table) - return self._wrap_result(result) + dtype = object if self._data.dtype == "object" else None + return self._wrap_result(result, dtype=dtype) @forbid_nonstring_types(["bytes"]) def count(self, pat, flags: int = 0): diff --git a/pandas/tests/strings/test_find_replace.py b/pandas/tests/strings/test_find_replace.py index 78f0730d730e8..bd64a5dce3b9a 100644 --- a/pandas/tests/strings/test_find_replace.py +++ b/pandas/tests/strings/test_find_replace.py @@ -5,6 +5,7 @@ import pytest from pandas.errors import PerformanceWarning +import pandas.util._test_decorators as td import pandas as pd from pandas import ( @@ -893,7 +894,10 @@ def test_find_nan(any_string_dtype): # -------------------------------------------------------------------------------------- -def test_translate(index_or_series, any_string_dtype): +@pytest.mark.parametrize( + "infer_string", [False, pytest.param(True, marks=td.skip_if_no("pyarrow"))] +) +def test_translate(index_or_series, any_string_dtype, infer_string): obj = index_or_series( ["abcdefg", "abcc", "cdddfg", "cdefggg"], dtype=any_string_dtype ) From 74b2c37c11b35e1645b658f8bef65e9e3e1c2057 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 19:24:53 +0100 Subject: [PATCH 19/30] Adjust test in tools for new string option (#56178) --- pandas/tests/tools/test_to_datetime.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pandas/tests/tools/test_to_datetime.py b/pandas/tests/tools/test_to_datetime.py index 503032293dc81..ee209f74eb4e0 100644 --- a/pandas/tests/tools/test_to_datetime.py +++ b/pandas/tests/tools/test_to_datetime.py @@ -183,7 +183,7 @@ def test_to_datetime_format_YYYYMMDD_ignore_with_outofbounds(self, cache): errors="ignore", cache=cache, ) - expected = Index(["15010101", "20150101", np.nan]) + expected = Index(["15010101", "20150101", np.nan], dtype=object) tm.assert_index_equal(result, expected) def test_to_datetime_format_YYYYMMDD_coercion(self, cache): @@ -1206,7 +1206,9 @@ def test_out_of_bounds_errors_ignore2(self): # GH#12424 msg = "errors='ignore' is deprecated" with tm.assert_produces_warning(FutureWarning, match=msg): - res = to_datetime(Series(["2362-01-01", np.nan]), errors="ignore") + res = to_datetime( + Series(["2362-01-01", np.nan], dtype=object), errors="ignore" + ) exp = Series(["2362-01-01", np.nan], dtype=object) tm.assert_series_equal(res, exp) @@ -1489,7 +1491,7 @@ def test_datetime_invalid_index(self, values, format): warn, match="Could not infer format", raise_on_extra_warnings=False ): res = to_datetime(values, errors="ignore", format=format) - tm.assert_index_equal(res, Index(values)) + tm.assert_index_equal(res, Index(values, dtype=object)) with tm.assert_produces_warning( warn, match="Could not infer format", raise_on_extra_warnings=False @@ -1667,7 +1669,7 @@ def test_to_datetime_coerce_oob(self, string_arg, format, outofbounds): "errors, expected", [ ("coerce", Index([NaT, NaT])), - ("ignore", Index(["200622-12-31", "111111-24-11"])), + ("ignore", Index(["200622-12-31", "111111-24-11"], dtype=object)), ], ) def test_to_datetime_malformed_no_raise(self, errors, expected): @@ -2681,7 +2683,7 @@ def test_string_na_nat_conversion_malformed(self, cache): result = to_datetime(malformed, errors="ignore", cache=cache) # GH 21864 - expected = Index(malformed) + expected = Index(malformed, dtype=object) tm.assert_index_equal(result, expected) with pytest.raises(ValueError, match=msg): @@ -3670,7 +3672,7 @@ def test_to_datetime_mixed_not_necessarily_iso8601_raise(): ("errors", "expected"), [ ("coerce", DatetimeIndex(["2020-01-01 00:00:00", NaT])), - ("ignore", Index(["2020-01-01", "01-01-2000"])), + ("ignore", Index(["2020-01-01", "01-01-2000"], dtype=object)), ], ) def test_to_datetime_mixed_not_necessarily_iso8601_coerce(errors, expected): From 20ce643c1a7c5038eb129af55b73fba6b6d43ab1 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 19:25:44 +0100 Subject: [PATCH 20/30] Adjust tests in tseries folder for new string option (#56180) --- pandas/tests/tseries/frequencies/test_inference.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pandas/tests/tseries/frequencies/test_inference.py b/pandas/tests/tseries/frequencies/test_inference.py index ee8f161858b2b..75528a8b99c4d 100644 --- a/pandas/tests/tseries/frequencies/test_inference.py +++ b/pandas/tests/tseries/frequencies/test_inference.py @@ -431,12 +431,18 @@ def test_series_invalid_type(end): frequencies.infer_freq(s) -def test_series_inconvertible_string(): +def test_series_inconvertible_string(using_infer_string): # see gh-6407 - msg = "Unknown datetime string format" + if using_infer_string: + msg = "cannot infer freq from" - with pytest.raises(ValueError, match=msg): - frequencies.infer_freq(Series(["foo", "bar"])) + with pytest.raises(TypeError, match=msg): + frequencies.infer_freq(Series(["foo", "bar"])) + else: + msg = "Unknown datetime string format" + + with pytest.raises(ValueError, match=msg): + frequencies.infer_freq(Series(["foo", "bar"])) @pytest.mark.parametrize("freq", [None, "ms"]) From 09fb8e576306610af1b6b5dc613979e6150c9957 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Sun, 26 Nov 2023 08:35:25 -1000 Subject: [PATCH 21/30] TST/CLN: Remove make_rand_series (#56163) * TST/CLN: Remove make_rand_series * Remove old usages * Adjust test --- pandas/_testing/__init__.py | 20 ------------------- pandas/conftest.py | 12 +++++++---- pandas/tests/apply/test_invalid_arg.py | 5 +---- pandas/tests/base/test_misc.py | 2 +- pandas/tests/dtypes/test_missing.py | 2 -- pandas/tests/generic/test_generic.py | 20 +++++++++++++++---- pandas/tests/io/pytables/test_append.py | 2 +- pandas/tests/io/pytables/test_keys.py | 5 ++++- pandas/tests/io/pytables/test_read.py | 2 +- pandas/tests/io/pytables/test_round_trip.py | 4 ++-- pandas/tests/io/pytables/test_store.py | 4 +++- pandas/tests/plotting/test_series.py | 4 +++- pandas/tests/reductions/test_reductions.py | 4 ++-- .../tests/reductions/test_stat_reductions.py | 20 +++++++++---------- pandas/tests/series/test_arithmetic.py | 6 +++++- pandas/tests/series/test_unary.py | 6 ++---- 16 files changed, 59 insertions(+), 59 deletions(-) diff --git a/pandas/_testing/__init__.py b/pandas/_testing/__init__.py index 3a3c98f253fcc..a74fb2bf48bc4 100644 --- a/pandas/_testing/__init__.py +++ b/pandas/_testing/__init__.py @@ -456,23 +456,6 @@ def makePeriodIndex(k: int = 10, name=None, **kwargs) -> PeriodIndex: return pi -# make series -def make_rand_series(name=None, dtype=np.float64) -> Series: - index = makeStringIndex(_N) - data = np.random.default_rng(2).standard_normal(_N) - with np.errstate(invalid="ignore"): - data = data.astype(dtype, copy=False) - return Series(data, index=index, name=name) - - -def makeFloatSeries(name=None) -> Series: - return make_rand_series(name=name) - - -def makeStringSeries(name=None) -> Series: - return make_rand_series(name=name) - - def makeObjectSeries(name=None) -> Series: data = makeStringIndex(_N) data = Index(data, dtype=object) @@ -1073,16 +1056,13 @@ def shares_memory(left, right) -> bool: "makeDataFrame", "makeDateIndex", "makeFloatIndex", - "makeFloatSeries", "makeIntIndex", "makeMixedDataFrame", "makeNumericIndex", "makeObjectSeries", "makePeriodIndex", - "make_rand_series", "makeRangeIndex", "makeStringIndex", - "makeStringSeries", "makeTimeDataFrame", "makeTimedeltaIndex", "makeTimeSeries", diff --git a/pandas/conftest.py b/pandas/conftest.py index 4faf7faa6aa5d..350871c3085c1 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -729,9 +729,11 @@ def string_series() -> Series: """ Fixture for Series of floats with Index of unique strings """ - s = tm.makeStringSeries() - s.name = "series" - return s + return Series( + np.arange(30, dtype=np.float64) * 1.1, + index=Index([f"i_{i}" for i in range(30)], dtype=object), + name="series", + ) @pytest.fixture @@ -776,7 +778,9 @@ def series_with_simple_index(index) -> Series: _narrow_series = { - f"{dtype.__name__}-series": tm.make_rand_series(name="a", dtype=dtype) + f"{dtype.__name__}-series": Series( + range(30), index=[f"i-{i}" for i in range(30)], name="a", dtype=dtype + ) for dtype in tm.NARROW_NP_DTYPES } diff --git a/pandas/tests/apply/test_invalid_arg.py b/pandas/tests/apply/test_invalid_arg.py index 9f5157181843e..9f8611dd4b08b 100644 --- a/pandas/tests/apply/test_invalid_arg.py +++ b/pandas/tests/apply/test_invalid_arg.py @@ -338,11 +338,8 @@ def test_transform_wont_agg_series(string_series, func): # we are trying to transform with an aggregator msg = "Function did not transform" - warn = RuntimeWarning if func[0] == "sqrt" else None - warn_msg = "invalid value encountered in sqrt" with pytest.raises(ValueError, match=msg): - with tm.assert_produces_warning(warn, match=warn_msg, check_stacklevel=False): - string_series.transform(func) + string_series.transform(func) @pytest.mark.parametrize( diff --git a/pandas/tests/base/test_misc.py b/pandas/tests/base/test_misc.py index 15daca86b14ee..65e234e799353 100644 --- a/pandas/tests/base/test_misc.py +++ b/pandas/tests/base/test_misc.py @@ -132,7 +132,7 @@ def test_memory_usage_components_series(series_with_simple_index): @pytest.mark.parametrize("dtype", tm.NARROW_NP_DTYPES) def test_memory_usage_components_narrow_series(dtype): - series = tm.make_rand_series(name="a", dtype=dtype) + series = Series(range(5), dtype=dtype, index=[f"i-{i}" for i in range(5)], name="a") total_usage = series.memory_usage(index=True) non_index_usage = series.memory_usage(index=False) index_usage = series.index.memory_usage() diff --git a/pandas/tests/dtypes/test_missing.py b/pandas/tests/dtypes/test_missing.py index d008249db1a3f..88cec50c08aba 100644 --- a/pandas/tests/dtypes/test_missing.py +++ b/pandas/tests/dtypes/test_missing.py @@ -78,8 +78,6 @@ def test_notna_notnull(notna_f): @pytest.mark.parametrize( "ser", [ - tm.makeFloatSeries(), - tm.makeStringSeries(), tm.makeObjectSeries(), tm.makeTimeSeries(), Series(range(5), period_range("2020-01-01", periods=5)), diff --git a/pandas/tests/generic/test_generic.py b/pandas/tests/generic/test_generic.py index 87beab04bc586..1f08b9d5c35b8 100644 --- a/pandas/tests/generic/test_generic.py +++ b/pandas/tests/generic/test_generic.py @@ -316,7 +316,11 @@ class TestNDFrame: # tests that don't fit elsewhere @pytest.mark.parametrize( - "ser", [tm.makeFloatSeries(), tm.makeStringSeries(), tm.makeObjectSeries()] + "ser", + [ + Series(range(10), dtype=np.float64), + Series([str(i) for i in range(10)], dtype=object), + ], ) def test_squeeze_series_noop(self, ser): # noop @@ -360,14 +364,18 @@ def test_squeeze_axis_len_3(self): tm.assert_frame_equal(df.squeeze(axis=0), df) def test_numpy_squeeze(self): - s = tm.makeFloatSeries() + s = Series(range(2), dtype=np.float64) tm.assert_series_equal(np.squeeze(s), s) df = tm.makeTimeDataFrame().reindex(columns=["A"]) tm.assert_series_equal(np.squeeze(df), df["A"]) @pytest.mark.parametrize( - "ser", [tm.makeFloatSeries(), tm.makeStringSeries(), tm.makeObjectSeries()] + "ser", + [ + Series(range(10), dtype=np.float64), + Series([str(i) for i in range(10)], dtype=object), + ], ) def test_transpose_series(self, ser): # calls implementation in pandas/core/base.py @@ -393,7 +401,11 @@ def test_numpy_transpose(self, frame_or_series): np.transpose(obj, axes=1) @pytest.mark.parametrize( - "ser", [tm.makeFloatSeries(), tm.makeStringSeries(), tm.makeObjectSeries()] + "ser", + [ + Series(range(10), dtype=np.float64), + Series([str(i) for i in range(10)], dtype=object), + ], ) def test_take_series(self, ser): indices = [1, 5, -2, 6, 3, -1] diff --git a/pandas/tests/io/pytables/test_append.py b/pandas/tests/io/pytables/test_append.py index dace8435595ee..50cf7d737eb99 100644 --- a/pandas/tests/io/pytables/test_append.py +++ b/pandas/tests/io/pytables/test_append.py @@ -99,7 +99,7 @@ def test_append(setup_path): def test_append_series(setup_path): with ensure_clean_store(setup_path) as store: # basic - ss = tm.makeStringSeries() + ss = Series(range(20), dtype=np.float64, index=[f"i_{i}" for i in range(20)]) ts = tm.makeTimeSeries() ns = Series(np.arange(100)) diff --git a/pandas/tests/io/pytables/test_keys.py b/pandas/tests/io/pytables/test_keys.py index 0dcc9f7f1b9c2..fd7df29595090 100644 --- a/pandas/tests/io/pytables/test_keys.py +++ b/pandas/tests/io/pytables/test_keys.py @@ -3,6 +3,7 @@ from pandas import ( DataFrame, HDFStore, + Series, _testing as tm, ) from pandas.tests.io.pytables.common import ( @@ -16,7 +17,9 @@ def test_keys(setup_path): with ensure_clean_store(setup_path) as store: store["a"] = tm.makeTimeSeries() - store["b"] = tm.makeStringSeries() + store["b"] = Series( + range(10), dtype="float64", index=[f"i_{i}" for i in range(10)] + ) store["c"] = tm.makeDataFrame() assert len(store) == 3 diff --git a/pandas/tests/io/pytables/test_read.py b/pandas/tests/io/pytables/test_read.py index 32af61de05ee4..2030b1eca3203 100644 --- a/pandas/tests/io/pytables/test_read.py +++ b/pandas/tests/io/pytables/test_read.py @@ -356,7 +356,7 @@ def test_read_hdf_series_mode_r(tmp_path, format, setup_path): # GH 16583 # Tests that reading a Series saved to an HDF file # still works if a mode='r' argument is supplied - series = tm.makeFloatSeries() + series = Series(range(10), dtype=np.float64) path = tmp_path / setup_path series.to_hdf(path, key="data", format=format) result = read_hdf(path, key="data", mode="r") diff --git a/pandas/tests/io/pytables/test_round_trip.py b/pandas/tests/io/pytables/test_round_trip.py index 4f908f28cb5e9..6c24843f18d0d 100644 --- a/pandas/tests/io/pytables/test_round_trip.py +++ b/pandas/tests/io/pytables/test_round_trip.py @@ -36,7 +36,7 @@ def roundtrip(key, obj, **kwargs): o = tm.makeTimeSeries() tm.assert_series_equal(o, roundtrip("series", o)) - o = tm.makeStringSeries() + o = Series(range(10), dtype="float64", index=[f"i_{i}" for i in range(10)]) tm.assert_series_equal(o, roundtrip("string_series", o)) o = tm.makeDataFrame() @@ -249,7 +249,7 @@ def test_table_values_dtypes_roundtrip(setup_path): @pytest.mark.filterwarnings("ignore::pandas.errors.PerformanceWarning") def test_series(setup_path): - s = tm.makeStringSeries() + s = Series(range(10), dtype="float64", index=[f"i_{i}" for i in range(10)]) _check_roundtrip(s, tm.assert_series_equal, path=setup_path) ts = tm.makeTimeSeries() diff --git a/pandas/tests/io/pytables/test_store.py b/pandas/tests/io/pytables/test_store.py index 4624a48df18e3..96c160ab40bd8 100644 --- a/pandas/tests/io/pytables/test_store.py +++ b/pandas/tests/io/pytables/test_store.py @@ -103,7 +103,9 @@ def test_repr(setup_path): repr(store) store.info() store["a"] = tm.makeTimeSeries() - store["b"] = tm.makeStringSeries() + store["b"] = Series( + range(10), dtype="float64", index=[f"i_{i}" for i in range(10)] + ) store["c"] = tm.makeDataFrame() df = tm.makeDataFrame() diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py index 7d543d29c034d..c8b47666e1b4a 100644 --- a/pandas/tests/plotting/test_series.py +++ b/pandas/tests/plotting/test_series.py @@ -43,7 +43,9 @@ def ts(): @pytest.fixture def series(): - return tm.makeStringSeries(name="series") + return Series( + range(20), dtype=np.float64, name="series", index=[f"i_{i}" for i in range(20)] + ) class TestSeriesPlots: diff --git a/pandas/tests/reductions/test_reductions.py b/pandas/tests/reductions/test_reductions.py index 5a6d1af6257bb..9c4ae92224148 100644 --- a/pandas/tests/reductions/test_reductions.py +++ b/pandas/tests/reductions/test_reductions.py @@ -868,7 +868,7 @@ def test_idxmin_dt64index(self, unit): def test_idxmin(self): # test idxmin # _check_stat_op approach can not be used here because of isna check. - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") # add some NaNs string_series[5:15] = np.nan @@ -901,7 +901,7 @@ def test_idxmin(self): def test_idxmax(self): # test idxmax # _check_stat_op approach can not be used here because of isna check. - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") # add some NaNs string_series[5:15] = np.nan diff --git a/pandas/tests/reductions/test_stat_reductions.py b/pandas/tests/reductions/test_stat_reductions.py index 74e521ab71f41..81f560caff3fa 100644 --- a/pandas/tests/reductions/test_stat_reductions.py +++ b/pandas/tests/reductions/test_stat_reductions.py @@ -154,15 +154,15 @@ def _check_stat_op( f(string_series_, numeric_only=True) def test_sum(self): - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") self._check_stat_op("sum", np.sum, string_series, check_allna=False) def test_mean(self): - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") self._check_stat_op("mean", np.mean, string_series) def test_median(self): - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") self._check_stat_op("median", np.median, string_series) # test with integers, test failure @@ -170,19 +170,19 @@ def test_median(self): tm.assert_almost_equal(np.median(int_ts), int_ts.median()) def test_prod(self): - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") self._check_stat_op("prod", np.prod, string_series) def test_min(self): - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") self._check_stat_op("min", np.min, string_series, check_objects=True) def test_max(self): - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") self._check_stat_op("max", np.max, string_series, check_objects=True) def test_var_std(self): - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") datetime_series = tm.makeTimeSeries().rename("ts") alt = lambda x: np.std(x, ddof=1) @@ -208,7 +208,7 @@ def test_var_std(self): assert pd.isna(result) def test_sem(self): - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") datetime_series = tm.makeTimeSeries().rename("ts") alt = lambda x: np.std(x, ddof=1) / np.sqrt(len(x)) @@ -228,7 +228,7 @@ def test_sem(self): def test_skew(self): sp_stats = pytest.importorskip("scipy.stats") - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") alt = lambda x: sp_stats.skew(x, bias=False) self._check_stat_op("skew", alt, string_series) @@ -250,7 +250,7 @@ def test_skew(self): def test_kurt(self): sp_stats = pytest.importorskip("scipy.stats") - string_series = tm.makeStringSeries().rename("series") + string_series = Series(range(20), dtype=np.float64, name="series") alt = lambda x: sp_stats.kurtosis(x, bias=False) self._check_stat_op("kurt", alt, string_series) diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index 6471cd71f0860..f38a29bfe7e88 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -47,7 +47,11 @@ class TestSeriesFlexArithmetic: (lambda x: x, lambda x: x * 2, False), (lambda x: x, lambda x: x[::2], False), (lambda x: x, lambda x: 5, True), - (lambda x: tm.makeFloatSeries(), lambda x: tm.makeFloatSeries(), True), + ( + lambda x: Series(range(10), dtype=np.float64), + lambda x: Series(range(10), dtype=np.float64), + True, + ), ], ) @pytest.mark.parametrize( diff --git a/pandas/tests/series/test_unary.py b/pandas/tests/series/test_unary.py index ad0e344fa4420..8f153788e413c 100644 --- a/pandas/tests/series/test_unary.py +++ b/pandas/tests/series/test_unary.py @@ -8,13 +8,11 @@ class TestSeriesUnaryOps: # __neg__, __pos__, __invert__ def test_neg(self): - ser = tm.makeStringSeries() - ser.name = "series" + ser = Series(range(5), dtype="float64", name="series") tm.assert_series_equal(-ser, -1 * ser) def test_invert(self): - ser = tm.makeStringSeries() - ser.name = "series" + ser = Series(range(5), dtype="float64", name="series") tm.assert_series_equal(-(ser < 0), ~(ser < 0)) @pytest.mark.parametrize( From 762b61dfb3028bc3b9c96c664243ba188b0ed923 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Sun, 26 Nov 2023 19:36:13 +0100 Subject: [PATCH 22/30] Adjust tests in util folder for new string option (#56181) --- pandas/tests/util/test_assert_frame_equal.py | 20 +++++++++----- pandas/tests/util/test_assert_index_equal.py | 12 ++++++--- pandas/tests/util/test_assert_series_equal.py | 26 ++++++++++++++----- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/pandas/tests/util/test_assert_frame_equal.py b/pandas/tests/util/test_assert_frame_equal.py index 2d3b47cd2e994..0c93ee453bb1c 100644 --- a/pandas/tests/util/test_assert_frame_equal.py +++ b/pandas/tests/util/test_assert_frame_equal.py @@ -109,12 +109,16 @@ def test_empty_dtypes(check_dtype): @pytest.mark.parametrize("check_like", [True, False]) -def test_frame_equal_index_mismatch(check_like, obj_fixture): +def test_frame_equal_index_mismatch(check_like, obj_fixture, using_infer_string): + if using_infer_string: + dtype = "string" + else: + dtype = "object" msg = f"""{obj_fixture}\\.index are different {obj_fixture}\\.index values are different \\(33\\.33333 %\\) -\\[left\\]: Index\\(\\['a', 'b', 'c'\\], dtype='object'\\) -\\[right\\]: Index\\(\\['a', 'b', 'd'\\], dtype='object'\\) +\\[left\\]: Index\\(\\['a', 'b', 'c'\\], dtype='{dtype}'\\) +\\[right\\]: Index\\(\\['a', 'b', 'd'\\], dtype='{dtype}'\\) At positional index 2, first diff: c != d""" df1 = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}, index=["a", "b", "c"]) @@ -125,12 +129,16 @@ def test_frame_equal_index_mismatch(check_like, obj_fixture): @pytest.mark.parametrize("check_like", [True, False]) -def test_frame_equal_columns_mismatch(check_like, obj_fixture): +def test_frame_equal_columns_mismatch(check_like, obj_fixture, using_infer_string): + if using_infer_string: + dtype = "string" + else: + dtype = "object" msg = f"""{obj_fixture}\\.columns are different {obj_fixture}\\.columns values are different \\(50\\.0 %\\) -\\[left\\]: Index\\(\\['A', 'B'\\], dtype='object'\\) -\\[right\\]: Index\\(\\['A', 'b'\\], dtype='object'\\)""" +\\[left\\]: Index\\(\\['A', 'B'\\], dtype='{dtype}'\\) +\\[right\\]: Index\\(\\['A', 'b'\\], dtype='{dtype}'\\)""" df1 = DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}, index=["a", "b", "c"]) df2 = DataFrame({"A": [1, 2, 3], "b": [4, 5, 6]}, index=["a", "b", "c"]) diff --git a/pandas/tests/util/test_assert_index_equal.py b/pandas/tests/util/test_assert_index_equal.py index 15263db9ec645..dc6efdcec380e 100644 --- a/pandas/tests/util/test_assert_index_equal.py +++ b/pandas/tests/util/test_assert_index_equal.py @@ -205,14 +205,18 @@ def test_index_equal_names(name1, name2): tm.assert_index_equal(idx1, idx2) -def test_index_equal_category_mismatch(check_categorical): - msg = """Index are different +def test_index_equal_category_mismatch(check_categorical, using_infer_string): + if using_infer_string: + dtype = "string" + else: + dtype = "object" + msg = f"""Index are different Attribute "dtype" are different \\[left\\]: CategoricalDtype\\(categories=\\['a', 'b'\\], ordered=False, \ -categories_dtype=object\\) +categories_dtype={dtype}\\) \\[right\\]: CategoricalDtype\\(categories=\\['a', 'b', 'c'\\], \ -ordered=False, categories_dtype=object\\)""" +ordered=False, categories_dtype={dtype}\\)""" idx1 = Index(Categorical(["a", "b"])) idx2 = Index(Categorical(["a", "b"], categories=["a", "b", "c"])) diff --git a/pandas/tests/util/test_assert_series_equal.py b/pandas/tests/util/test_assert_series_equal.py index 12b5987cdb3de..ffc5e105d6f2f 100644 --- a/pandas/tests/util/test_assert_series_equal.py +++ b/pandas/tests/util/test_assert_series_equal.py @@ -214,8 +214,18 @@ def test_series_equal_numeric_values_mismatch(rtol): tm.assert_series_equal(s1, s2, rtol=rtol) -def test_series_equal_categorical_values_mismatch(rtol): - msg = """Series are different +def test_series_equal_categorical_values_mismatch(rtol, using_infer_string): + if using_infer_string: + msg = """Series are different + +Series values are different \\(66\\.66667 %\\) +\\[index\\]: \\[0, 1, 2\\] +\\[left\\]: \\['a', 'b', 'c'\\] +Categories \\(3, string\\): \\[a, b, c\\] +\\[right\\]: \\['a', 'c', 'b'\\] +Categories \\(3, string\\): \\[a, b, c\\]""" + else: + msg = """Series are different Series values are different \\(66\\.66667 %\\) \\[index\\]: \\[0, 1, 2\\] @@ -246,14 +256,18 @@ def test_series_equal_datetime_values_mismatch(rtol): tm.assert_series_equal(s1, s2, rtol=rtol) -def test_series_equal_categorical_mismatch(check_categorical): - msg = """Attributes of Series are different +def test_series_equal_categorical_mismatch(check_categorical, using_infer_string): + if using_infer_string: + dtype = "string" + else: + dtype = "object" + msg = f"""Attributes of Series are different Attribute "dtype" are different \\[left\\]: CategoricalDtype\\(categories=\\['a', 'b'\\], ordered=False, \ -categories_dtype=object\\) +categories_dtype={dtype}\\) \\[right\\]: CategoricalDtype\\(categories=\\['a', 'b', 'c'\\], \ -ordered=False, categories_dtype=object\\)""" +ordered=False, categories_dtype={dtype}\\)""" s1 = Series(Categorical(["a", "b"])) s2 = Series(Categorical(["a", "b"], categories=list("abc"))) From 52ddb2e208be3103f0b47bfabad2a66805b34996 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Mon, 27 Nov 2023 03:31:23 +0100 Subject: [PATCH 23/30] Adjust tests in xml folder for new string option (#56198) --- pandas/tests/io/xml/test_xml.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pandas/tests/io/xml/test_xml.py b/pandas/tests/io/xml/test_xml.py index 88655483800ee..e4456b0a78e06 100644 --- a/pandas/tests/io/xml/test_xml.py +++ b/pandas/tests/io/xml/test_xml.py @@ -32,6 +32,7 @@ ArrowStringArray, StringArray, ) +from pandas.core.arrays.string_arrow import ArrowStringArrayNumpySemantics from pandas.io.common import get_handle from pandas.io.xml import read_xml @@ -2004,7 +2005,9 @@ def test_s3_parser_consistency(s3_public_bucket_with_data, s3so): tm.assert_frame_equal(df_lxml, df_etree) -def test_read_xml_nullable_dtypes(parser, string_storage, dtype_backend): +def test_read_xml_nullable_dtypes( + parser, string_storage, dtype_backend, using_infer_string +): # GH#50500 data = """ @@ -2032,7 +2035,12 @@ def test_read_xml_nullable_dtypes(parser, string_storage, dtype_backend): """ - if string_storage == "python": + if using_infer_string: + pa = pytest.importorskip("pyarrow") + string_array = ArrowStringArrayNumpySemantics(pa.array(["x", "y"])) + string_array_na = ArrowStringArrayNumpySemantics(pa.array(["x", None])) + + elif string_storage == "python": string_array = StringArray(np.array(["x", "y"], dtype=np.object_)) string_array_na = StringArray(np.array(["x", NA], dtype=np.object_)) From 4b6a80ea9f7547dbfe016137ecf91ad60ad305bc Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Mon, 27 Nov 2023 03:32:58 +0100 Subject: [PATCH 24/30] DOC: reoder whatsnew enhancements (#56196) --- doc/source/whatsnew/v2.2.0.rst | 150 ++++++++++++++++----------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index d252c19a95d4a..6bd20ace44b65 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -14,81 +14,6 @@ including other versions of pandas. Enhancements ~~~~~~~~~~~~ -.. _whatsnew_220.enhancements.calamine: - -Calamine engine for :func:`read_excel` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``calamine`` engine was added to :func:`read_excel`. -It uses ``python-calamine``, which provides Python bindings for the Rust library `calamine `__. -This engine supports Excel files (``.xlsx``, ``.xlsm``, ``.xls``, ``.xlsb``) and OpenDocument spreadsheets (``.ods``) (:issue:`50395`). - -There are two advantages of this engine: - -1. Calamine is often faster than other engines, some benchmarks show results up to 5x faster than 'openpyxl', 20x - 'odf', 4x - 'pyxlsb', and 1.5x - 'xlrd'. - But, 'openpyxl' and 'pyxlsb' are faster in reading a few rows from large files because of lazy iteration over rows. -2. Calamine supports the recognition of datetime in ``.xlsb`` files, unlike 'pyxlsb' which is the only other engine in pandas that can read ``.xlsb`` files. - -.. code-block:: python - - pd.read_excel("path_to_file.xlsb", engine="calamine") - - -For more, see :ref:`io.calamine` in the user guide on IO tools. - -.. _whatsnew_220.enhancements.struct_accessor: - -Series.struct accessor to with PyArrow structured data -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``Series.struct`` accessor provides attributes and methods for processing -data with ``struct[pyarrow]`` dtype Series. For example, -:meth:`Series.struct.explode` converts PyArrow structured data to a pandas -DataFrame. (:issue:`54938`) - -.. ipython:: python - - import pyarrow as pa - series = pd.Series( - [ - {"project": "pandas", "version": "2.2.0"}, - {"project": "numpy", "version": "1.25.2"}, - {"project": "pyarrow", "version": "13.0.0"}, - ], - dtype=pd.ArrowDtype( - pa.struct([ - ("project", pa.string()), - ("version", pa.string()), - ]) - ), - ) - series.struct.explode() - -.. _whatsnew_220.enhancements.list_accessor: - -Series.list accessor for PyArrow list data -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``Series.list`` accessor provides attributes and methods for processing -data with ``list[pyarrow]`` dtype Series. For example, -:meth:`Series.list.__getitem__` allows indexing pyarrow lists in -a Series. (:issue:`55323`) - -.. ipython:: python - - import pyarrow as pa - series = pd.Series( - [ - [1, 2, 3], - [4, 5], - [6], - ], - dtype=pd.ArrowDtype( - pa.list_(pa.int64()) - ), - ) - series.list[0] - .. _whatsnew_220.enhancements.adbc_support: ADBC Driver support in to_sql and read_sql @@ -180,6 +105,81 @@ For a full list of ADBC drivers and their development status, see the `ADBC Driv Implementation Status `_ documentation. +.. _whatsnew_220.enhancements.struct_accessor: + +Series.struct accessor to with PyArrow structured data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``Series.struct`` accessor provides attributes and methods for processing +data with ``struct[pyarrow]`` dtype Series. For example, +:meth:`Series.struct.explode` converts PyArrow structured data to a pandas +DataFrame. (:issue:`54938`) + +.. ipython:: python + + import pyarrow as pa + series = pd.Series( + [ + {"project": "pandas", "version": "2.2.0"}, + {"project": "numpy", "version": "1.25.2"}, + {"project": "pyarrow", "version": "13.0.0"}, + ], + dtype=pd.ArrowDtype( + pa.struct([ + ("project", pa.string()), + ("version", pa.string()), + ]) + ), + ) + series.struct.explode() + +.. _whatsnew_220.enhancements.list_accessor: + +Series.list accessor for PyArrow list data +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``Series.list`` accessor provides attributes and methods for processing +data with ``list[pyarrow]`` dtype Series. For example, +:meth:`Series.list.__getitem__` allows indexing pyarrow lists in +a Series. (:issue:`55323`) + +.. ipython:: python + + import pyarrow as pa + series = pd.Series( + [ + [1, 2, 3], + [4, 5], + [6], + ], + dtype=pd.ArrowDtype( + pa.list_(pa.int64()) + ), + ) + series.list[0] + +.. _whatsnew_220.enhancements.calamine: + +Calamine engine for :func:`read_excel` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``calamine`` engine was added to :func:`read_excel`. +It uses ``python-calamine``, which provides Python bindings for the Rust library `calamine `__. +This engine supports Excel files (``.xlsx``, ``.xlsm``, ``.xls``, ``.xlsb``) and OpenDocument spreadsheets (``.ods``) (:issue:`50395`). + +There are two advantages of this engine: + +1. Calamine is often faster than other engines, some benchmarks show results up to 5x faster than 'openpyxl', 20x - 'odf', 4x - 'pyxlsb', and 1.5x - 'xlrd'. + But, 'openpyxl' and 'pyxlsb' are faster in reading a few rows from large files because of lazy iteration over rows. +2. Calamine supports the recognition of datetime in ``.xlsb`` files, unlike 'pyxlsb' which is the only other engine in pandas that can read ``.xlsb`` files. + +.. code-block:: python + + pd.read_excel("path_to_file.xlsb", engine="calamine") + + +For more, see :ref:`io.calamine` in the user guide on IO tools. + .. _whatsnew_220.enhancements.other: Other enhancements From 3fc83203910aa7196d88850ab5becea41e30f6d7 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Mon, 27 Nov 2023 03:43:04 +0100 Subject: [PATCH 25/30] BUG: hdf can't deal with ea dtype columns (#56194) --- doc/source/whatsnew/v2.1.4.rst | 1 + pandas/io/pytables.py | 3 +-- pandas/tests/io/pytables/test_round_trip.py | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v2.1.4.rst b/doc/source/whatsnew/v2.1.4.rst index 77ce303dc1bfe..0cbf211305d12 100644 --- a/doc/source/whatsnew/v2.1.4.rst +++ b/doc/source/whatsnew/v2.1.4.rst @@ -24,6 +24,7 @@ Bug fixes - Bug in :class:`Series` constructor raising DeprecationWarning when ``index`` is a list of :class:`Series` (:issue:`55228`) - Bug in :meth:`Index.__getitem__` returning wrong result for Arrow dtypes and negative stepsize (:issue:`55832`) - Fixed bug in :meth:`DataFrame.__setitem__` casting :class:`Index` with object-dtype to PyArrow backed strings when ``infer_string`` option is set (:issue:`55638`) +- Fixed bug in :meth:`DataFrame.to_hdf` raising when columns have ``StringDtype`` (:issue:`55088`) - Fixed bug in :meth:`Index.insert` casting object-dtype to PyArrow backed strings when ``infer_string`` option is set (:issue:`55638`) - Fixed bug in :meth:`Series.str.translate` losing object dtype when string option is set (:issue:`56152`) diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index 9e0e3686e4aa2..50611197ad7dd 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -57,7 +57,6 @@ is_bool_dtype, is_complex_dtype, is_list_like, - is_object_dtype, is_string_dtype, needs_i8_conversion, ) @@ -2647,7 +2646,7 @@ class DataIndexableCol(DataCol): is_data_indexable = True def validate_names(self) -> None: - if not is_object_dtype(Index(self.values).dtype): + if not is_string_dtype(Index(self.values).dtype): # TODO: should the message here be more specifically non-str? raise ValueError("cannot have non-object label DataIndexableCol") diff --git a/pandas/tests/io/pytables/test_round_trip.py b/pandas/tests/io/pytables/test_round_trip.py index 6c24843f18d0d..baac90e52e962 100644 --- a/pandas/tests/io/pytables/test_round_trip.py +++ b/pandas/tests/io/pytables/test_round_trip.py @@ -531,3 +531,18 @@ def test_round_trip_equals(tmp_path, setup_path): tm.assert_frame_equal(df, other) assert df.equals(other) assert other.equals(df) + + +def test_infer_string_columns(tmp_path, setup_path): + # GH# + pytest.importorskip("pyarrow") + path = tmp_path / setup_path + with pd.option_context("future.infer_string", True): + df = DataFrame(1, columns=list("ABCD"), index=list(range(10))).set_index( + ["A", "B"] + ) + expected = df.copy() + df.to_hdf(path, key="df", format="table") + + result = read_hdf(path, "df") + tm.assert_frame_equal(result, expected) From 82c591d3ff2d49cb4605a6b70388f1bd4a20f9ab Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Mon, 27 Nov 2023 03:44:41 +0100 Subject: [PATCH 26/30] Adjust test in excel folder for new string option (#56193) --- pandas/tests/io/excel/test_readers.py | 5 +++++ pandas/tests/io/excel/test_writers.py | 13 +++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pandas/tests/io/excel/test_readers.py b/pandas/tests/io/excel/test_readers.py index abbdb77efad0e..caa2da1b6123b 100644 --- a/pandas/tests/io/excel/test_readers.py +++ b/pandas/tests/io/excel/test_readers.py @@ -14,6 +14,8 @@ import numpy as np import pytest +from pandas._config import using_pyarrow_string_dtype + import pandas.util._test_decorators as td import pandas as pd @@ -637,6 +639,9 @@ def test_dtype_backend_and_dtype(self, read_ext): ) tm.assert_frame_equal(result, df) + @pytest.mark.xfail( + using_pyarrow_string_dtype(), reason="infer_string takes precedence" + ) def test_dtype_backend_string(self, read_ext, string_storage): # GH#36712 if read_ext in (".xlsb", ".xls"): diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 22cd0621fd4c4..507d7ed4bf9d0 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -709,7 +709,7 @@ def test_excel_date_datetime_format(self, ext, path): # we need to use df_expected to check the result. tm.assert_frame_equal(rs2, df_expected) - def test_to_excel_interval_no_labels(self, path): + def test_to_excel_interval_no_labels(self, path, using_infer_string): # see gh-19242 # # Test writing Interval without labels. @@ -719,7 +719,9 @@ def test_to_excel_interval_no_labels(self, path): expected = df.copy() df["new"] = pd.cut(df[0], 10) - expected["new"] = pd.cut(expected[0], 10).astype(str) + expected["new"] = pd.cut(expected[0], 10).astype( + str if not using_infer_string else "string[pyarrow_numpy]" + ) df.to_excel(path, sheet_name="test1") with ExcelFile(path) as reader: @@ -1213,10 +1215,9 @@ def test_render_as_column_name(self, path): def test_true_and_false_value_options(self, path): # see gh-13347 - df = DataFrame([["foo", "bar"]], columns=["col1", "col2"]) - msg = "Downcasting behavior in `replace`" - with tm.assert_produces_warning(FutureWarning, match=msg): - expected = df.replace({"foo": True, "bar": False}) + df = DataFrame([["foo", "bar"]], columns=["col1", "col2"], dtype=object) + with option_context("future.no_silent_downcasting", True): + expected = df.replace({"foo": True, "bar": False}).astype("bool") df.to_excel(path) read_frame = pd.read_excel( From 27ec8873be0c5958c5cf7c76a32180da31bd56cf Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Mon, 27 Nov 2023 03:52:43 +0100 Subject: [PATCH 27/30] BUG: numba raises for string columns or index (#56189) --- doc/source/whatsnew/v2.2.0.rst | 2 +- pandas/core/apply.py | 12 +++++++++--- pandas/tests/apply/test_numba.py | 19 ++++++++++++++++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 6bd20ace44b65..9680ccf409564 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -496,8 +496,8 @@ Conversion Strings ^^^^^^^ - Bug in :func:`pandas.api.types.is_string_dtype` while checking object array with no elements is of the string dtype (:issue:`54661`) +- Bug in :meth:`DataFrame.apply` failing when ``engine="numba"`` and columns or index have ``StringDtype`` (:issue:`56189`) - Bug in :meth:`Series.str.startswith` and :meth:`Series.str.endswith` with arguments of type ``tuple[str, ...]`` for ``string[pyarrow]`` (:issue:`54942`) -- Interval ^^^^^^^^ diff --git a/pandas/core/apply.py b/pandas/core/apply.py index 3b79882d3c762..bb3cc3a03760f 100644 --- a/pandas/core/apply.py +++ b/pandas/core/apply.py @@ -1172,11 +1172,17 @@ def apply_with_numba(self) -> dict[int, Any]: ) from pandas.core._numba.extensions import set_numba_data + index = self.obj.index + if index.dtype == "string": + index = index.astype(object) + + columns = self.obj.columns + if columns.dtype == "string": + columns = columns.astype(object) + # Convert from numba dict to regular dict # Our isinstance checks in the df constructor don't pass for numbas typed dict - with set_numba_data(self.obj.index) as index, set_numba_data( - self.columns - ) as columns: + with set_numba_data(index) as index, set_numba_data(columns) as columns: res = dict(nb_func(self.values, columns, index)) return res diff --git a/pandas/tests/apply/test_numba.py b/pandas/tests/apply/test_numba.py index ee239568d057d..85d7baee1bdf5 100644 --- a/pandas/tests/apply/test_numba.py +++ b/pandas/tests/apply/test_numba.py @@ -24,6 +24,22 @@ def test_numba_vs_python_noop(float_frame, apply_axis): tm.assert_frame_equal(result, expected) +def test_numba_vs_python_string_index(): + # GH#56189 + pytest.importorskip("pyarrow") + df = DataFrame( + 1, + index=Index(["a", "b"], dtype="string[pyarrow_numpy]"), + columns=Index(["x", "y"], dtype="string[pyarrow_numpy]"), + ) + func = lambda x: x + result = df.apply(func, engine="numba", axis=0) + expected = df.apply(func, engine="python", axis=0) + tm.assert_frame_equal( + result, expected, check_column_type=False, check_index_type=False + ) + + def test_numba_vs_python_indexing(): frame = DataFrame( {"a": [1, 2, 3], "b": [4, 5, 6], "c": [7.0, 8.0, 9.0]}, @@ -88,7 +104,8 @@ def test_numba_unsupported_dtypes(apply_axis): df["c"] = df["c"].astype("double[pyarrow]") with pytest.raises( - ValueError, match="Column b must have a numeric dtype. Found 'object' instead" + ValueError, + match="Column b must have a numeric dtype. Found 'object|string' instead", ): df.apply(f, engine="numba", axis=apply_axis) From a38ecd5b84a96ae1453e889f3dfebb0d218a575c Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Sun, 26 Nov 2023 16:59:21 -1000 Subject: [PATCH 28/30] TST/CLN: Remove makeStringIndex (#56155) * TST/CLN: Remove makeStringIndex * Fix failures * Fix one more test * Remove makeBoolIndex too * Remove name * Adjust test * Fix benchmarks * Fix another benchmark --- asv_bench/benchmarks/algorithms.py | 15 +++--- asv_bench/benchmarks/algos/isin.py | 6 +-- asv_bench/benchmarks/ctors.py | 4 +- asv_bench/benchmarks/dtypes.py | 9 ++-- asv_bench/benchmarks/frame_ctor.py | 6 +-- asv_bench/benchmarks/frame_methods.py | 11 ++-- asv_bench/benchmarks/gil.py | 6 +-- asv_bench/benchmarks/groupby.py | 12 +++-- asv_bench/benchmarks/index_object.py | 11 ++-- asv_bench/benchmarks/indexing.py | 8 ++- asv_bench/benchmarks/inference.py | 8 ++- asv_bench/benchmarks/io/csv.py | 8 ++- asv_bench/benchmarks/io/excel.py | 5 +- asv_bench/benchmarks/io/hdf.py | 10 ++-- asv_bench/benchmarks/io/json.py | 10 ++-- asv_bench/benchmarks/io/pickle.py | 8 ++- asv_bench/benchmarks/io/sql.py | 11 ++-- asv_bench/benchmarks/io/stata.py | 8 ++- asv_bench/benchmarks/join_merge.py | 16 +++--- asv_bench/benchmarks/libs.py | 10 ++-- asv_bench/benchmarks/multiindex_object.py | 17 ++++--- asv_bench/benchmarks/reindex.py | 16 +++--- asv_bench/benchmarks/series_methods.py | 4 +- asv_bench/benchmarks/strings.py | 22 +++++--- pandas/_testing/__init__.py | 26 ++-------- pandas/conftest.py | 10 ++-- pandas/tests/arithmetic/test_numeric.py | 2 +- pandas/tests/arithmetic/test_object.py | 4 +- .../frame/methods/test_first_valid_index.py | 22 ++++---- pandas/tests/frame/test_constructors.py | 2 +- pandas/tests/frame/test_repr.py | 2 +- pandas/tests/groupby/test_grouping.py | 17 +++---- pandas/tests/indexes/multi/test_duplicates.py | 2 +- pandas/tests/indexing/test_floats.py | 50 +++++++++---------- pandas/tests/io/formats/test_format.py | 17 ++++--- pandas/tests/io/formats/test_to_html.py | 2 +- pandas/tests/io/formats/test_to_string.py | 2 +- pandas/tests/io/pytables/test_put.py | 25 ++++------ pandas/tests/io/pytables/test_round_trip.py | 11 ++-- pandas/tests/io/pytables/test_store.py | 24 ++++----- pandas/tests/reductions/test_reductions.py | 4 +- pandas/tests/resample/test_time_grouper.py | 16 +++--- pandas/tests/reshape/merge/test_multi.py | 2 +- .../series/methods/test_combine_first.py | 4 +- pandas/tests/series/test_api.py | 2 +- pandas/tests/test_algos.py | 34 ++++++------- .../tseries/frequencies/test_inference.py | 2 +- pandas/tests/util/test_hashing.py | 4 +- 48 files changed, 253 insertions(+), 274 deletions(-) diff --git a/asv_bench/benchmarks/algorithms.py b/asv_bench/benchmarks/algorithms.py index 3c78b0a9a60c8..933e8fbc175d8 100644 --- a/asv_bench/benchmarks/algorithms.py +++ b/asv_bench/benchmarks/algorithms.py @@ -4,8 +4,6 @@ import pandas as pd -from .pandas_vb_common import tm - for imp in ["pandas.util", "pandas.tools.hashing"]: try: hashing = import_module(imp) @@ -47,9 +45,12 @@ def setup(self, unique, sort, dtype): elif dtype == "datetime64[ns, tz]": data = pd.date_range("2011-01-01", freq="h", periods=N, tz="Asia/Tokyo") elif dtype == "object_str": - data = tm.makeStringIndex(N) + data = pd.Index([f"i-{i}" for i in range(N)], dtype=object) elif dtype == "string[pyarrow]": - data = pd.array(tm.makeStringIndex(N), dtype="string[pyarrow]") + data = pd.array( + pd.Index([f"i-{i}" for i in range(N)], dtype=object), + dtype="string[pyarrow]", + ) else: raise NotImplementedError @@ -88,7 +89,7 @@ def setup(self, unique, keep, dtype): elif dtype == "float64": data = pd.Index(np.random.randn(N), dtype="float64") elif dtype == "string": - data = tm.makeStringIndex(N) + data = pd.Index([f"i-{i}" for i in range(N)], dtype=object) elif dtype == "datetime64[ns]": data = pd.date_range("2011-01-01", freq="h", periods=N) elif dtype == "datetime64[ns, tz]": @@ -136,7 +137,9 @@ def setup_cache(self): df = pd.DataFrame( { "strings": pd.Series( - tm.makeStringIndex(10000).take(np.random.randint(0, 10000, size=N)) + pd.Index([f"i-{i}" for i in range(10000)], dtype=object).take( + np.random.randint(0, 10000, size=N) + ) ), "floats": np.random.randn(N), "ints": np.arange(N), diff --git a/asv_bench/benchmarks/algos/isin.py b/asv_bench/benchmarks/algos/isin.py index 92797425b2c30..2b3d32fb579dc 100644 --- a/asv_bench/benchmarks/algos/isin.py +++ b/asv_bench/benchmarks/algos/isin.py @@ -8,8 +8,6 @@ date_range, ) -from ..pandas_vb_common import tm - class IsIn: params = [ @@ -60,7 +58,9 @@ def setup(self, dtype): elif dtype in ["str", "string[python]", "string[pyarrow]"]: try: - self.series = Series(tm.makeStringIndex(N), dtype=dtype) + self.series = Series( + Index([f"i-{i}" for i in range(N)], dtype=object), dtype=dtype + ) except ImportError: raise NotImplementedError self.values = list(self.series[:2]) diff --git a/asv_bench/benchmarks/ctors.py b/asv_bench/benchmarks/ctors.py index 2db00cc7f2ad9..77c9faf3d3a87 100644 --- a/asv_bench/benchmarks/ctors.py +++ b/asv_bench/benchmarks/ctors.py @@ -9,8 +9,6 @@ date_range, ) -from .pandas_vb_common import tm - def no_change(arr): return arr @@ -115,7 +113,7 @@ def time_dtindex_from_index_with_series(self): class MultiIndexConstructor: def setup(self): N = 10**4 - self.iterables = [tm.makeStringIndex(N), range(20)] + self.iterables = [Index([f"i-{i}" for i in range(N)], dtype=object), range(20)] def time_multiindex_from_iterables(self): MultiIndex.from_product(self.iterables) diff --git a/asv_bench/benchmarks/dtypes.py b/asv_bench/benchmarks/dtypes.py index c33043c0eddc1..7f3429b5e3882 100644 --- a/asv_bench/benchmarks/dtypes.py +++ b/asv_bench/benchmarks/dtypes.py @@ -3,7 +3,10 @@ import numpy as np import pandas as pd -from pandas import DataFrame +from pandas import ( + DataFrame, + Index, +) import pandas._testing as tm from pandas.api.types import ( is_extension_array_dtype, @@ -73,8 +76,8 @@ class SelectDtypes: def setup(self, dtype): N, K = 5000, 50 - self.index = tm.makeStringIndex(N) - self.columns = tm.makeStringIndex(K) + self.index = Index([f"i-{i}" for i in range(N)], dtype=object) + self.columns = Index([f"i-{i}" for i in range(K)], dtype=object) def create_df(data): return DataFrame(data, index=self.index, columns=self.columns) diff --git a/asv_bench/benchmarks/frame_ctor.py b/asv_bench/benchmarks/frame_ctor.py index 7092a679b8cf0..f938f7eb0d951 100644 --- a/asv_bench/benchmarks/frame_ctor.py +++ b/asv_bench/benchmarks/frame_ctor.py @@ -12,8 +12,6 @@ date_range, ) -from .pandas_vb_common import tm - try: from pandas.tseries.offsets import ( Hour, @@ -30,8 +28,8 @@ class FromDicts: def setup(self): N, K = 5000, 50 - self.index = tm.makeStringIndex(N) - self.columns = tm.makeStringIndex(K) + self.index = pd.Index([f"i-{i}" for i in range(N)], dtype=object) + self.columns = pd.Index([f"i-{i}" for i in range(K)], dtype=object) frame = DataFrame(np.random.randn(N, K), index=self.index, columns=self.columns) self.data = frame.to_dict() self.dict_list = frame.to_dict(orient="records") diff --git a/asv_bench/benchmarks/frame_methods.py b/asv_bench/benchmarks/frame_methods.py index f22a261041e17..a283afd1f0f1e 100644 --- a/asv_bench/benchmarks/frame_methods.py +++ b/asv_bench/benchmarks/frame_methods.py @@ -5,6 +5,7 @@ from pandas import ( DataFrame, + Index, MultiIndex, NaT, Series, @@ -14,8 +15,6 @@ timedelta_range, ) -from .pandas_vb_common import tm - class AsType: params = [ @@ -703,8 +702,12 @@ def setup(self, monotonic): K = 10 df = DataFrame( { - "key1": tm.makeStringIndex(N).values.repeat(K), - "key2": tm.makeStringIndex(N).values.repeat(K), + "key1": Index([f"i-{i}" for i in range(N)], dtype=object).values.repeat( + K + ), + "key2": Index([f"i-{i}" for i in range(N)], dtype=object).values.repeat( + K + ), "value": np.random.randn(N * K), } ) diff --git a/asv_bench/benchmarks/gil.py b/asv_bench/benchmarks/gil.py index c78819f75c52a..a0c4189c72d0e 100644 --- a/asv_bench/benchmarks/gil.py +++ b/asv_bench/benchmarks/gil.py @@ -5,6 +5,7 @@ from pandas import ( DataFrame, + Index, Series, date_range, factorize, @@ -12,8 +13,6 @@ ) from pandas.core.algorithms import take_nd -from .pandas_vb_common import tm - try: from pandas import ( rolling_kurt, @@ -34,7 +33,6 @@ except ImportError: from pandas import algos - from .pandas_vb_common import BaseIO # isort:skip @@ -305,7 +303,7 @@ class ParallelFactorize: param_names = ["threads"] def setup(self, threads): - strings = tm.makeStringIndex(100000) + strings = Index([f"i-{i}" for i in range(100000)], dtype=object) @test_parallel(num_threads=threads) def parallel(): diff --git a/asv_bench/benchmarks/groupby.py b/asv_bench/benchmarks/groupby.py index 202ba6e981b70..abffa1f702b9c 100644 --- a/asv_bench/benchmarks/groupby.py +++ b/asv_bench/benchmarks/groupby.py @@ -17,8 +17,6 @@ to_timedelta, ) -from .pandas_vb_common import tm - method_blocklist = { "object": { "diff", @@ -167,10 +165,14 @@ def setup_cache(self): "int64_small": Series(np.random.randint(0, 100, size=size)), "int64_large": Series(np.random.randint(0, 10000, size=size)), "object_small": Series( - tm.makeStringIndex(100).take(np.random.randint(0, 100, size=size)) + Index([f"i-{i}" for i in range(100)], dtype=object).take( + np.random.randint(0, 100, size=size) + ) ), "object_large": Series( - tm.makeStringIndex(10000).take(np.random.randint(0, 10000, size=size)) + Index([f"i-{i}" for i in range(10000)], dtype=object).take( + np.random.randint(0, 10000, size=size) + ) ), } return data @@ -912,7 +914,7 @@ def setup(self): n1 = 400 n2 = 250 index = MultiIndex( - levels=[np.arange(n1), tm.makeStringIndex(n2)], + levels=[np.arange(n1), Index([f"i-{i}" for i in range(n2)], dtype=object)], codes=[np.repeat(range(n1), n2).tolist(), list(range(n2)) * n1], names=["lev1", "lev2"], ) diff --git a/asv_bench/benchmarks/index_object.py b/asv_bench/benchmarks/index_object.py index 7e33223260e0f..637b1b40f59a3 100644 --- a/asv_bench/benchmarks/index_object.py +++ b/asv_bench/benchmarks/index_object.py @@ -12,8 +12,6 @@ date_range, ) -from .pandas_vb_common import tm - class SetOperations: params = ( @@ -30,7 +28,7 @@ def setup(self, index_structure, dtype, method): date_str_left = Index(dates_left.strftime(fmt)) int_left = Index(np.arange(N)) ea_int_left = Index(np.arange(N), dtype="Int64") - str_left = tm.makeStringIndex(N) + str_left = Index([f"i-{i}" for i in range(N)], dtype=object) data = { "datetime": dates_left, @@ -155,7 +153,12 @@ class Indexing: def setup(self, dtype): N = 10**6 - self.idx = getattr(tm, f"make{dtype}Index")(N) + if dtype == "String": + self.idx = Index([f"i-{i}" for i in range(N)], dtype=object) + elif dtype == "Float": + self.idx = Index(np.arange(N), dtype=np.float64) + elif dtype == "Int": + self.idx = Index(np.arange(N), dtype=np.int64) self.array_mask = (np.arange(N) % 3) == 0 self.series_mask = Series(self.array_mask) self.sorted = self.idx.sort_values() diff --git a/asv_bench/benchmarks/indexing.py b/asv_bench/benchmarks/indexing.py index 4961722c0e9cd..9ad1f5b31016d 100644 --- a/asv_bench/benchmarks/indexing.py +++ b/asv_bench/benchmarks/indexing.py @@ -22,8 +22,6 @@ period_range, ) -from .pandas_vb_common import tm - class NumericSeriesIndexing: params = [ @@ -124,7 +122,7 @@ class NonNumericSeriesIndexing: def setup(self, index, index_structure): N = 10**6 if index == "string": - index = tm.makeStringIndex(N) + index = Index([f"i-{i}" for i in range(N)], dtype=object) elif index == "datetime": index = date_range("1900", periods=N, freq="s") elif index == "period": @@ -156,8 +154,8 @@ def time_getitem_list_like(self, index, index_structure): class DataFrameStringIndexing: def setup(self): - index = tm.makeStringIndex(1000) - columns = tm.makeStringIndex(30) + index = Index([f"i-{i}" for i in range(1000)], dtype=object) + columns = Index([f"i-{i}" for i in range(30)], dtype=object) with warnings.catch_warnings(record=True): self.df = DataFrame(np.random.randn(1000, 30), index=index, columns=columns) self.idx_scalar = index[100] diff --git a/asv_bench/benchmarks/inference.py b/asv_bench/benchmarks/inference.py index 805b0c807452c..d5c58033c1157 100644 --- a/asv_bench/benchmarks/inference.py +++ b/asv_bench/benchmarks/inference.py @@ -9,6 +9,7 @@ import numpy as np from pandas import ( + Index, NaT, Series, date_range, @@ -17,10 +18,7 @@ to_timedelta, ) -from .pandas_vb_common import ( - lib, - tm, -) +from .pandas_vb_common import lib class ToNumeric: @@ -31,7 +29,7 @@ def setup(self, errors): N = 10000 self.float = Series(np.random.randn(N)) self.numstr = self.float.astype("str") - self.str = Series(tm.makeStringIndex(N)) + self.str = Series(Index([f"i-{i}" for i in range(N)], dtype=object)) def time_from_float(self, errors): to_numeric(self.float, errors=errors) diff --git a/asv_bench/benchmarks/io/csv.py b/asv_bench/benchmarks/io/csv.py index a45315f63d62e..9ac83db4f85b9 100644 --- a/asv_bench/benchmarks/io/csv.py +++ b/asv_bench/benchmarks/io/csv.py @@ -10,6 +10,7 @@ from pandas import ( Categorical, DataFrame, + Index, concat, date_range, period_range, @@ -17,10 +18,7 @@ to_datetime, ) -from ..pandas_vb_common import ( - BaseIO, - tm, -) +from ..pandas_vb_common import BaseIO class ToCSV(BaseIO): @@ -288,7 +286,7 @@ class ReadCSVSkipRows(BaseIO): def setup(self, skiprows, engine): N = 20000 - index = tm.makeStringIndex(N) + index = Index([f"i-{i}" for i in range(N)], dtype=object) df = DataFrame( { "float1": np.random.randn(N), diff --git a/asv_bench/benchmarks/io/excel.py b/asv_bench/benchmarks/io/excel.py index f8d81b0f6a699..902a61be901bd 100644 --- a/asv_bench/benchmarks/io/excel.py +++ b/asv_bench/benchmarks/io/excel.py @@ -12,12 +12,11 @@ from pandas import ( DataFrame, ExcelWriter, + Index, date_range, read_excel, ) -from ..pandas_vb_common import tm - def _generate_dataframe(): N = 2000 @@ -27,7 +26,7 @@ def _generate_dataframe(): columns=[f"float{i}" for i in range(C)], index=date_range("20000101", periods=N, freq="h"), ) - df["object"] = tm.makeStringIndex(N) + df["object"] = Index([f"i-{i}" for i in range(N)], dtype=object) return df diff --git a/asv_bench/benchmarks/io/hdf.py b/asv_bench/benchmarks/io/hdf.py index 195aaa158e178..acf0ec4b9d359 100644 --- a/asv_bench/benchmarks/io/hdf.py +++ b/asv_bench/benchmarks/io/hdf.py @@ -3,20 +3,18 @@ from pandas import ( DataFrame, HDFStore, + Index, date_range, read_hdf, ) -from ..pandas_vb_common import ( - BaseIO, - tm, -) +from ..pandas_vb_common import BaseIO class HDFStoreDataFrame(BaseIO): def setup(self): N = 25000 - index = tm.makeStringIndex(N) + index = Index([f"i-{i}" for i in range(N)], dtype=object) self.df = DataFrame( {"float1": np.random.randn(N), "float2": np.random.randn(N)}, index=index ) @@ -124,7 +122,7 @@ def setup(self, format): columns=[f"float{i}" for i in range(C)], index=date_range("20000101", periods=N, freq="h"), ) - self.df["object"] = tm.makeStringIndex(N) + self.df["object"] = Index([f"i-{i}" for i in range(N)], dtype=object) self.df.to_hdf(self.fname, "df", format=format) # Numeric df diff --git a/asv_bench/benchmarks/io/json.py b/asv_bench/benchmarks/io/json.py index 8a2e3fa87eb37..bcbfcdea42dd9 100644 --- a/asv_bench/benchmarks/io/json.py +++ b/asv_bench/benchmarks/io/json.py @@ -4,6 +4,7 @@ from pandas import ( DataFrame, + Index, concat, date_range, json_normalize, @@ -11,10 +12,7 @@ timedelta_range, ) -from ..pandas_vb_common import ( - BaseIO, - tm, -) +from ..pandas_vb_common import BaseIO class ReadJSON(BaseIO): @@ -114,7 +112,7 @@ def setup(self, orient, frame): ints = np.random.randint(100000000, size=N) longints = sys.maxsize * np.random.randint(100000000, size=N) floats = np.random.randn(N) - strings = tm.makeStringIndex(N) + strings = Index([f"i-{i}" for i in range(N)], dtype=object) self.df = DataFrame(np.random.randn(N, ncols), index=np.arange(N)) self.df_date_idx = DataFrame(np.random.randn(N, ncols), index=index) self.df_td_int_ts = DataFrame( @@ -220,7 +218,7 @@ def setup(self): ints = np.random.randint(100000000, size=N) longints = sys.maxsize * np.random.randint(100000000, size=N) floats = np.random.randn(N) - strings = tm.makeStringIndex(N) + strings = Index([f"i-{i}" for i in range(N)], dtype=object) self.df = DataFrame(np.random.randn(N, ncols), index=np.arange(N)) self.df_date_idx = DataFrame(np.random.randn(N, ncols), index=index) self.df_td_int_ts = DataFrame( diff --git a/asv_bench/benchmarks/io/pickle.py b/asv_bench/benchmarks/io/pickle.py index 54631d9236887..4787b57b54756 100644 --- a/asv_bench/benchmarks/io/pickle.py +++ b/asv_bench/benchmarks/io/pickle.py @@ -2,14 +2,12 @@ from pandas import ( DataFrame, + Index, date_range, read_pickle, ) -from ..pandas_vb_common import ( - BaseIO, - tm, -) +from ..pandas_vb_common import BaseIO class Pickle(BaseIO): @@ -22,7 +20,7 @@ def setup(self): columns=[f"float{i}" for i in range(C)], index=date_range("20000101", periods=N, freq="h"), ) - self.df["object"] = tm.makeStringIndex(N) + self.df["object"] = Index([f"i-{i}" for i in range(N)], dtype=object) self.df.to_pickle(self.fname) def time_read_pickle(self): diff --git a/asv_bench/benchmarks/io/sql.py b/asv_bench/benchmarks/io/sql.py index 6f893ee72d918..e87cc4aaa80c7 100644 --- a/asv_bench/benchmarks/io/sql.py +++ b/asv_bench/benchmarks/io/sql.py @@ -5,13 +5,12 @@ from pandas import ( DataFrame, + Index, date_range, read_sql_query, read_sql_table, ) -from ..pandas_vb_common import tm - class SQL: params = ["sqlalchemy", "sqlite"] @@ -35,7 +34,7 @@ def setup(self, connection): "int": np.random.randint(0, N, size=N), "datetime": date_range("2000-01-01", periods=N, freq="s"), }, - index=tm.makeStringIndex(N), + index=Index([f"i-{i}" for i in range(N)], dtype=object), ) self.df.iloc[1000:3000, 1] = np.nan self.df["date"] = self.df["datetime"].dt.date @@ -84,7 +83,7 @@ def setup(self, connection, dtype): "int": np.random.randint(0, N, size=N), "datetime": date_range("2000-01-01", periods=N, freq="s"), }, - index=tm.makeStringIndex(N), + index=Index([f"i-{i}" for i in range(N)], dtype=object), ) self.df.iloc[1000:3000, 1] = np.nan self.df["date"] = self.df["datetime"].dt.date @@ -113,7 +112,7 @@ def setup(self): "int": np.random.randint(0, N, size=N), "datetime": date_range("2000-01-01", periods=N, freq="s"), }, - index=tm.makeStringIndex(N), + index=Index([f"i-{i}" for i in range(N)], dtype=object), ) self.df.iloc[1000:3000, 1] = np.nan self.df["date"] = self.df["datetime"].dt.date @@ -159,7 +158,7 @@ def setup(self, dtype): "int": np.random.randint(0, N, size=N), "datetime": date_range("2000-01-01", periods=N, freq="s"), }, - index=tm.makeStringIndex(N), + index=Index([f"i-{i}" for i in range(N)], dtype=object), ) self.df.iloc[1000:3000, 1] = np.nan self.df["date"] = self.df["datetime"].dt.date diff --git a/asv_bench/benchmarks/io/stata.py b/asv_bench/benchmarks/io/stata.py index 750bcf4ccee5c..ff33ededdfed9 100644 --- a/asv_bench/benchmarks/io/stata.py +++ b/asv_bench/benchmarks/io/stata.py @@ -2,14 +2,12 @@ from pandas import ( DataFrame, + Index, date_range, read_stata, ) -from ..pandas_vb_common import ( - BaseIO, - tm, -) +from ..pandas_vb_common import BaseIO class Stata(BaseIO): @@ -25,7 +23,7 @@ def setup(self, convert_dates): columns=[f"float{i}" for i in range(C)], index=date_range("20000101", periods=N, freq="h"), ) - self.df["object"] = tm.makeStringIndex(self.N) + self.df["object"] = Index([f"i-{i}" for i in range(self.N)], dtype=object) self.df["int8_"] = np.random.randint( np.iinfo(np.int8).min, np.iinfo(np.int8).max - 27, N ) diff --git a/asv_bench/benchmarks/join_merge.py b/asv_bench/benchmarks/join_merge.py index 23824c2c748df..6f494562103c2 100644 --- a/asv_bench/benchmarks/join_merge.py +++ b/asv_bench/benchmarks/join_merge.py @@ -14,8 +14,6 @@ merge_asof, ) -from .pandas_vb_common import tm - try: from pandas import merge_ordered except ImportError: @@ -28,7 +26,7 @@ class Concat: def setup(self, axis): N = 1000 - s = Series(N, index=tm.makeStringIndex(N)) + s = Series(N, index=Index([f"i-{i}" for i in range(N)], dtype=object)) self.series = [s[i:-i] for i in range(1, 10)] * 50 self.small_frames = [DataFrame(np.random.randn(5, 4))] * 1000 df = DataFrame( @@ -94,7 +92,7 @@ def setup(self, dtype, structure, axis, sort): elif dtype in ("int64", "Int64", "int64[pyarrow]"): vals = np.arange(N, dtype=np.int64) elif dtype in ("string[python]", "string[pyarrow]"): - vals = tm.makeStringIndex(N) + vals = Index([f"i-{i}" for i in range(N)], dtype=object) else: raise NotImplementedError @@ -122,8 +120,8 @@ class Join: param_names = ["sort"] def setup(self, sort): - level1 = tm.makeStringIndex(10).values - level2 = tm.makeStringIndex(1000).values + level1 = Index([f"i-{i}" for i in range(10)], dtype=object).values + level2 = Index([f"i-{i}" for i in range(1000)], dtype=object).values codes1 = np.arange(10).repeat(1000) codes2 = np.tile(np.arange(1000), 10) index2 = MultiIndex(levels=[level1, level2], codes=[codes1, codes2]) @@ -231,8 +229,8 @@ class Merge: def setup(self, sort): N = 10000 - indices = tm.makeStringIndex(N).values - indices2 = tm.makeStringIndex(N).values + indices = Index([f"i-{i}" for i in range(N)], dtype=object).values + indices2 = Index([f"i-{i}" for i in range(N)], dtype=object).values key = np.tile(indices[:8000], 10) key2 = np.tile(indices2[:8000], 10) self.left = DataFrame( @@ -400,7 +398,7 @@ def time_merge_on_cat_idx(self): class MergeOrdered: def setup(self): - groups = tm.makeStringIndex(10).values + groups = Index([f"i-{i}" for i in range(10)], dtype=object).values self.left = DataFrame( { "group": groups.repeat(5000), diff --git a/asv_bench/benchmarks/libs.py b/asv_bench/benchmarks/libs.py index f041499c9c622..3419163bcfe09 100644 --- a/asv_bench/benchmarks/libs.py +++ b/asv_bench/benchmarks/libs.py @@ -15,13 +15,11 @@ from pandas import ( NA, + Index, NaT, ) -from .pandas_vb_common import ( - lib, - tm, -) +from .pandas_vb_common import lib try: from pandas.util import cache_readonly @@ -61,8 +59,8 @@ class FastZip: def setup(self): N = 10000 K = 10 - key1 = tm.makeStringIndex(N).values.repeat(K) - key2 = tm.makeStringIndex(N).values.repeat(K) + key1 = Index([f"i-{i}" for i in range(N)], dtype=object).values.repeat(K) + key2 = Index([f"i-{i}" for i in range(N)], dtype=object).values.repeat(K) col_array = np.vstack([key1, key2, np.random.randn(N * K)]) col_array2 = col_array.copy() col_array2[:, :10000] = np.nan diff --git a/asv_bench/benchmarks/multiindex_object.py b/asv_bench/benchmarks/multiindex_object.py index 87dcdb16fa647..54788d41d83fe 100644 --- a/asv_bench/benchmarks/multiindex_object.py +++ b/asv_bench/benchmarks/multiindex_object.py @@ -5,6 +5,7 @@ from pandas import ( NA, DataFrame, + Index, MultiIndex, RangeIndex, Series, @@ -12,8 +13,6 @@ date_range, ) -from .pandas_vb_common import tm - class GetLoc: def setup(self): @@ -144,7 +143,11 @@ def time_is_monotonic(self): class Duplicated: def setup(self): n, k = 200, 5000 - levels = [np.arange(n), tm.makeStringIndex(n).values, 1000 + np.arange(n)] + levels = [ + np.arange(n), + Index([f"i-{i}" for i in range(n)], dtype=object).values, + 1000 + np.arange(n), + ] codes = [np.random.choice(n, (k * n)) for lev in levels] self.mi = MultiIndex(levels=levels, codes=codes) @@ -249,7 +252,7 @@ def setup(self, index_structure, dtype, method, sort): level2 = range(N // 1000) int_left = MultiIndex.from_product([level1, level2]) - level2 = tm.makeStringIndex(N // 1000).values + level2 = Index([f"i-{i}" for i in range(N // 1000)], dtype=object).values str_left = MultiIndex.from_product([level1, level2]) level2 = range(N // 1000) @@ -293,7 +296,7 @@ def setup(self, dtype): level2[0] = NA ea_int_left = MultiIndex.from_product([level1, level2]) - level2 = tm.makeStringIndex(N // 1000).values + level2 = Index([f"i-{i}" for i in range(N // 1000)], dtype=object).values str_left = MultiIndex.from_product([level1, level2]) data = { @@ -354,7 +357,7 @@ def setup(self, dtype): level2 = range(N // 1000) int_midx = MultiIndex.from_product([level1, level2]) - level2 = tm.makeStringIndex(N // 1000).values + level2 = Index([f"i-{i}" for i in range(N // 1000)], dtype=object).values str_midx = MultiIndex.from_product([level1, level2]) data = { @@ -411,7 +414,7 @@ def setup(self, dtype): elif dtype == "int64": level2 = range(N2) elif dtype == "string": - level2 = tm.makeStringIndex(N2) + level2 = Index([f"i-{i}" for i in range(N2)], dtype=object) else: raise NotImplementedError diff --git a/asv_bench/benchmarks/reindex.py b/asv_bench/benchmarks/reindex.py index 1c5e6050db275..3d22bfce7e2b2 100644 --- a/asv_bench/benchmarks/reindex.py +++ b/asv_bench/benchmarks/reindex.py @@ -9,8 +9,6 @@ period_range, ) -from .pandas_vb_common import tm - class Reindex: def setup(self): @@ -23,8 +21,8 @@ def setup(self): ) N = 5000 K = 200 - level1 = tm.makeStringIndex(N).values.repeat(K) - level2 = np.tile(tm.makeStringIndex(K).values, N) + level1 = Index([f"i-{i}" for i in range(N)], dtype=object).values.repeat(K) + level2 = np.tile(Index([f"i-{i}" for i in range(K)], dtype=object).values, N) index = MultiIndex.from_arrays([level1, level2]) self.s = Series(np.random.randn(N * K), index=index) self.s_subset = self.s[::2] @@ -93,8 +91,8 @@ class DropDuplicates: def setup(self, inplace): N = 10000 K = 10 - key1 = tm.makeStringIndex(N).values.repeat(K) - key2 = tm.makeStringIndex(N).values.repeat(K) + key1 = Index([f"i-{i}" for i in range(N)], dtype=object).values.repeat(K) + key2 = Index([f"i-{i}" for i in range(N)], dtype=object).values.repeat(K) self.df = DataFrame( {"key1": key1, "key2": key2, "value": np.random.randn(N * K)} ) @@ -102,7 +100,9 @@ def setup(self, inplace): self.df_nan.iloc[:10000, :] = np.nan self.s = Series(np.random.randint(0, 1000, size=10000)) - self.s_str = Series(np.tile(tm.makeStringIndex(1000).values, 10)) + self.s_str = Series( + np.tile(Index([f"i-{i}" for i in range(1000)], dtype=object).values, 10) + ) N = 1000000 K = 10000 @@ -133,7 +133,7 @@ class Align: # blog "pandas escaped the zoo" def setup(self): n = 50000 - indices = tm.makeStringIndex(n) + indices = Index([f"i-{i}" for i in range(n)], dtype=object) subsample_size = 40000 self.x = Series(np.random.randn(n), indices) self.y = Series( diff --git a/asv_bench/benchmarks/series_methods.py b/asv_bench/benchmarks/series_methods.py index 79cf8f9cd2048..b021af4694d7d 100644 --- a/asv_bench/benchmarks/series_methods.py +++ b/asv_bench/benchmarks/series_methods.py @@ -10,8 +10,6 @@ date_range, ) -from .pandas_vb_common import tm - class SeriesConstructor: def setup(self): @@ -253,7 +251,7 @@ def time_mode(self, N): class Dir: def setup(self): - self.s = Series(index=tm.makeStringIndex(10000)) + self.s = Series(index=Index([f"i-{i}" for i in range(10000)], dtype=object)) def time_dir_strings(self): dir(self.s) diff --git a/asv_bench/benchmarks/strings.py b/asv_bench/benchmarks/strings.py index 6682a60f42997..1f4a104255057 100644 --- a/asv_bench/benchmarks/strings.py +++ b/asv_bench/benchmarks/strings.py @@ -6,12 +6,11 @@ NA, Categorical, DataFrame, + Index, Series, ) from pandas.arrays import StringArray -from .pandas_vb_common import tm - class Dtypes: params = ["str", "string[python]", "string[pyarrow]"] @@ -19,7 +18,9 @@ class Dtypes: def setup(self, dtype): try: - self.s = Series(tm.makeStringIndex(10**5), dtype=dtype) + self.s = Series( + Index([f"i-{i}" for i in range(10000)], dtype=object), dtype=dtype + ) except ImportError: raise NotImplementedError @@ -172,7 +173,7 @@ class Repeat: def setup(self, repeats): N = 10**5 - self.s = Series(tm.makeStringIndex(N)) + self.s = Series(Index([f"i-{i}" for i in range(N)], dtype=object)) repeat = {"int": 1, "array": np.random.randint(1, 3, N)} self.values = repeat[repeats] @@ -187,13 +188,20 @@ class Cat: def setup(self, other_cols, sep, na_rep, na_frac): N = 10**5 mask_gen = lambda: np.random.choice([True, False], N, p=[1 - na_frac, na_frac]) - self.s = Series(tm.makeStringIndex(N)).where(mask_gen()) + self.s = Series(Index([f"i-{i}" for i in range(N)], dtype=object)).where( + mask_gen() + ) if other_cols == 0: # str.cat self-concatenates only for others=None self.others = None else: self.others = DataFrame( - {i: tm.makeStringIndex(N).where(mask_gen()) for i in range(other_cols)} + { + i: Index([f"i-{i}" for i in range(N)], dtype=object).where( + mask_gen() + ) + for i in range(other_cols) + } ) def time_cat(self, other_cols, sep, na_rep, na_frac): @@ -254,7 +262,7 @@ def time_get_dummies(self, dtype): class Encode: def setup(self): - self.ser = Series(tm.makeStringIndex()) + self.ser = Series(Index([f"i-{i}" for i in range(10_000)], dtype=object)) def time_encode_decode(self): self.ser.str.encode("utf-8").str.decode("utf-8") diff --git a/pandas/_testing/__init__.py b/pandas/_testing/__init__.py index a74fb2bf48bc4..c73d869b6c39c 100644 --- a/pandas/_testing/__init__.py +++ b/pandas/_testing/__init__.py @@ -370,11 +370,6 @@ def getCols(k) -> str: return string.ascii_uppercase[:k] -# make index -def makeStringIndex(k: int = 10, name=None) -> Index: - return Index(rands_array(nchars=10, size=k), name=name) - - def makeCategoricalIndex( k: int = 10, n: int = 3, name=None, **kwargs ) -> CategoricalIndex: @@ -385,14 +380,6 @@ def makeCategoricalIndex( ) -def makeBoolIndex(k: int = 10, name=None) -> Index: - if k == 1: - return Index([True], name=name) - elif k == 2: - return Index([False, True], name=name) - return Index([False, True] + [False] * (k - 2), name=name) - - def makeNumericIndex(k: int = 10, *, name=None, dtype: Dtype | None) -> Index: dtype = pandas_dtype(dtype) assert isinstance(dtype, np.dtype) @@ -457,14 +444,13 @@ def makePeriodIndex(k: int = 10, name=None, **kwargs) -> PeriodIndex: def makeObjectSeries(name=None) -> Series: - data = makeStringIndex(_N) - data = Index(data, dtype=object) - index = makeStringIndex(_N) - return Series(data, index=index, name=name) + data = [f"foo_{i}" for i in range(_N)] + index = Index([f"bar_{i}" for i in range(_N)]) + return Series(data, index=index, name=name, dtype=object) def getSeriesData() -> dict[str, Series]: - index = makeStringIndex(_N) + index = Index([f"foo_{i}" for i in range(_N)]) return { c: Series(np.random.default_rng(i).standard_normal(_N), index=index) for i, c in enumerate(getCols(_K)) @@ -566,7 +552,7 @@ def makeCustomIndex( idx_func_dict: dict[str, Callable[..., Index]] = { "i": makeIntIndex, "f": makeFloatIndex, - "s": makeStringIndex, + "s": lambda n: Index([f"{i}_{chr(i)}" for i in range(97, 97 + n)]), "dt": makeDateIndex, "td": makeTimedeltaIndex, "p": makePeriodIndex, @@ -1049,7 +1035,6 @@ def shares_memory(left, right) -> bool: "iat", "iloc", "loc", - "makeBoolIndex", "makeCategoricalIndex", "makeCustomDataframe", "makeCustomIndex", @@ -1062,7 +1047,6 @@ def shares_memory(left, right) -> bool: "makeObjectSeries", "makePeriodIndex", "makeRangeIndex", - "makeStringIndex", "makeTimeDataFrame", "makeTimedeltaIndex", "makeTimeSeries", diff --git a/pandas/conftest.py b/pandas/conftest.py index 350871c3085c1..3205b6657439f 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -610,7 +610,7 @@ def _create_mi_with_dt64tz_level(): indices_dict = { - "string": tm.makeStringIndex(100), + "string": Index([f"pandas_{i}" for i in range(100)]), "datetime": tm.makeDateIndex(100), "datetime-tz": tm.makeDateIndex(100, tz="US/Pacific"), "period": tm.makePeriodIndex(100), @@ -626,7 +626,7 @@ def _create_mi_with_dt64tz_level(): "uint64": tm.makeUIntIndex(100, dtype="uint64"), "float32": tm.makeFloatIndex(100, dtype="float32"), "float64": tm.makeFloatIndex(100, dtype="float64"), - "bool-object": tm.makeBoolIndex(10).astype(object), + "bool-object": Index([True, False] * 5, dtype=object), "bool-dtype": Index(np.random.default_rng(2).standard_normal(10) < 0), "complex64": tm.makeNumericIndex(100, dtype="float64").astype("complex64"), "complex128": tm.makeNumericIndex(100, dtype="float64").astype("complex128"), @@ -641,10 +641,12 @@ def _create_mi_with_dt64tz_level(): "nullable_uint": Index(np.arange(100), dtype="UInt16"), "nullable_float": Index(np.arange(100), dtype="Float32"), "nullable_bool": Index(np.arange(100).astype(bool), dtype="boolean"), - "string-python": Index(pd.array(tm.makeStringIndex(100), dtype="string[python]")), + "string-python": Index( + pd.array([f"pandas_{i}" for i in range(100)], dtype="string[python]") + ), } if has_pyarrow: - idx = Index(pd.array(tm.makeStringIndex(100), dtype="string[pyarrow]")) + idx = Index(pd.array([f"pandas_{i}" for i in range(100)], dtype="string[pyarrow]")) indices_dict["string-pyarrow"] = idx diff --git a/pandas/tests/arithmetic/test_numeric.py b/pandas/tests/arithmetic/test_numeric.py index f89711c0edee7..c3c4a8b4fc6c0 100644 --- a/pandas/tests/arithmetic/test_numeric.py +++ b/pandas/tests/arithmetic/test_numeric.py @@ -916,7 +916,7 @@ def test_add_frames(self, first, second, expected): # TODO: This came from series.test.test_operators, needs cleanup def test_series_frame_radd_bug(self, fixed_now_ts): # GH#353 - vals = Series(tm.makeStringIndex()) + vals = Series([str(i) for i in range(5)]) result = "foo_" + vals expected = vals.map(lambda x: "foo_" + x) tm.assert_series_equal(result, expected) diff --git a/pandas/tests/arithmetic/test_object.py b/pandas/tests/arithmetic/test_object.py index 33953900c2006..6b36f447eb7d5 100644 --- a/pandas/tests/arithmetic/test_object.py +++ b/pandas/tests/arithmetic/test_object.py @@ -299,7 +299,7 @@ def test_iadd_string(self): @pytest.mark.xfail(using_pyarrow_string_dtype(), reason="add doesn't work") def test_add(self): - index = tm.makeStringIndex(100) + index = pd.Index([str(i) for i in range(10)]) expected = pd.Index(index.values * 2) tm.assert_index_equal(index + index, expected) tm.assert_index_equal(index + index.tolist(), expected) @@ -313,7 +313,7 @@ def test_add(self): tm.assert_index_equal("1" + index, expected) def test_sub_fail(self, using_infer_string): - index = tm.makeStringIndex(100) + index = pd.Index([str(i) for i in range(10)]) if using_infer_string: import pyarrow as pa diff --git a/pandas/tests/frame/methods/test_first_valid_index.py b/pandas/tests/frame/methods/test_first_valid_index.py index a448768f4173d..2e27f1aa71700 100644 --- a/pandas/tests/frame/methods/test_first_valid_index.py +++ b/pandas/tests/frame/methods/test_first_valid_index.py @@ -6,9 +6,10 @@ from pandas import ( DataFrame, + Index, Series, + date_range, ) -import pandas._testing as tm class TestFirstValidIndex: @@ -44,11 +45,12 @@ def test_first_last_valid_frame(self, data, idx, expected_first, expected_last): assert expected_first == df.first_valid_index() assert expected_last == df.last_valid_index() - @pytest.mark.parametrize("index_func", [tm.makeStringIndex, tm.makeDateIndex]) - def test_first_last_valid(self, index_func): - N = 30 - index = index_func(N) - mat = np.random.default_rng(2).standard_normal(N) + @pytest.mark.parametrize( + "index", + [Index([str(i) for i in range(20)]), date_range("2020-01-01", periods=20)], + ) + def test_first_last_valid(self, index): + mat = np.random.default_rng(2).standard_normal(len(index)) mat[:5] = np.nan mat[-5:] = np.nan @@ -60,10 +62,12 @@ def test_first_last_valid(self, index_func): assert ser.first_valid_index() == frame.index[5] assert ser.last_valid_index() == frame.index[-6] - @pytest.mark.parametrize("index_func", [tm.makeStringIndex, tm.makeDateIndex]) - def test_first_last_valid_all_nan(self, index_func): + @pytest.mark.parametrize( + "index", + [Index([str(i) for i in range(10)]), date_range("2020-01-01", periods=10)], + ) + def test_first_last_valid_all_nan(self, index): # GH#17400: no valid entries - index = index_func(30) frame = DataFrame(np.nan, columns=["foo"], index=index) assert frame.last_valid_index() is None diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index bf17b61b0e3f3..3111075c5c1a7 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -784,7 +784,7 @@ def test_constructor_dict_cast(self): def test_constructor_dict_cast2(self): # can't cast to float test_data = { - "A": dict(zip(range(20), tm.makeStringIndex(20))), + "A": dict(zip(range(20), [f"word_{i}" for i in range(20)])), "B": dict(zip(range(15), np.random.default_rng(2).standard_normal(15))), } with pytest.raises(ValueError, match="could not convert string"): diff --git a/pandas/tests/frame/test_repr.py b/pandas/tests/frame/test_repr.py index 98962b3003b6d..eed48b9db116b 100644 --- a/pandas/tests/frame/test_repr.py +++ b/pandas/tests/frame/test_repr.py @@ -165,7 +165,7 @@ def test_repr_mixed_big(self): biggie = DataFrame( { "A": np.random.default_rng(2).standard_normal(200), - "B": tm.makeStringIndex(200), + "B": [str(i) for i in range(200)], }, index=range(200), ) diff --git a/pandas/tests/groupby/test_grouping.py b/pandas/tests/groupby/test_grouping.py index 3c1a35c984031..c401762dace23 100644 --- a/pandas/tests/groupby/test_grouping.py +++ b/pandas/tests/groupby/test_grouping.py @@ -19,6 +19,7 @@ Series, Timestamp, date_range, + period_range, ) import pandas._testing as tm from pandas.core.groupby.grouper import Grouping @@ -174,23 +175,21 @@ class TestGrouping: @pytest.mark.parametrize( "index", [ - tm.makeFloatIndex, - tm.makeStringIndex, - tm.makeIntIndex, - tm.makeDateIndex, - tm.makePeriodIndex, + Index(list("abcde")), + Index(np.arange(5)), + Index(np.arange(5, dtype=float)), + date_range("2020-01-01", periods=5), + period_range("2020-01-01", periods=5), ], ) - @pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning") def test_grouper_index_types(self, index): # related GH5375 # groupby misbehaving when using a Floatlike index - df = DataFrame(np.arange(10).reshape(5, 2), columns=list("AB")) + df = DataFrame(np.arange(10).reshape(5, 2), columns=list("AB"), index=index) - df.index = index(len(df)) df.groupby(list("abcde"), group_keys=False).apply(lambda x: x) - df.index = list(reversed(df.index.tolist())) + df.index = df.index[::-1] df.groupby(list("abcde"), group_keys=False).apply(lambda x: x) def test_grouper_multilevel_freq(self): diff --git a/pandas/tests/indexes/multi/test_duplicates.py b/pandas/tests/indexes/multi/test_duplicates.py index a69248cf038f8..6c6d9022b1af3 100644 --- a/pandas/tests/indexes/multi/test_duplicates.py +++ b/pandas/tests/indexes/multi/test_duplicates.py @@ -257,7 +257,7 @@ def test_duplicated(idx_dup, keep, expected): def test_duplicated_hashtable_impl(keep, monkeypatch): # GH 9125 n, k = 6, 10 - levels = [np.arange(n), tm.makeStringIndex(n), 1000 + np.arange(n)] + levels = [np.arange(n), [str(i) for i in range(n)], 1000 + np.arange(n)] codes = [np.random.default_rng(2).choice(n, k * n) for _ in levels] with monkeypatch.context() as m: m.setattr(libindex, "_SIZE_CUTOFF", 50) diff --git a/pandas/tests/indexing/test_floats.py b/pandas/tests/indexing/test_floats.py index c9fbf95751dfe..cf9966145afce 100644 --- a/pandas/tests/indexing/test_floats.py +++ b/pandas/tests/indexing/test_floats.py @@ -6,6 +6,9 @@ Index, RangeIndex, Series, + date_range, + period_range, + timedelta_range, ) import pandas._testing as tm @@ -39,22 +42,21 @@ def check(self, result, original, indexer, getitem): tm.assert_almost_equal(result, expected) @pytest.mark.parametrize( - "index_func", + "index", [ - tm.makeStringIndex, - tm.makeCategoricalIndex, - tm.makeDateIndex, - tm.makeTimedeltaIndex, - tm.makePeriodIndex, + Index(list("abcde")), + Index(list("abcde"), dtype="category"), + date_range("2020-01-01", periods=5), + timedelta_range("1 day", periods=5), + period_range("2020-01-01", periods=5), ], ) - def test_scalar_non_numeric(self, index_func, frame_or_series, indexer_sl): + def test_scalar_non_numeric(self, index, frame_or_series, indexer_sl): # GH 4892 # float_indexers should raise exceptions # on appropriate Index types & accessors - i = index_func(5) - s = gen_obj(frame_or_series, i) + s = gen_obj(frame_or_series, index) # getting with pytest.raises(KeyError, match="^3.0$"): @@ -75,19 +77,18 @@ def test_scalar_non_numeric(self, index_func, frame_or_series, indexer_sl): assert 3.0 not in s2.axes[-1] @pytest.mark.parametrize( - "index_func", + "index", [ - tm.makeStringIndex, - tm.makeCategoricalIndex, - tm.makeDateIndex, - tm.makeTimedeltaIndex, - tm.makePeriodIndex, + Index(list("abcde")), + Index(list("abcde"), dtype="category"), + date_range("2020-01-01", periods=5), + timedelta_range("1 day", periods=5), + period_range("2020-01-01", periods=5), ], ) - def test_scalar_non_numeric_series_fallback(self, index_func): + def test_scalar_non_numeric_series_fallback(self, index): # fallsback to position selection, series only - i = index_func(5) - s = Series(np.arange(len(i)), index=i) + s = Series(np.arange(len(index)), index=index) msg = "Series.__getitem__ treating keys as positions is deprecated" with tm.assert_produces_warning(FutureWarning, match=msg): @@ -214,21 +215,20 @@ def test_scalar_float(self, frame_or_series): self.check(result, s, 3, False) @pytest.mark.parametrize( - "index_func", + "index", [ - tm.makeStringIndex, - tm.makeDateIndex, - tm.makeTimedeltaIndex, - tm.makePeriodIndex, + Index(list("abcde"), dtype=object), + date_range("2020-01-01", periods=5), + timedelta_range("1 day", periods=5), + period_range("2020-01-01", periods=5), ], ) @pytest.mark.parametrize("idx", [slice(3.0, 4), slice(3, 4.0), slice(3.0, 4.0)]) - def test_slice_non_numeric(self, index_func, idx, frame_or_series, indexer_sli): + def test_slice_non_numeric(self, index, idx, frame_or_series, indexer_sli): # GH 4892 # float_indexers should raise exceptions # on appropriate Index types & accessors - index = index_func(5) s = gen_obj(frame_or_series, index) # getitem diff --git a/pandas/tests/io/formats/test_format.py b/pandas/tests/io/formats/test_format.py index edac82193d1c8..f5738b83a8e64 100644 --- a/pandas/tests/io/formats/test_format.py +++ b/pandas/tests/io/formats/test_format.py @@ -25,7 +25,6 @@ read_csv, reset_option, ) -import pandas._testing as tm from pandas.io.formats import printing import pandas.io.formats.format as fmt @@ -834,19 +833,21 @@ def test_to_string_buffer_all_unicode(self): buf.getvalue() @pytest.mark.parametrize( - "index", + "index_scalar", [ - tm.makeStringIndex, - tm.makeIntIndex, - tm.makeDateIndex, - tm.makePeriodIndex, + "a" * 10, + 1, + Timestamp(2020, 1, 1), + pd.Period("2020-01-01"), ], ) @pytest.mark.parametrize("h", [10, 20]) @pytest.mark.parametrize("w", [10, 20]) - def test_to_string_truncate_indices(self, index, h, w): + def test_to_string_truncate_indices(self, index_scalar, h, w): with option_context("display.expand_frame_repr", False): - df = DataFrame(index=index(h), columns=tm.makeStringIndex(w)) + df = DataFrame( + index=[index_scalar] * h, columns=[str(i) * 10 for i in range(w)] + ) with option_context("display.max_rows", 15): if h == 20: assert has_vertically_truncated_repr(df) diff --git a/pandas/tests/io/formats/test_to_html.py b/pandas/tests/io/formats/test_to_html.py index a7648cf1c471a..605e5e182d8cc 100644 --- a/pandas/tests/io/formats/test_to_html.py +++ b/pandas/tests/io/formats/test_to_html.py @@ -59,7 +59,7 @@ def biggie_df_fixture(request): df = DataFrame( { "A": np.random.default_rng(2).standard_normal(200), - "B": tm.makeStringIndex(200), + "B": Index([f"{i}?!" for i in range(200)]), }, index=np.arange(200), ) diff --git a/pandas/tests/io/formats/test_to_string.py b/pandas/tests/io/formats/test_to_string.py index e607b6eb454a1..02c20b0e25477 100644 --- a/pandas/tests/io/formats/test_to_string.py +++ b/pandas/tests/io/formats/test_to_string.py @@ -804,7 +804,7 @@ def test_to_string(self): biggie = DataFrame( { "A": np.random.default_rng(2).standard_normal(200), - "B": tm.makeStringIndex(200), + "B": Index([f"{i}?!" for i in range(200)]), }, ) diff --git a/pandas/tests/io/pytables/test_put.py b/pandas/tests/io/pytables/test_put.py index c109b72e9c239..59a05dc9ea546 100644 --- a/pandas/tests/io/pytables/test_put.py +++ b/pandas/tests/io/pytables/test_put.py @@ -196,32 +196,27 @@ def test_put_mixed_type(setup_path): tm.assert_frame_equal(expected, df) +@pytest.mark.parametrize("format", ["table", "fixed"]) @pytest.mark.parametrize( - "format, index", + "index", [ - ["table", tm.makeFloatIndex], - ["table", tm.makeStringIndex], - ["table", tm.makeIntIndex], - ["table", tm.makeDateIndex], - ["fixed", tm.makeFloatIndex], - ["fixed", tm.makeStringIndex], - ["fixed", tm.makeIntIndex], - ["fixed", tm.makeDateIndex], - ["table", tm.makePeriodIndex], # GH#7796 - ["fixed", tm.makePeriodIndex], + Index([str(i) for i in range(10)]), + Index(np.arange(10, dtype=float)), + Index(np.arange(10)), + date_range("2020-01-01", periods=10), + pd.period_range("2020-01-01", periods=10), ], ) -@pytest.mark.filterwarnings(r"ignore:PeriodDtype\[B\] is deprecated:FutureWarning") def test_store_index_types(setup_path, format, index): # GH5386 # test storing various index types with ensure_clean_store(setup_path) as store: df = DataFrame( - np.random.default_rng(2).standard_normal((10, 2)), columns=list("AB") + np.random.default_rng(2).standard_normal((10, 2)), + columns=list("AB"), + index=index, ) - df.index = index(len(df)) - _maybe_remove(store, "df") store.put("df", df, format=format) tm.assert_frame_equal(df, store["df"]) diff --git a/pandas/tests/io/pytables/test_round_trip.py b/pandas/tests/io/pytables/test_round_trip.py index baac90e52e962..e05e1e96eb11f 100644 --- a/pandas/tests/io/pytables/test_round_trip.py +++ b/pandas/tests/io/pytables/test_round_trip.py @@ -51,7 +51,8 @@ def roundtrip(key, obj, **kwargs): def test_long_strings(setup_path): # GH6166 - df = DataFrame({"a": tm.makeStringIndex(10)}, index=tm.makeStringIndex(10)) + data = ["a" * 50] * 10 + df = DataFrame({"a": data}, index=data) with ensure_clean_store(setup_path) as store: store.append("df", df, data_columns=["a"]) @@ -65,7 +66,7 @@ def test_api(tmp_path, setup_path): # API issue when to_hdf doesn't accept append AND format args path = tmp_path / setup_path - df = tm.makeDataFrame() + df = DataFrame(range(20)) df.iloc[:10].to_hdf(path, key="df", append=True, format="table") df.iloc[10:].to_hdf(path, key="df", append=True, format="table") tm.assert_frame_equal(read_hdf(path, "df"), df) @@ -79,7 +80,7 @@ def test_api(tmp_path, setup_path): def test_api_append(tmp_path, setup_path): path = tmp_path / setup_path - df = tm.makeDataFrame() + df = DataFrame(range(20)) df.iloc[:10].to_hdf(path, key="df", append=True) df.iloc[10:].to_hdf(path, key="df", append=True, format="table") tm.assert_frame_equal(read_hdf(path, "df"), df) @@ -93,7 +94,7 @@ def test_api_append(tmp_path, setup_path): def test_api_2(tmp_path, setup_path): path = tmp_path / setup_path - df = tm.makeDataFrame() + df = DataFrame(range(20)) df.to_hdf(path, key="df", append=False, format="fixed") tm.assert_frame_equal(read_hdf(path, "df"), df) @@ -107,7 +108,7 @@ def test_api_2(tmp_path, setup_path): tm.assert_frame_equal(read_hdf(path, "df"), df) with ensure_clean_store(setup_path) as store: - df = tm.makeDataFrame() + df = DataFrame(range(20)) _maybe_remove(store, "df") store.append("df", df.iloc[:10], append=True, format="table") diff --git a/pandas/tests/io/pytables/test_store.py b/pandas/tests/io/pytables/test_store.py index 96c160ab40bd8..df8a1e3cb7470 100644 --- a/pandas/tests/io/pytables/test_store.py +++ b/pandas/tests/io/pytables/test_store.py @@ -956,10 +956,6 @@ def test_to_hdf_with_object_column_names(tmp_path, setup_path): tm.makeTimedeltaIndex, tm.makePeriodIndex, ] - types_should_run = [ - tm.makeStringIndex, - tm.makeCategoricalIndex, - ] for index in types_should_fail: df = DataFrame( @@ -970,14 +966,18 @@ def test_to_hdf_with_object_column_names(tmp_path, setup_path): with pytest.raises(ValueError, match=msg): df.to_hdf(path, key="df", format="table", data_columns=True) - for index in types_should_run: - df = DataFrame( - np.random.default_rng(2).standard_normal((10, 2)), columns=index(2) - ) - path = tmp_path / setup_path - df.to_hdf(path, key="df", format="table", data_columns=True) - result = read_hdf(path, "df", where=f"index = [{df.index[0]}]") - assert len(result) + +@pytest.mark.parametrize("dtype", [None, "category"]) +def test_to_hdf_with_object_column_names_should_run(tmp_path, setup_path, dtype): + # GH9057 + df = DataFrame( + np.random.default_rng(2).standard_normal((10, 2)), + columns=Index(["a", "b"], dtype=dtype), + ) + path = tmp_path / setup_path + df.to_hdf(path, key="df", format="table", data_columns=True) + result = read_hdf(path, "df", where=f"index = [{df.index[0]}]") + assert len(result) def test_hdfstore_strides(setup_path): diff --git a/pandas/tests/reductions/test_reductions.py b/pandas/tests/reductions/test_reductions.py index 9c4ae92224148..303f8550c5a80 100644 --- a/pandas/tests/reductions/test_reductions.py +++ b/pandas/tests/reductions/test_reductions.py @@ -33,13 +33,13 @@ def get_objs(): indexes = [ - tm.makeBoolIndex(10, name="a"), + Index([True, False] * 5, name="a"), tm.makeIntIndex(10, name="a"), tm.makeFloatIndex(10, name="a"), tm.makeDateIndex(10, name="a"), tm.makeDateIndex(10, name="a").tz_localize(tz="US/Eastern"), tm.makePeriodIndex(10, name="a"), - tm.makeStringIndex(10, name="a"), + Index([str(i) for i in range(10)], name="a"), ] arr = np.random.default_rng(2).standard_normal(10) diff --git a/pandas/tests/resample/test_time_grouper.py b/pandas/tests/resample/test_time_grouper.py index 1dc25cb9d4c1e..3d9098917a12d 100644 --- a/pandas/tests/resample/test_time_grouper.py +++ b/pandas/tests/resample/test_time_grouper.py @@ -87,19 +87,17 @@ def f(df): @pytest.mark.parametrize( - "func", + "index", [ - tm.makeIntIndex, - tm.makeStringIndex, - tm.makeFloatIndex, - (lambda m: tm.makeCustomIndex(m, 2)), + Index([1, 2]), + Index(["a", "b"]), + Index([1.1, 2.2]), + pd.MultiIndex.from_arrays([[1, 2], ["a", "b"]]), ], ) -def test_fails_on_no_datetime_index(func): - n = 2 - index = func(n) +def test_fails_on_no_datetime_index(index): name = type(index).__name__ - df = DataFrame({"a": np.random.default_rng(2).standard_normal(n)}, index=index) + df = DataFrame({"a": range(len(index))}, index=index) msg = ( "Only valid with DatetimeIndex, TimedeltaIndex " diff --git a/pandas/tests/reshape/merge/test_multi.py b/pandas/tests/reshape/merge/test_multi.py index f5d78fbd44812..269d3a2b7078e 100644 --- a/pandas/tests/reshape/merge/test_multi.py +++ b/pandas/tests/reshape/merge/test_multi.py @@ -188,7 +188,7 @@ def test_merge_multiple_cols_with_mixed_cols_index(self): def test_compress_group_combinations(self): # ~ 40000000 possible unique groups - key1 = tm.makeStringIndex(10000) + key1 = [str(i) for i in range(10000)] key1 = np.tile(key1, 2) key2 = key1[::-1] diff --git a/pandas/tests/series/methods/test_combine_first.py b/pandas/tests/series/methods/test_combine_first.py index 795b2eab82aca..1cbb7c7982802 100644 --- a/pandas/tests/series/methods/test_combine_first.py +++ b/pandas/tests/series/methods/test_combine_first.py @@ -51,9 +51,9 @@ def test_combine_first(self): tm.assert_series_equal(combined[1::2], series_copy[1::2]) # mixed types - index = tm.makeStringIndex(20) + index = pd.Index([str(i) for i in range(20)]) floats = Series(np.random.default_rng(2).standard_normal(20), index=index) - strings = Series(tm.makeStringIndex(10), index=index[::2], dtype=object) + strings = Series([str(i) for i in range(10)], index=index[::2], dtype=object) combined = strings.combine_first(floats) diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index a39b3ff7e6f2b..c29fe6ba06ab4 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -68,7 +68,7 @@ def test_tab_completion_with_categorical(self): @pytest.mark.parametrize( "index", [ - tm.makeStringIndex(10), + Index([str(i) for i in range(10)]), tm.makeCategoricalIndex(10), Index(["foo", "bar", "baz"] * 2), tm.makeDateIndex(10), diff --git a/pandas/tests/test_algos.py b/pandas/tests/test_algos.py index a6e63dfd5f409..24c4706810154 100644 --- a/pandas/tests/test_algos.py +++ b/pandas/tests/test_algos.py @@ -1663,19 +1663,18 @@ def test_unique_complex_numbers(self, array, expected): class TestHashTable: @pytest.mark.parametrize( - "htable, tm_dtype", + "htable, data", [ - (ht.PyObjectHashTable, "String"), - (ht.StringHashTable, "String"), - (ht.Float64HashTable, "Float"), - (ht.Int64HashTable, "Int"), - (ht.UInt64HashTable, "UInt"), + (ht.PyObjectHashTable, [f"foo_{i}" for i in range(1000)]), + (ht.StringHashTable, [f"foo_{i}" for i in range(1000)]), + (ht.Float64HashTable, np.arange(1000, dtype=np.float64)), + (ht.Int64HashTable, np.arange(1000, dtype=np.int64)), + (ht.UInt64HashTable, np.arange(1000, dtype=np.uint64)), ], ) - def test_hashtable_unique(self, htable, tm_dtype, writable): + def test_hashtable_unique(self, htable, data, writable): # output of maker has guaranteed unique elements - maker = getattr(tm, "make" + tm_dtype + "Index") - s = Series(maker(1000)) + s = Series(data) if htable == ht.Float64HashTable: # add NaN for float column s.loc[500] = np.nan @@ -1703,19 +1702,18 @@ def test_hashtable_unique(self, htable, tm_dtype, writable): tm.assert_numpy_array_equal(reconstr, s_duplicated.values) @pytest.mark.parametrize( - "htable, tm_dtype", + "htable, data", [ - (ht.PyObjectHashTable, "String"), - (ht.StringHashTable, "String"), - (ht.Float64HashTable, "Float"), - (ht.Int64HashTable, "Int"), - (ht.UInt64HashTable, "UInt"), + (ht.PyObjectHashTable, [f"foo_{i}" for i in range(1000)]), + (ht.StringHashTable, [f"foo_{i}" for i in range(1000)]), + (ht.Float64HashTable, np.arange(1000, dtype=np.float64)), + (ht.Int64HashTable, np.arange(1000, dtype=np.int64)), + (ht.UInt64HashTable, np.arange(1000, dtype=np.uint64)), ], ) - def test_hashtable_factorize(self, htable, tm_dtype, writable): + def test_hashtable_factorize(self, htable, writable, data): # output of maker has guaranteed unique elements - maker = getattr(tm, "make" + tm_dtype + "Index") - s = Series(maker(1000)) + s = Series(data) if htable == ht.Float64HashTable: # add NaN for float column s.loc[500] = np.nan diff --git a/pandas/tests/tseries/frequencies/test_inference.py b/pandas/tests/tseries/frequencies/test_inference.py index 75528a8b99c4d..5d22896d9d055 100644 --- a/pandas/tests/tseries/frequencies/test_inference.py +++ b/pandas/tests/tseries/frequencies/test_inference.py @@ -401,7 +401,7 @@ def test_invalid_index_types_unicode(): msg = "Unknown datetime string format" with pytest.raises(ValueError, match=msg): - frequencies.infer_freq(tm.makeStringIndex(10)) + frequencies.infer_freq(Index(["ZqgszYBfuL"])) def test_string_datetime_like_compat(): diff --git a/pandas/tests/util/test_hashing.py b/pandas/tests/util/test_hashing.py index 00dc184a0ac4d..e7c4c27714d5f 100644 --- a/pandas/tests/util/test_hashing.py +++ b/pandas/tests/util/test_hashing.py @@ -328,9 +328,9 @@ def test_alternate_encoding(index): @pytest.mark.parametrize("l_add", [0, 1]) def test_same_len_hash_collisions(l_exp, l_add): length = 2 ** (l_exp + 8) + l_add - s = tm.makeStringIndex(length).to_numpy() + idx = np.array([str(i) for i in range(length)], dtype=object) - result = hash_array(s, "utf8") + result = hash_array(idx, "utf8") assert not result[0] == result[1] From 6f080bd1ccf9d761aa720ea30333ba784cf59f8e Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Mon, 27 Nov 2023 01:15:40 -1000 Subject: [PATCH 29/30] TST/CLN: Remove makeMixedDataFrame and getMixedTypeDict (#56202) --- pandas/_testing/__init__.py | 19 ------------------- pandas/tests/frame/methods/test_transpose.py | 14 ++++++++++++-- pandas/tests/io/pytables/test_append.py | 9 ++++++++- pandas/tests/io/pytables/test_store.py | 9 ++++++++- pandas/tests/reshape/merge/test_join.py | 10 ++++++++-- pandas/tests/series/methods/test_map.py | 18 ++++++++++++++++-- pandas/tests/util/test_hashing.py | 18 ++++++++++++++++-- 7 files changed, 68 insertions(+), 29 deletions(-) diff --git a/pandas/_testing/__init__.py b/pandas/_testing/__init__.py index c73d869b6c39c..14ee29d24800e 100644 --- a/pandas/_testing/__init__.py +++ b/pandas/_testing/__init__.py @@ -482,23 +482,6 @@ def makeDataFrame() -> DataFrame: return DataFrame(data) -def getMixedTypeDict(): - index = Index(["a", "b", "c", "d", "e"]) - - data = { - "A": [0.0, 1.0, 2.0, 3.0, 4.0], - "B": [0.0, 1.0, 0.0, 1.0, 0.0], - "C": ["foo1", "foo2", "foo3", "foo4", "foo5"], - "D": bdate_range("1/1/2009", periods=5), - } - - return index, data - - -def makeMixedDataFrame() -> DataFrame: - return DataFrame(getMixedTypeDict()[1]) - - def makeCustomIndex( nentries, nlevels, @@ -1026,7 +1009,6 @@ def shares_memory(left, right) -> bool: "get_dtype", "getitem", "get_locales", - "getMixedTypeDict", "get_finest_unit", "get_obj", "get_op_from_name", @@ -1042,7 +1024,6 @@ def shares_memory(left, right) -> bool: "makeDateIndex", "makeFloatIndex", "makeIntIndex", - "makeMixedDataFrame", "makeNumericIndex", "makeObjectSeries", "makePeriodIndex", diff --git a/pandas/tests/frame/methods/test_transpose.py b/pandas/tests/frame/methods/test_transpose.py index 93a2f8d80019c..d0caa071fae1c 100644 --- a/pandas/tests/frame/methods/test_transpose.py +++ b/pandas/tests/frame/methods/test_transpose.py @@ -6,9 +6,11 @@ from pandas import ( DataFrame, DatetimeIndex, + Index, IntervalIndex, Series, Timestamp, + bdate_range, date_range, timedelta_range, ) @@ -108,9 +110,17 @@ def test_transpose_float(self, float_frame): else: assert value == frame[col][idx] + def test_transpose_mixed(self): # mixed type - index, data = tm.getMixedTypeDict() - mixed = DataFrame(data, index=index) + mixed = DataFrame( + { + "A": [0.0, 1.0, 2.0, 3.0, 4.0], + "B": [0.0, 1.0, 0.0, 1.0, 0.0], + "C": ["foo1", "foo2", "foo3", "foo4", "foo5"], + "D": bdate_range("1/1/2009", periods=5), + }, + index=Index(["a", "b", "c", "d", "e"], dtype=object), + ) mixed_T = mixed.T for col, s in mixed_T.items(): diff --git a/pandas/tests/io/pytables/test_append.py b/pandas/tests/io/pytables/test_append.py index 50cf7d737eb99..89a0162af1c54 100644 --- a/pandas/tests/io/pytables/test_append.py +++ b/pandas/tests/io/pytables/test_append.py @@ -397,7 +397,14 @@ def check_col(key, name, size): store.append("df_new", df_new) # min_itemsize on Series index (GH 11412) - df = tm.makeMixedDataFrame().set_index("C") + df = DataFrame( + { + "A": [0.0, 1.0, 2.0, 3.0, 4.0], + "B": [0.0, 1.0, 0.0, 1.0, 0.0], + "C": pd.Index(["foo1", "foo2", "foo3", "foo4", "foo5"], dtype=object), + "D": date_range("20130101", periods=5), + } + ).set_index("C") store.append("ss", df["B"], min_itemsize={"index": 4}) tm.assert_series_equal(store.select("ss"), df["B"]) diff --git a/pandas/tests/io/pytables/test_store.py b/pandas/tests/io/pytables/test_store.py index df8a1e3cb7470..5b16ea9ee6b09 100644 --- a/pandas/tests/io/pytables/test_store.py +++ b/pandas/tests/io/pytables/test_store.py @@ -323,7 +323,14 @@ def test_to_hdf_with_min_itemsize(tmp_path, setup_path): path = tmp_path / setup_path # min_itemsize in index with to_hdf (GH 10381) - df = tm.makeMixedDataFrame().set_index("C") + df = DataFrame( + { + "A": [0.0, 1.0, 2.0, 3.0, 4.0], + "B": [0.0, 1.0, 0.0, 1.0, 0.0], + "C": Index(["foo1", "foo2", "foo3", "foo4", "foo5"], dtype=object), + "D": date_range("20130101", periods=5), + } + ).set_index("C") df.to_hdf(path, key="ss3", format="table", min_itemsize={"index": 6}) # just make sure there is a longer string: df2 = df.copy().reset_index().assign(C="longer").set_index("C") diff --git a/pandas/tests/reshape/merge/test_join.py b/pandas/tests/reshape/merge/test_join.py index c4c83e2046b76..f07c223bf0de2 100644 --- a/pandas/tests/reshape/merge/test_join.py +++ b/pandas/tests/reshape/merge/test_join.py @@ -11,6 +11,7 @@ MultiIndex, Series, Timestamp, + bdate_range, concat, merge, ) @@ -57,8 +58,13 @@ def df2(self): @pytest.fixture def target_source(self): - index, data = tm.getMixedTypeDict() - target = DataFrame(data, index=index) + data = { + "A": [0.0, 1.0, 2.0, 3.0, 4.0], + "B": [0.0, 1.0, 0.0, 1.0, 0.0], + "C": ["foo1", "foo2", "foo3", "foo4", "foo5"], + "D": bdate_range("1/1/2009", periods=5), + } + target = DataFrame(data, index=Index(["a", "b", "c", "d", "e"], dtype=object)) # Join on string value diff --git a/pandas/tests/series/methods/test_map.py b/pandas/tests/series/methods/test_map.py index f86f6069a2ef3..7b14e1289abf0 100644 --- a/pandas/tests/series/methods/test_map.py +++ b/pandas/tests/series/methods/test_map.py @@ -14,6 +14,7 @@ Index, MultiIndex, Series, + bdate_range, isna, timedelta_range, ) @@ -154,8 +155,13 @@ def test_list_raises(string_series): string_series.map([lambda x: x]) -def test_map(datetime_series): - index, data = tm.getMixedTypeDict() +def test_map(): + data = { + "A": [0.0, 1.0, 2.0, 3.0, 4.0], + "B": [0.0, 1.0, 0.0, 1.0, 0.0], + "C": ["foo1", "foo2", "foo3", "foo4", "foo5"], + "D": bdate_range("1/1/2009", periods=5), + } source = Series(data["B"], index=data["C"]) target = Series(data["C"][:4], index=data["D"][:4]) @@ -171,10 +177,14 @@ def test_map(datetime_series): for k, v in merged.items(): assert v == source[target[k]] + +def test_map_datetime(datetime_series): # function result = datetime_series.map(lambda x: x * 2) tm.assert_series_equal(result, datetime_series * 2) + +def test_map_category(): # GH 10324 a = Series([1, 2, 3, 4]) b = Series(["even", "odd", "even", "odd"], dtype="category") @@ -185,6 +195,8 @@ def test_map(datetime_series): exp = Series(["odd", "even", "odd", np.nan]) tm.assert_series_equal(a.map(c), exp) + +def test_map_category_numeric(): a = Series(["a", "b", "c", "d"]) b = Series([1, 2, 3, 4], index=pd.CategoricalIndex(["b", "c", "d", "e"])) c = Series([1, 2, 3, 4], index=Index(["b", "c", "d", "e"])) @@ -194,6 +206,8 @@ def test_map(datetime_series): exp = Series([np.nan, 1, 2, 3]) tm.assert_series_equal(a.map(c), exp) + +def test_map_category_string(): a = Series(["a", "b", "c", "d"]) b = Series( ["B", "C", "D", "E"], diff --git a/pandas/tests/util/test_hashing.py b/pandas/tests/util/test_hashing.py index e7c4c27714d5f..5f1d905aa4a46 100644 --- a/pandas/tests/util/test_hashing.py +++ b/pandas/tests/util/test_hashing.py @@ -136,7 +136,14 @@ def test_multiindex_objects(): DataFrame({"x": ["a", "b", "c"], "y": [1, 2, 3]}), DataFrame(), DataFrame(np.full((10, 4), np.nan)), - tm.makeMixedDataFrame(), + DataFrame( + { + "A": [0.0, 1.0, 2.0, 3.0, 4.0], + "B": [0.0, 1.0, 0.0, 1.0, 0.0], + "C": Index(["foo1", "foo2", "foo3", "foo4", "foo5"], dtype=object), + "D": pd.date_range("20130101", periods=5), + } + ), tm.makeTimeDataFrame(), tm.makeTimeSeries(), Series(tm.makePeriodIndex()), @@ -162,7 +169,14 @@ def test_hash_pandas_object(obj, index): Series([True, False, True]), DataFrame({"x": ["a", "b", "c"], "y": [1, 2, 3]}), DataFrame(np.full((10, 4), np.nan)), - tm.makeMixedDataFrame(), + DataFrame( + { + "A": [0.0, 1.0, 2.0, 3.0, 4.0], + "B": [0.0, 1.0, 0.0, 1.0, 0.0], + "C": Index(["foo1", "foo2", "foo3", "foo4", "foo5"], dtype=object), + "D": pd.date_range("20130101", periods=5), + } + ), tm.makeTimeDataFrame(), tm.makeTimeSeries(), Series(tm.makePeriodIndex()), From 368643f6af89a8aa023b1a457ffa33b7e35e653a Mon Sep 17 00:00:00 2001 From: Patrick Hoefler <61934744+phofl@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:22:16 +0100 Subject: [PATCH 30/30] DEPR: Deprecate Series.view (#56054) Co-authored-by: Joris Van den Bossche --- doc/source/whatsnew/v2.2.0.rst | 1 + pandas/core/series.py | 42 ++++++----------------- pandas/core/window/ewm.py | 3 ++ pandas/io/stata.py | 4 +-- pandas/tests/copy_view/test_methods.py | 3 +- pandas/tests/frame/indexing/test_where.py | 3 +- pandas/tests/frame/test_constructors.py | 4 +-- pandas/tests/generic/test_finalize.py | 5 --- pandas/tests/groupby/test_cumulative.py | 2 +- pandas/tests/groupby/test_timegrouper.py | 2 +- pandas/tests/io/json/test_pandas.py | 2 +- pandas/tests/io/test_sql.py | 2 +- pandas/tests/series/methods/test_view.py | 4 +++ pandas/tests/series/test_constructors.py | 2 +- pandas/tests/test_algos.py | 2 +- pandas/tests/test_nanops.py | 4 +++ 16 files changed, 37 insertions(+), 48 deletions(-) diff --git a/doc/source/whatsnew/v2.2.0.rst b/doc/source/whatsnew/v2.2.0.rst index 9680ccf409564..b30a11dca7c57 100644 --- a/doc/source/whatsnew/v2.2.0.rst +++ b/doc/source/whatsnew/v2.2.0.rst @@ -362,6 +362,7 @@ Other Deprecations - Deprecated :func:`read_gbq` and :meth:`DataFrame.to_gbq`. Use ``pandas_gbq.read_gbq`` and ``pandas_gbq.to_gbq`` instead https://pandas-gbq.readthedocs.io/en/latest/api.html (:issue:`55525`) - Deprecated :meth:`.DataFrameGroupBy.fillna` and :meth:`.SeriesGroupBy.fillna`; use :meth:`.DataFrameGroupBy.ffill`, :meth:`.DataFrameGroupBy.bfill` for forward and backward filling or :meth:`.DataFrame.fillna` to fill with a single value (or the Series equivalents) (:issue:`55718`) - Deprecated :meth:`Index.format`, use ``index.astype(str)`` or ``index.map(formatter)`` instead (:issue:`55413`) +- Deprecated :meth:`Series.view`, use ``astype`` instead to change the dtype (:issue:`20251`) - Deprecated ``core.internals`` members ``Block``, ``ExtensionBlock``, and ``DatetimeTZBlock``, use public APIs instead (:issue:`55139`) - Deprecated ``year``, ``month``, ``quarter``, ``day``, ``hour``, ``minute``, and ``second`` keywords in the :class:`PeriodIndex` constructor, use :meth:`PeriodIndex.from_fields` instead (:issue:`55960`) - Deprecated allowing non-integer ``periods`` argument in :func:`date_range`, :func:`timedelta_range`, :func:`period_range`, and :func:`interval_range` (:issue:`56036`) diff --git a/pandas/core/series.py b/pandas/core/series.py index a9679f22f9933..6b06786549ade 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -877,6 +877,10 @@ def view(self, dtype: Dtype | None = None) -> Series: """ Create a new view of the Series. + .. deprecated:: 2.2.0 + ``Series.view`` is deprecated and will be removed in a future version. + Use :meth:`Series.astype` as an alternative to change the dtype. + This function will return a new Series with a view of the same underlying values in memory, optionally reinterpreted with a new data type. The new data type must preserve the same size in bytes as to not @@ -907,38 +911,14 @@ def view(self, dtype: Dtype | None = None) -> Series: Examples -------- - >>> s = pd.Series([-2, -1, 0, 1, 2], dtype='int8') - >>> s - 0 -2 - 1 -1 - 2 0 - 3 1 - 4 2 - dtype: int8 - - The 8 bit signed integer representation of `-1` is `0b11111111`, but - the same bytes represent 255 if read as an 8 bit unsigned integer: - - >>> us = s.view('uint8') - >>> us - 0 254 - 1 255 - 2 0 - 3 1 - 4 2 - dtype: uint8 - - The views share the same underlying values: - - >>> us[0] = 128 - >>> s - 0 -128 - 1 -1 - 2 0 - 3 1 - 4 2 - dtype: int8 + Use ``astype`` to change the dtype instead. """ + warnings.warn( + "Series.view is deprecated and will be removed in a future version. " + "Use ``astype`` as an alternative to change the dtype.", + FutureWarning, + stacklevel=2, + ) # self.array instead of self._values so we piggyback on NumpyExtensionArray # implementation res_values = self.array.view(dtype) diff --git a/pandas/core/window/ewm.py b/pandas/core/window/ewm.py index 82b4746aa57a5..db659713c6f16 100644 --- a/pandas/core/window/ewm.py +++ b/pandas/core/window/ewm.py @@ -15,6 +15,7 @@ is_datetime64_ns_dtype, is_numeric_dtype, ) +from pandas.core.dtypes.generic import ABCSeries from pandas.core.dtypes.missing import isna from pandas.core import common @@ -118,6 +119,8 @@ def _calculate_deltas( np.ndarray Diff of the times divided by the half-life """ + if isinstance(times, ABCSeries): + times = times._values _times = np.asarray(times.view(np.int64), dtype=np.float64) # TODO: generalize to non-nano? _halflife = float(Timedelta(halflife).as_unit("ns")._value) diff --git a/pandas/io/stata.py b/pandas/io/stata.py index 70294e8a62cca..1eb8f531dc62a 100644 --- a/pandas/io/stata.py +++ b/pandas/io/stata.py @@ -429,9 +429,9 @@ def parse_dates_safe( d["year"] = date_index._data.year d["month"] = date_index._data.month if days: - days_in_ns = dates.view(np.int64) - to_datetime( + days_in_ns = dates._values.view(np.int64) - to_datetime( d["year"], format="%Y" - ).view(np.int64) + )._values.view(np.int64) d["days"] = days_in_ns // NS_PER_DAY elif infer_dtype(dates, skipna=False) == "datetime": diff --git a/pandas/tests/copy_view/test_methods.py b/pandas/tests/copy_view/test_methods.py index 6416dd50daddc..3fd86f916c771 100644 --- a/pandas/tests/copy_view/test_methods.py +++ b/pandas/tests/copy_view/test_methods.py @@ -1934,7 +1934,8 @@ def test_series_view(using_copy_on_write, warn_copy_on_write): ser = Series([1, 2, 3]) ser_orig = ser.copy() - ser2 = ser.view() + with tm.assert_produces_warning(FutureWarning, match="is deprecated"): + ser2 = ser.view() assert np.shares_memory(get_array(ser), get_array(ser2)) if using_copy_on_write: assert not ser2._mgr._has_no_reference(0) diff --git a/pandas/tests/frame/indexing/test_where.py b/pandas/tests/frame/indexing/test_where.py index 4576a86ad27cd..6b2bf211ab748 100644 --- a/pandas/tests/frame/indexing/test_where.py +++ b/pandas/tests/frame/indexing/test_where.py @@ -771,7 +771,8 @@ def test_where_datetimelike_noop(self, dtype): # GH#45135, analogue to GH#44181 for Period don't raise on no-op # For td64/dt64/dt64tz we already don't raise, but also are # checking that we don't unnecessarily upcast to object. - ser = Series(np.arange(3) * 10**9, dtype=np.int64).view(dtype) + with tm.assert_produces_warning(FutureWarning, match="is deprecated"): + ser = Series(np.arange(3) * 10**9, dtype=np.int64).view(dtype) df = ser.to_frame() mask = np.array([False, False, False]) diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index 3111075c5c1a7..2cca87adc5189 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -1101,8 +1101,8 @@ def test_constructor_maskedarray_nonfloat(self): mat2[0, 0] = 1 mat2[1, 2] = 2 frame = DataFrame(mat2, columns=["A", "B", "C"], index=[1, 2]) - assert 1 == frame["A"].view("i8")[1] - assert 2 == frame["C"].view("i8")[2] + assert 1 == frame["A"].astype("i8")[1] + assert 2 == frame["C"].astype("i8")[2] # masked bool promoted to object mat = ma.masked_all((2, 3), dtype=bool) diff --git a/pandas/tests/generic/test_finalize.py b/pandas/tests/generic/test_finalize.py index 68746b9e9a803..866e9e203ffe3 100644 --- a/pandas/tests/generic/test_finalize.py +++ b/pandas/tests/generic/test_finalize.py @@ -31,11 +31,6 @@ # - Callable: pass the constructed value with attrs set to this. _all_methods = [ - ( - pd.Series, - (np.array([0], dtype="float64")), - operator.methodcaller("view", "int64"), - ), (pd.Series, ([0],), operator.methodcaller("take", [])), (pd.Series, ([0],), operator.methodcaller("__getitem__", [True])), (pd.Series, ([0],), operator.methodcaller("repeat", 2)), diff --git a/pandas/tests/groupby/test_cumulative.py b/pandas/tests/groupby/test_cumulative.py index 25534865b3486..bf572609f3d37 100644 --- a/pandas/tests/groupby/test_cumulative.py +++ b/pandas/tests/groupby/test_cumulative.py @@ -216,7 +216,7 @@ def test_cummax_i8_at_implementation_bound(): # the minimum value used to be treated as NPY_NAT+1 instead of NPY_NAT # for int64 dtype GH#46382 ser = Series([pd.NaT._value + n for n in range(5)]) - df = DataFrame({"A": 1, "B": ser, "C": ser.view("M8[ns]")}) + df = DataFrame({"A": 1, "B": ser, "C": ser._values.view("M8[ns]")}) gb = df.groupby("A") res = gb.cummax() diff --git a/pandas/tests/groupby/test_timegrouper.py b/pandas/tests/groupby/test_timegrouper.py index b79bed32e7fa4..b8b34d365d051 100644 --- a/pandas/tests/groupby/test_timegrouper.py +++ b/pandas/tests/groupby/test_timegrouper.py @@ -726,7 +726,7 @@ def test_groupby_groups_periods(self): def test_groupby_first_datetime64(self): df = DataFrame([(1, 1351036800000000000), (2, 1351036800000000000)]) - df[1] = df[1].view("M8[ns]") + df[1] = df[1].astype("M8[ns]") assert issubclass(df[1].dtype.type, np.datetime64) diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index 7312facc44c26..411cc90ba41a7 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -168,7 +168,7 @@ def test_frame_non_unique_columns(self, orient, data): # in milliseconds; these are internally stored in nanosecond, # so divide to get where we need # TODO: a to_epoch method would also solve; see GH 14772 - expected.iloc[:, 0] = expected.iloc[:, 0].view(np.int64) // 1000000 + expected.iloc[:, 0] = expected.iloc[:, 0].astype(np.int64) // 1000000 elif orient == "split": expected = df expected.columns = ["x", "x.1"] diff --git a/pandas/tests/io/test_sql.py b/pandas/tests/io/test_sql.py index 45d0a839b9e1a..d7c69ff17749c 100644 --- a/pandas/tests/io/test_sql.py +++ b/pandas/tests/io/test_sql.py @@ -1908,7 +1908,7 @@ def test_api_timedelta(conn, request): name="foo", ) else: - expected = df["foo"].view("int64") + expected = df["foo"].astype("int64") tm.assert_series_equal(result["foo"], expected) diff --git a/pandas/tests/series/methods/test_view.py b/pandas/tests/series/methods/test_view.py index a9b29c9329193..7e0ac372cd443 100644 --- a/pandas/tests/series/methods/test_view.py +++ b/pandas/tests/series/methods/test_view.py @@ -9,6 +9,10 @@ ) import pandas._testing as tm +pytestmark = pytest.mark.filterwarnings( + "ignore:Series.view is deprecated and will be removed in a future version.:FutureWarning" # noqa: E501 +) + class TestView: def test_view_i8_to_datetimelike(self): diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 84c612c43da29..4281610b28951 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -969,7 +969,7 @@ def test_constructor_dtype_datetime64_10(self): # GH3414 related expected = Series(pydates, dtype="datetime64[ms]") - result = Series(Series(dates).view(np.int64) / 1000000, dtype="M8[ms]") + result = Series(Series(dates).astype(np.int64) / 1000000, dtype="M8[ms]") tm.assert_series_equal(result, expected) result = Series(dates, dtype="datetime64[ms]") diff --git a/pandas/tests/test_algos.py b/pandas/tests/test_algos.py index 24c4706810154..e5302ec9833f1 100644 --- a/pandas/tests/test_algos.py +++ b/pandas/tests/test_algos.py @@ -976,7 +976,7 @@ def test_isin_datetimelike_values_numeric_comps(self, dtype, dtype1): # Anything but object and we get all-False shortcut dta = date_range("2013-01-01", periods=3)._values - arr = Series(dta.view("i8")).view(dtype1)._values + arr = Series(dta.view("i8")).array.view(dtype1) comps = arr.view("i8").astype(dtype) diff --git a/pandas/tests/test_nanops.py b/pandas/tests/test_nanops.py index a0062d2b6dd44..632d9783c7f81 100644 --- a/pandas/tests/test_nanops.py +++ b/pandas/tests/test_nanops.py @@ -1114,12 +1114,16 @@ def test_nanmean(self, unit): expected = dti[1] for obj in [dti, DatetimeArray(dti), Series(dti)]: + if isinstance(obj, Series): + obj = obj._values result = nanops.nanmean(obj) assert result == expected dti2 = dti.insert(1, pd.NaT) for obj in [dti2, DatetimeArray(dti2), Series(dti2)]: + if isinstance(obj, Series): + obj = obj._values result = nanops.nanmean(obj) assert result == expected