Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLN: Refactor pandas/tests/base - part3 #30147

Merged
merged 27 commits into from
Feb 1, 2020
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4d1750c
added new fixtures that can replace the Ops mixin
SaturnFromTitan Dec 8, 2019
3d04ff2
fixed type hinting
SaturnFromTitan Dec 8, 2019
89d84b4
refactoring according to review comments
SaturnFromTitan Dec 9, 2019
199896f
removing unused variable arr and fixing the _narrow_series data
SaturnFromTitan Dec 9, 2019
e269b09
removing usage of Ops mixin in tests/indexes
SaturnFromTitan Dec 10, 2019
baab827
moved new fixtures to tests/indexes/conftest.py as they're not used a…
SaturnFromTitan Dec 10, 2019
28291f1
using new utils and fixtures in tests/indexes
SaturnFromTitan Dec 10, 2019
589ae3b
fixturizing tests/base/test_ops.py::test_binary_ops_docs
SaturnFromTitan Dec 10, 2019
2bbc3fd
refactoring of tests/indexes/conftest.py
SaturnFromTitan Dec 10, 2019
b3d0252
using pytest.skip instead of early return
SaturnFromTitan Dec 10, 2019
53db63f
skipping broken tests
SaturnFromTitan Dec 10, 2019
891b24c
replaced more return statements with pytest.skip
SaturnFromTitan Dec 10, 2019
8f0fdf6
Merge branch 'master' into refactor-test-base-part3
SaturnFromTitan Jan 23, 2020
69a0a0d
took care of review comments
SaturnFromTitan Jan 23, 2020
0fce4c5
removed redundant tests
SaturnFromTitan Jan 24, 2020
b7892fa
removed changes that aren't needed for this PR anymore
SaturnFromTitan Jan 24, 2020
471f217
moved helper to more appropriate location
SaturnFromTitan Jan 24, 2020
baa4965
Merge branch 'master' into refactor-test-base-part3
SaturnFromTitan Jan 24, 2020
7562479
removed some outdated changes
SaturnFromTitan Jan 24, 2020
85b16cb
moved datetime_series fixture to the root conftest so it's available …
SaturnFromTitan Jan 24, 2020
87e0a5b
using all_arithmetic_functions in test_binary_ops_docs now
SaturnFromTitan Jan 26, 2020
3979b3d
undid some unrelated changes
SaturnFromTitan Jan 26, 2020
452335a
Merge branch 'master' into refactor-test-base-part3
SaturnFromTitan Jan 26, 2020
8bf1142
undid my change in pandas/core/ops/__init__.py and handle the case di…
SaturnFromTitan Jan 27, 2020
c1e9f28
Revert "undid my change in pandas/core/ops/__init__.py and handle the…
SaturnFromTitan Jan 27, 2020
d9bea94
Merge branch 'master' into refactor-test-base-part3
SaturnFromTitan Jan 27, 2020
87247a5
using hard-coded values to parametrize test_binary_ops
SaturnFromTitan Feb 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions pandas/_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2757,3 +2757,29 @@ def convert_rows_list_to_csv_str(rows_list: List[str]):
sep = os.linesep
expected = sep.join(rows_list) + sep
return expected


def allow_na_ops(obj: Any) -> bool:
"""Whether to skip test cases including NaN"""
is_bool_index = isinstance(obj, Index) and obj.is_boolean()
return not is_bool_index and obj._can_hold_na


def check_ops_properties_valid(obj: Any, op: str) -> None:
""" Validates that certain properties are available """
if isinstance(obj, Series):
expected = Series(getattr(obj.index, op), index=obj.index, name="a")
else:
expected = getattr(obj, op)

result = getattr(obj, op)

# these could be series, arrays or scalars
if isinstance(result, Series) and isinstance(expected, Series):
assert_series_equal(result, expected)
elif isinstance(result, Index) and isinstance(expected, Index):
assert_index_equal(result, expected)
elif isinstance(result, np.ndarray) and isinstance(expected, np.ndarray):
assert_numpy_array_equal(result, expected)
else:
assert result == expected
10 changes: 10 additions & 0 deletions pandas/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,16 @@ def tick_classes(request):
)


@pytest.fixture
def datetime_series():
jreback marked this conversation as resolved.
Show resolved Hide resolved
"""
Fixture for Series of floats with DatetimeIndex
"""
s = tm.makeTimeSeries()
s.name = "ts"
return s


