From cf910571bdaa8cced245b8549b88f01c0a78c954 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Wed, 29 Dec 2021 21:42:37 -0800 Subject: [PATCH 1/5] parameterize more --- pandas/tests/indexing/test_scalar.py | 50 ++++++++++++++++------------ 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/pandas/tests/indexing/test_scalar.py b/pandas/tests/indexing/test_scalar.py index bcb76fb078e74..d5268a1b1bc81 100644 --- a/pandas/tests/indexing/test_scalar.py +++ b/pandas/tests/indexing/test_scalar.py @@ -47,31 +47,39 @@ def _check(f, func, values=False): _check(f, "at") @pytest.mark.parametrize("kind", ["series", "frame"]) - def test_at_and_iat_set(self, kind): - def _check(f, func, values=False): + @pytest.mark.parametrize("col", ["ints", "uints"]) + def test_iat_set_ints(self, kind, col): + f = getattr(self, kind)[col] + if f is not None: + indices = self.generate_indices(f, True) + for i in indices: + f.iat[i] = 1 + expected = self.get_value("iat", f, i, True) + tm.assert_almost_equal(expected, 1) - if f is not None: - indices = self.generate_indices(f, values) + @pytest.mark.parametrize("kind", ["series", "frame"]) + @pytest.mark.parametrize("col", ["labels", "ts", "floats"]) + def test_iat_set_other(self, kind, col): + f = getattr(self, kind)[col] + if f is not None: + msg = "iAt based indexing can only have integer indexers" + with pytest.raises(ValueError, match=msg): + indices = self.generate_indices(f, False) for i in indices: - getattr(f, func)[i] = 1 - expected = self.get_value(func, f, i, values) + f.iat[i] = 1 + expected = self.get_value("iat", f, i, False) tm.assert_almost_equal(expected, 1) - d = getattr(self, kind) - - # iat - for f in [d["ints"], d["uints"]]: - _check(f, "iat", values=True) - - for f in [d["labels"], d["ts"], d["floats"]]: - if f is not None: - msg = "iAt based indexing can only have integer indexers" - with pytest.raises(ValueError, match=msg): - _check(f, "iat") - - # at - for f in [d["ints"], d["uints"], d["labels"], d["ts"], d["floats"]]: - _check(f, "at") + @pytest.mark.parametrize("kind", ["series", "frame"]) + @pytest.mark.parametrize("col", ["ints", "uints", "labels", "ts", "floats"]) + def test_at_set_ints_other(self, kind, col): + f = getattr(self, kind)[col] + if f is not None: + indices = self.generate_indices(f, False) + for i in indices: + f.at[i] = 1 + expected = self.get_value("at", f, i, False) + tm.assert_almost_equal(expected, 1) class TestAtAndiAT: From 550fb05d3cf5c6ed500b1981cb5c976b9074b45b Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Thu, 30 Dec 2021 10:50:16 -0800 Subject: [PATCH 2/5] Another parameterize --- pandas/tests/arithmetic/test_datetime64.py | 89 ++++++++++------------ 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index e76b558f4c669..7ea515854608f 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -1810,55 +1810,50 @@ def test_dt64ser_sub_datetime_dtype(self): # TODO: This next block of tests came from tests.series.test_operators, # needs to be de-duplicated and parametrized over `box` classes - def test_operators_datetimelike_invalid(self, all_arithmetic_operators): - # these are all TypeEror ops + @pytest.mark.parametrize( + "left, right, op_fail", + [ + [ + [Timestamp("20111230"), Timestamp("20120101"), NaT], + [Timestamp("20111231"), Timestamp("20120102"), Timestamp("20120104")], + ["__sub__", "__rsub__"], + ], + [ + [Timestamp("20111230"), Timestamp("20120101"), NaT], + [timedelta(minutes=5, seconds=3), timedelta(minutes=5, seconds=3), NaT], + ["__add__", "__radd__", "__sub__"], + ], # TODO(jreback) __rsub__ should raise? + [ + [ + Timestamp("20111230", tz="US/Eastern"), + Timestamp("20111230", tz="US/Eastern"), + NaT, + ], + [timedelta(minutes=5, seconds=3), NaT, timedelta(minutes=5, seconds=3)], + ["__add__", "__radd__", "__sub__"], + ], + ], + ) + def test_operators_datetimelike_invalid( + self, request, left, right, op_fail, all_arithmetic_operators + ): + # these are all TypeError ops op_str = all_arithmetic_operators - - def check(get_ser, test_ser): - - # check that we are getting a TypeError - # with 'operate' (from core/ops.py) for the ops that are not - # defined - op = getattr(get_ser, op_str, None) - # Previously, _validate_for_numeric_binop in core/indexes/base.py - # did this for us. - with pytest.raises( - TypeError, match="operate|[cC]annot|unsupported operand" - ): - op(test_ser) - - # ## timedelta64 ### - td1 = Series([timedelta(minutes=5, seconds=3)] * 3) - td1.iloc[2] = np.nan - - # ## datetime64 ### - dt1 = Series( - [Timestamp("20111230"), Timestamp("20120101"), Timestamp("20120103")] - ) - dt1.iloc[2] = np.nan - dt2 = Series( - [Timestamp("20111231"), Timestamp("20120102"), Timestamp("20120104")] + request.node.add_marker( + pytest.mark.xfail( + op_str in op_fail, reason=f"{op_str} doesn't raise TypeError" + ) ) - if op_str not in ["__sub__", "__rsub__"]: - check(dt1, dt2) - - # ## datetime64 with timetimedelta ### - # TODO(jreback) __rsub__ should raise? - if op_str not in ["__add__", "__radd__", "__sub__"]: - check(dt1, td1) - - # 8260, 10763 - # datetime64 with tz - tz = "US/Eastern" - dt1 = Series(date_range("2000-01-01 09:00:00", periods=5, tz=tz), name="foo") - dt2 = dt1.copy() - dt2.iloc[2] = np.nan - td1 = Series(pd.timedelta_range("1 days 1 min", periods=5, freq="H")) - td2 = td1.copy() - td2.iloc[1] = np.nan - - if op_str not in ["__add__", "__radd__", "__sub__", "__rsub__"]: - check(dt2, td2) + arg1 = Series(left) + arg2 = Series(right) + # check that we are getting a TypeError + # with 'operate' (from core/ops.py) for the ops that are not + # defined + op = getattr(arg1, op_str, None) + # Previously, _validate_for_numeric_binop in core/indexes/base.py + # did this for us. + with pytest.raises(TypeError, match="operate|[cC]annot|unsupported operand"): + op(arg2) def test_sub_single_tz(self): # GH#12290 From 1289f31ae60580682a762e08e365eb24c20bbe6c Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Thu, 30 Dec 2021 11:11:13 -0800 Subject: [PATCH 3/5] Anotha one --- pandas/tests/arithmetic/test_numeric.py | 129 +++++++++++------------- 1 file changed, 60 insertions(+), 69 deletions(-) diff --git a/pandas/tests/arithmetic/test_numeric.py b/pandas/tests/arithmetic/test_numeric.py index 063e3c7e6dd19..c2901e71a4645 100644 --- a/pandas/tests/arithmetic/test_numeric.py +++ b/pandas/tests/arithmetic/test_numeric.py @@ -959,77 +959,68 @@ def test_frame_operators(self, float_frame): assert (df + df).equals(df) tm.assert_frame_equal(df + df, df) - # TODO: taken from tests.series.test_operators; needs cleanup - def test_series_operators(self): - def _check_op(series, other, op, pos_only=False): - left = np.abs(series) if pos_only else series - right = np.abs(other) if pos_only else other - - cython_or_numpy = op(left, right) - python = left.combine(right, op) - if isinstance(other, Series) and not other.index.equals(series.index): - python.index = python.index._with_freq(None) - tm.assert_series_equal(cython_or_numpy, python) - - def check(series, other): - simple_ops = ["add", "sub", "mul", "truediv", "floordiv", "mod"] - - for opname in simple_ops: - _check_op(series, other, getattr(operator, opname)) - - _check_op(series, other, operator.pow, pos_only=True) - - _check_op(series, other, ops.radd) - _check_op(series, other, ops.rsub) - _check_op(series, other, ops.rtruediv) - _check_op(series, other, ops.rfloordiv) - _check_op(series, other, ops.rmul) - _check_op(series, other, ops.rpow, pos_only=True) - _check_op(series, other, ops.rmod) + @pytest.mark.parametrize( + "func", + [lambda x: x * 2, lambda x: x[::2], lambda x: 5], + ids=["multiply", "slice", "constant"], + ) + def test_series_operators_arithmetic(self, all_arithmetic_functions, func): + op = all_arithmetic_functions + series = tm.makeTimeSeries().rename("ts") + other = func(series) + left = np.abs(series) if op in (ops.rpow, operator.pow) else series + right = np.abs(other) if op in (ops.rpow, operator.pow) else other + + cython_or_numpy = op(left, right) + python = left.combine(right, op) + if isinstance(other, Series) and not other.index.equals(series.index): + python.index = python.index._with_freq(None) + tm.assert_series_equal(cython_or_numpy, python) - tser = tm.makeTimeSeries().rename("ts") - check(tser, tser * 2) - check(tser, tser[::2]) - check(tser, 5) - - def check_comparators(series, other): - _check_op(series, other, operator.gt) - _check_op(series, other, operator.ge) - _check_op(series, other, operator.eq) - _check_op(series, other, operator.lt) - _check_op(series, other, operator.le) - - check_comparators(tser, 5) - check_comparators(tser, tser + 1) - - # TODO: taken from tests.series.test_operators; needs cleanup - def test_divmod(self): - def check(series, other): - results = divmod(series, other) - if isinstance(other, abc.Iterable) and len(series) != len(other): - # if the lengths don't match, this is the test where we use - # `tser[::2]`. Pad every other value in `other_np` with nan. - other_np = [] - for n in other: - other_np.append(n) - other_np.append(np.nan) - else: - other_np = other - other_np = np.asarray(other_np) - with np.errstate(all="ignore"): - expecteds = divmod(series.values, np.asarray(other_np)) - - for result, expected in zip(results, expecteds): - # check the values, name, and index separately - tm.assert_almost_equal(np.asarray(result), expected) - - assert result.name == series.name - tm.assert_index_equal(result.index, series.index._with_freq(None)) + @pytest.mark.parametrize( + "func", [lambda x: x + 1, lambda x: 5], ids=["add", "constant"] + ) + def test_series_operators_compare(self, comparison_op, func): + op = comparison_op + series = tm.makeTimeSeries().rename("ts") + other = func(series) + left = series + right = other + + cython_or_numpy = op(left, right) + python = left.combine(right, op) + if isinstance(other, Series) and not other.index.equals(series.index): + python.index = python.index._with_freq(None) + tm.assert_series_equal(cython_or_numpy, python) - tser = tm.makeTimeSeries().rename("ts") - check(tser, tser * 2) - check(tser, tser[::2]) - check(tser, 5) + @pytest.mark.parametrize( + "func", + [lambda x: x * 2, lambda x: x[::2], lambda x: 5], + ids=["multiply", "slice", "constant"], + ) + def test_divmod(self, func): + series = tm.makeTimeSeries().rename("ts") + other = func(series) + results = divmod(series, other) + if isinstance(other, abc.Iterable) and len(series) != len(other): + # if the lengths don't match, this is the test where we use + # `tser[::2]`. Pad every other value in `other_np` with nan. + other_np = [] + for n in other: + other_np.append(n) + other_np.append(np.nan) + else: + other_np = other + other_np = np.asarray(other_np) + with np.errstate(all="ignore"): + expecteds = divmod(series.values, np.asarray(other_np)) + + for result, expected in zip(results, expecteds): + # check the values, name, and index separately + tm.assert_almost_equal(np.asarray(result), expected) + + assert result.name == series.name + tm.assert_index_equal(result.index, series.index._with_freq(None)) def test_series_divmod_zero(self): # Check that divmod uses pandas convention for division by zero, From 4efcd0fc7693cc3530f2aeef2f29e12b7ca3ccdd Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Thu, 30 Dec 2021 11:32:33 -0800 Subject: [PATCH 4/5] One more --- .../tests/scalar/timestamp/test_timestamp.py | 97 +++++++++---------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/pandas/tests/scalar/timestamp/test_timestamp.py b/pandas/tests/scalar/timestamp/test_timestamp.py index 008731b13172e..1c34b5c8e5475 100644 --- a/pandas/tests/scalar/timestamp/test_timestamp.py +++ b/pandas/tests/scalar/timestamp/test_timestamp.py @@ -91,63 +91,56 @@ def test_properties_business(self): assert control.is_month_end assert control.is_quarter_end - def test_fields(self): - def check(value, equal): - # that we are int like - assert isinstance(value, int) - assert value == equal - + @pytest.mark.parametrize( + "attr, expected", + [ + ["year", 2014], + ["month", 12], + ["day", 31], + ["hour", 23], + ["minute", 59], + ["second", 0], + ["microsecond", 0], + ["nanosecond", 0], + ["dayofweek", 2], + ["day_of_week", 2], + ["quarter", 4], + ["dayofyear", 365], + ["day_of_year", 365], + ["week", 1], + ["daysinmonth", 31], + ], + ) + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_fields(self, attr, expected, tz): # GH 10050 - ts = Timestamp("2015-05-10 09:06:03.000100001") - check(ts.year, 2015) - check(ts.month, 5) - check(ts.day, 10) - check(ts.hour, 9) - check(ts.minute, 6) - check(ts.second, 3) - msg = "'Timestamp' object has no attribute 'millisecond'" - with pytest.raises(AttributeError, match=msg): - ts.millisecond - check(ts.microsecond, 100) - check(ts.nanosecond, 1) - check(ts.dayofweek, 6) - check(ts.day_of_week, 6) - check(ts.quarter, 2) - check(ts.dayofyear, 130) - check(ts.day_of_year, 130) - check(ts.week, 19) - check(ts.daysinmonth, 31) - check(ts.daysinmonth, 31) - # GH 13303 - ts = Timestamp("2014-12-31 23:59:00-05:00", tz="US/Eastern") - check(ts.year, 2014) - check(ts.month, 12) - check(ts.day, 31) - check(ts.hour, 23) - check(ts.minute, 59) - check(ts.second, 0) + ts = Timestamp("2014-12-31 23:59:00", tz=tz) + result = getattr(ts, attr) + # that we are int like + assert isinstance(result, int) + assert result == expected + + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_millisecond_raises(self, tz): + ts = Timestamp("2014-12-31 23:59:00", tz=tz) msg = "'Timestamp' object has no attribute 'millisecond'" with pytest.raises(AttributeError, match=msg): ts.millisecond - check(ts.microsecond, 0) - check(ts.nanosecond, 0) - check(ts.dayofweek, 2) - check(ts.day_of_week, 2) - check(ts.quarter, 4) - check(ts.dayofyear, 365) - check(ts.day_of_year, 365) - check(ts.week, 1) - check(ts.daysinmonth, 31) - - ts = Timestamp("2014-01-01 00:00:00+01:00") - starts = ["is_month_start", "is_quarter_start", "is_year_start"] - for start in starts: - assert getattr(ts, start) - ts = Timestamp("2014-12-31 23:59:59+01:00") - ends = ["is_month_end", "is_year_end", "is_quarter_end"] - for end in ends: - assert getattr(ts, end) + + @pytest.mark.parametrize( + "start", ["is_month_start", "is_quarter_start", "is_year_start"] + ) + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_is_start(self, start, tz): + ts = Timestamp("2014-01-01 00:00:00", tz=tz) + assert getattr(ts, start) + + @pytest.mark.parametrize("end", ["is_month_end", "is_year_end", "is_quarter_end"]) + @pytest.mark.parametrize("tz", [None, "US/Eastern"]) + def test_is_end(self, end, tz): + ts = Timestamp("2014-12-31 23:59:59", tz=tz) + assert getattr(ts, end) # GH 12806 @pytest.mark.parametrize( From a6ba3ea2964025de492516f1fb3042274182e40f Mon Sep 17 00:00:00 2001 From: Matthew Roeschke Date: Thu, 30 Dec 2021 17:08:52 -0800 Subject: [PATCH 5/5] Address comments --- pandas/tests/arithmetic/test_datetime64.py | 17 +++++++------ pandas/tests/arithmetic/test_numeric.py | 29 ++++++++++------------ 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index 7ea515854608f..4414462dd9a48 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -1822,7 +1822,7 @@ def test_dt64ser_sub_datetime_dtype(self): [Timestamp("20111230"), Timestamp("20120101"), NaT], [timedelta(minutes=5, seconds=3), timedelta(minutes=5, seconds=3), NaT], ["__add__", "__radd__", "__sub__"], - ], # TODO(jreback) __rsub__ should raise? + ], [ [ Timestamp("20111230", tz="US/Eastern"), @@ -1835,15 +1835,10 @@ def test_dt64ser_sub_datetime_dtype(self): ], ) def test_operators_datetimelike_invalid( - self, request, left, right, op_fail, all_arithmetic_operators + self, left, right, op_fail, all_arithmetic_operators ): # these are all TypeError ops op_str = all_arithmetic_operators - request.node.add_marker( - pytest.mark.xfail( - op_str in op_fail, reason=f"{op_str} doesn't raise TypeError" - ) - ) arg1 = Series(left) arg2 = Series(right) # check that we are getting a TypeError @@ -1852,7 +1847,13 @@ def test_operators_datetimelike_invalid( op = getattr(arg1, op_str, None) # Previously, _validate_for_numeric_binop in core/indexes/base.py # did this for us. - with pytest.raises(TypeError, match="operate|[cC]annot|unsupported operand"): + if op_str not in op_fail: + with pytest.raises( + TypeError, match="operate|[cC]annot|unsupported operand" + ): + op(arg2) + else: + # Smoke test op(arg2) def test_sub_single_tz(self): diff --git a/pandas/tests/arithmetic/test_numeric.py b/pandas/tests/arithmetic/test_numeric.py index c2901e71a4645..5a3e417aa1799 100644 --- a/pandas/tests/arithmetic/test_numeric.py +++ b/pandas/tests/arithmetic/test_numeric.py @@ -58,6 +58,17 @@ def adjust_negative_zero(zero, expected): return expected +def compare_op(series, other, op): + left = np.abs(series) if op in (ops.rpow, operator.pow) else series + right = np.abs(other) if op in (ops.rpow, operator.pow) else other + + cython_or_numpy = op(left, right) + python = left.combine(right, op) + if isinstance(other, Series) and not other.index.equals(series.index): + python.index = python.index._with_freq(None) + tm.assert_series_equal(cython_or_numpy, python) + + # TODO: remove this kludge once mypy stops giving false positives here # List comprehension has incompatible type List[PandasObject]; expected List[RangeIndex] # See GH#29725 @@ -968,14 +979,7 @@ def test_series_operators_arithmetic(self, all_arithmetic_functions, func): op = all_arithmetic_functions series = tm.makeTimeSeries().rename("ts") other = func(series) - left = np.abs(series) if op in (ops.rpow, operator.pow) else series - right = np.abs(other) if op in (ops.rpow, operator.pow) else other - - cython_or_numpy = op(left, right) - python = left.combine(right, op) - if isinstance(other, Series) and not other.index.equals(series.index): - python.index = python.index._with_freq(None) - tm.assert_series_equal(cython_or_numpy, python) + compare_op(series, other, op) @pytest.mark.parametrize( "func", [lambda x: x + 1, lambda x: 5], ids=["add", "constant"] @@ -984,14 +988,7 @@ def test_series_operators_compare(self, comparison_op, func): op = comparison_op series = tm.makeTimeSeries().rename("ts") other = func(series) - left = series - right = other - - cython_or_numpy = op(left, right) - python = left.combine(right, op) - if isinstance(other, Series) and not other.index.equals(series.index): - python.index = python.index._with_freq(None) - tm.assert_series_equal(cython_or_numpy, python) + compare_op(series, other, op) @pytest.mark.parametrize( "func",