@pytest.fixture
def float_frame():
"""
Expand Down
103 changes: 25 additions & 78 deletions pandas/tests/base/test_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,9 @@
Timestamp,
)
import pandas._testing as tm
from pandas.core.indexes.datetimelike import DatetimeIndexOpsMixin


class Ops:
def _allow_na_ops(self, obj):
"""Whether to skip test cases including NaN"""
if (isinstance(obj, Index) and obj.is_boolean()) or not obj._can_hold_na:
# don't test boolean / integer dtypes
return False
return True

def setup_method(self, method):
self.bool_index = tm.makeBoolIndex(10, name="a")
self.int_index = tm.makeIntIndex(10, name="a")
Expand Down Expand Up @@ -83,74 +75,29 @@ def setup_method(self, method):

self.objs = self.indexes + self.series + self.narrow_series

def check_ops_properties(self, props, filter=None, ignore_failures=False):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was only used in pandas/tests/indexes. I could remove all tests where it was used though as they were redundant (already tested in pandas/tests/series/test_datetime_values.py::test_dt_namespace_accessor)

for op in props:
for o in self.is_valid_objs:
SaturnFromTitan marked this conversation as resolved.
Show resolved Hide resolved

# if a filter, skip if it doesn't match
if filter is not None:
filt = o.index if isinstance(o, Series) else o
if not filter(filt):
continue

try:
if isinstance(o, Series):
expected = Series(getattr(o.index, op), index=o.index, name="a")
else:
expected = getattr(o, op)
except (AttributeError):
if ignore_failures:
continue

result = getattr(o, op)

# these could be series, arrays or scalars
if isinstance(result, Series) and isinstance(expected, Series):
tm.assert_series_equal(result, expected)
elif isinstance(result, Index) and isinstance(expected, Index):
tm.assert_index_equal(result, expected)
elif isinstance(result, np.ndarray) and isinstance(
expected, np.ndarray
):
tm.assert_numpy_array_equal(result, expected)
else:
assert result == expected

# freq raises AttributeError on an Int64Index because its not
# defined we mostly care about Series here anyhow
if not ignore_failures:
for o in self.not_valid_objs:

# an object that is datetimelike will raise a TypeError,
# otherwise an AttributeError
msg = "no attribute"
err = AttributeError
if issubclass(type(o), DatetimeIndexOpsMixin):
err = TypeError
with pytest.raises(err, match=msg):
getattr(o, op)

@pytest.mark.parametrize("klass", [Series, DataFrame])
def test_binary_ops_docs(self, klass):
op_map = {
"add": "+",
"sub": "-",
"mul": "*",
"mod": "%",
"pow": "**",
"truediv": "/",
"floordiv": "//",
}
for op_name in op_map:
operand1 = klass.__name__.lower()
operand2 = "other"
op = op_map[op_name]
expected_str = " ".join([operand1, op, operand2])
assert expected_str in getattr(klass, op_name).__doc__

# reverse version of the binary ops
expected_str = " ".join([operand2, op, operand1])
assert expected_str in getattr(klass, "r" + op_name).__doc__

@pytest.mark.parametrize("klass", [Series, DataFrame])
@pytest.mark.parametrize(
"op_name, op",
[
("add", "+"),
SaturnFromTitan marked this conversation as resolved.
Show resolved Hide resolved
("sub", "-"),
("mul", "*"),
("mod", "%"),
("pow", "**"),
("truediv", "/"),
("floordiv", "//"),
],
)
def test_binary_ops_docs(klass, op_name, op):
operand1 = klass.__name__.lower()
operand2 = "other"
expected_str = " ".join([operand1, op, operand2])
assert expected_str in getattr(klass, op_name).__doc__

# reverse version of the binary ops
expected_str = " ".join([operand2, op, operand1])
assert expected_str in getattr(klass, "r" + op_name).__doc__


class TestTranspose(Ops):
Expand Down Expand Up @@ -313,7 +260,7 @@ def test_value_counts_unique_nunique_null(self, null_obj):
klass = type(o)
values = o._ndarray_values

if not self._allow_na_ops(o):
if not tm.allow_na_ops(o):
continue

# special assign to the numpy array
Expand Down Expand Up @@ -794,7 +741,7 @@ def test_fillna(self):
o = orig.copy()
klass = type(o)

if not self._allow_na_ops(o):
if not tm.allow_na_ops(o):
continue

if needs_i8_conversion(o):
Expand Down
13 changes: 11 additions & 2 deletions pandas/tests/indexes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,12 @@ def test_ensure_copied_data(self, indices):
if isinstance(indices, PeriodIndex):
# Needs "freq" specification:
init_kwargs["freq"] = indices.freq
elif isinstance(indices, DatetimeIndex) and indices.tz is not None:
pytest.skip()
elif isinstance(indices, (RangeIndex, MultiIndex, CategoricalIndex)):
# RangeIndex cannot be initialized from data
# MultiIndex and CategoricalIndex are tested separately
return
pytest.skip()
SaturnFromTitan marked this conversation as resolved.
Show resolved Hide resolved

index_type = type(indices)
result = index_type(indices.values, copy=True, **init_kwargs)
Expand Down Expand Up @@ -457,7 +459,10 @@ def test_set_ops_error_cases(self, case, method, indices):

def test_intersection_base(self, indices):
if isinstance(indices, CategoricalIndex):
return
pytest.skip()
if isinstance(indices, DatetimeIndex) and indices.tz is not None:
# TODO: This should probably be fixed
pytest.skip()

first = indices[:5]
second = indices[:3]
Expand All @@ -476,6 +481,10 @@ def test_intersection_base(self, indices):
first.intersection([1, 2, 3])

def test_union_base(self, indices):
if isinstance(indices, DatetimeIndex) and indices.tz is not None:
# TODO: This should probably be fixed
pytest.skip()

first = indices[3:]
second = indices[:5]
everything = indices
Expand Down
53 changes: 52 additions & 1 deletion pandas/tests/indexes/conftest.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import numpy as np
import pytest

import pandas as pd
import pandas._testing as tm
from pandas.core.indexes.api import Index, MultiIndex

indices_dict = {
"unicode": tm.makeUnicodeIndex(100),
"string": tm.makeStringIndex(100),
"datetime": tm.makeDateIndex(100),
"localized-datetime": tm.makeDateIndex(100, tz="US/Eastern"),
"period": tm.makePeriodIndex(100),
"timedelta": tm.makeTimedeltaIndex(100),
"int": tm.makeIntIndex(100),
"uint": tm.makeUIntIndex(100),
"range": tm.makeRangeIndex(100),
"float": tm.makeFloatIndex(100),
"bool": Index([True, False]),
"bool": tm.makeBoolIndex(2),
"categorical": tm.makeCategoricalIndex(100),
"interval": tm.makeIntervalIndex(100),
"empty": Index([]),
Expand All @@ -26,3 +29,51 @@
def indices(request):
# copy to avoid mutation, e.g. setting .name
return indices_dict[request.param].copy()


def _create_series(index):
SaturnFromTitan marked this conversation as resolved.
Show resolved Hide resolved
""" Helper for the _series dict """
data = np.random.randn(len(index))
return pd.Series(data, index=index, name=index.name)


_series = {
f"series-with-{i_id}-index": _create_series(i) for i_id, i in indices_dict.items()
}


def _create_narrow_series(data_dtype):
""" Helper for the _narrow_series dict """
index = indices_dict["int"].copy()
size = len(index)
if np.issubdtype(data_dtype, np.float):
data = np.random.choice(size, size=size, replace=False)
elif np.issubdtype(data_dtype, np.integer):
data = np.random.randn(size)
else:
raise ValueError(f"Received an unexpected data_dtype: {data_dtype}")

return pd.Series(data.astype(data_dtype), index=index, name="a")


_narrow_series = {
"float32-series": _create_narrow_series(np.float32),
"int8-series": _create_narrow_series(np.int8),
"int16-series": _create_narrow_series(np.int16),
"int32-series": _create_narrow_series(np.int32),
"uint8-series": _create_narrow_series(np.uint8),
"uint16-series": _create_narrow_series(np.uint16),
"uint32-series": _create_narrow_series(np.uint32),
}

_all_objs = {**indices_dict, **_series, **_narrow_series}


@pytest.fixture(params=_all_objs.keys())
def index_or_series_obj(request):
"""
Fixture for tests on indexes, series and series with a narrow dtype

copy to avoid mutation, e.g. setting .name
"""
return _all_objs[request.param].copy(deep=True)
35 changes: 20 additions & 15 deletions pandas/tests/indexes/datetimes/test_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,37 @@
Index,
PeriodIndex,
Series,
TimedeltaIndex,
Timestamp,
bdate_range,
date_range,
)
import pandas._testing as tm
from pandas.tests.base.test_ops import Ops

from pandas.tseries.offsets import BDay, BMonthEnd, CDay, Day, Hour

START, END = datetime(2009, 1, 1), datetime(2010, 1, 1)


class TestDatetimeIndexOps(Ops):
def setup_method(self, method):
super().setup_method(method)
mask = lambda x: (isinstance(x, DatetimeIndex) or isinstance(x, PeriodIndex))
self.is_valid_objs = [o for o in self.objs if mask(o)]
self.not_valid_objs = [o for o in self.objs if not mask(o)]

def test_ops_properties(self):
f = lambda x: isinstance(x, DatetimeIndex)
self.check_ops_properties(DatetimeIndex._field_ops, f)
self.check_ops_properties(DatetimeIndex._object_ops, f)
self.check_ops_properties(DatetimeIndex._bool_ops, f)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already tested in pandas/tests/series/test_datetime_values.py::test_dt_namespace_accessor

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. test_dt_namespace_accessor could use a good refactor if youre up to it (separate PR)


def test_ops_properties_basic(self):
class TestDatetimeIndexOps:
@pytest.mark.parametrize("op", DatetimeIndex._datetimelike_ops)
def test_valid_ops_properties(self, op, index_or_series_obj):
obj = index_or_series_obj
if isinstance(obj, DatetimeIndex):
tm.check_ops_properties_valid(obj, op)

@pytest.mark.parametrize("op", DatetimeIndex._datetimelike_ops)
def test_invalid_ops_properties(self, op, index_or_series_obj):
obj = index_or_series_obj
if isinstance(obj, (DatetimeIndex, PeriodIndex)):
pytest.skip()
if op == "freq" and isinstance(obj, TimedeltaIndex):
pytest.skip()

with pytest.raises((AttributeError, TypeError)):
getattr(obj, op)

def test_ops_properties_basic(self, datetime_series):

# sanity check that the behavior didn't change
# GH#7206
Expand Down
30 changes: 17 additions & 13 deletions pandas/tests/indexes/period/test_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,28 @@
import pytest

import pandas as pd
from pandas import DatetimeIndex, Index, NaT, PeriodIndex, Series
from pandas import DatetimeIndex, Index, NaT, PeriodIndex, Series, TimedeltaIndex
import pandas._testing as tm
from pandas.core.arrays import PeriodArray
from pandas.tests.base.test_ops import Ops


class TestPeriodIndexOps(Ops):
def setup_method(self, method):
super().setup_method(method)
mask = lambda x: (isinstance(x, DatetimeIndex) or isinstance(x, PeriodIndex))
self.is_valid_objs = [o for o in self.objs if mask(o)]
self.not_valid_objs = [o for o in self.objs if not mask(o)]
class TestPeriodIndexOps:
@pytest.mark.parametrize("op", PeriodArray._datetimelike_ops)
SaturnFromTitan marked this conversation as resolved.
Show resolved Hide resolved
def test_valid_ops_properties(self, op, index_or_series_obj):
obj = index_or_series_obj
if isinstance(obj, PeriodIndex):
tm.check_ops_properties_valid(obj, op)

def test_ops_properties(self):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already tested in pandas/tests/series/test_datetime_values.py::test_dt_namespace_accessor

f = lambda x: isinstance(x, PeriodIndex)
self.check_ops_properties(PeriodArray._field_ops, f)
self.check_ops_properties(PeriodArray._object_ops, f)
self.check_ops_properties(PeriodArray._bool_ops, f)
@pytest.mark.parametrize("op", PeriodArray._datetimelike_ops)
def test_invalid_ops_properties(self, op, index_or_series_obj):
obj = index_or_series_obj
if isinstance(obj, (PeriodIndex, DatetimeIndex)):
pytest.skip()
if op == "freq" and isinstance(obj, TimedeltaIndex):
pytest.skip()

with pytest.raises((AttributeError, TypeError)):
getattr(obj, op)

def test_resolution(self):
for freq, expected in zip(
Expand Down
Loading