Skip to content

Implement missing offset comparison methods #18738

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

Closed
wants to merge 6 commits into from

Conversation

jbrockmendel
Copy link
Member

  • closes #xxxx
  • tests added / passed
  • passes git diff upstream/master -u -- "*.py" | flake8 --diff
  • whatsnew entry

Not sure if it closes #8386, but its most of the way there.

Fix some missing comparisons, including Timedelta with Tick and Tick with (timedelta|Timedelta|Week)

Attach correct names for comparison methods.

If we can resolve #18510, we can make Week(weekday=None) just return a Tick, avoid some special casing.

assert isinstance(delta, Timedelta)
assert delta < offset
assert delta <= offset
assert not delta == offset
Copy link
Contributor

Choose a reason for hiding this comment

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

these are not correct comparisons, eg.. not a == b is equiv to a != b and the linter complains about the former.

Copy link
Member Author

Choose a reason for hiding this comment

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

If the linting is complaining I can change it to explicitly call e.g not a.__ne__(b). The methods have distinct implementations, need distinct tests.

with pytest.raises(TypeError):
off < 3

# Unfortunately there is no good way to make the reverse inequality work
Copy link
Contributor

Choose a reason for hiding this comment

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

huh?

Copy link
Member Author

Choose a reason for hiding this comment

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

We can make Tick.__richmp__(timedelta) work, but we can't make timedelta.__richcmp__(Tick) work. AFAICT datetime for deferring to other but timedelta does not.

if isinstance(other, DateOffset):
return f(self, other)
else:
if op == operator.eq:
Copy link
Contributor

Choose a reason for hiding this comment

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

why are you repeating

isn't
return op(self, other) enough here?

Copy link
Member Author

Choose a reason for hiding this comment

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

You mean in line 2181 returning f(self, other) instead of op(self, other)? Because the the behavior still depends on which DateOffset subclass other is.

other=other.__class__.__name__))
elif op == operator.eq:
# TODO: Consider changing this older behavior for
# __eq__ and __ne__to match other comparisons
Copy link
Contributor

Choose a reason for hiding this comment

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

what is this about?

Copy link
Member Author

Choose a reason for hiding this comment

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

In the status quo __eq__ returns False (and __ne__ returns True) as the fallback when other cannot be converted to a DateOffset. The other comparisons raise in this situation. (I think Timedelta behaves the same way, so this might be OK). This is just a note to double-check that we __eq__ and __ne__ should continue to be treated as special cases.

@jreback jreback added the Frequency DateOffsets label Dec 12, 2017
@jreback
Copy link
Contributor

jreback commented Dec 12, 2017

Not sure if it closes #8386, but its most of the way there.

if most of the way there, then let's take it over the line.

@jbrockmendel
Copy link
Member Author

if most of the way there, then let's take it over the line.

#8386 can be split into two parts. First is unimplemented comparison methods, which this PR addresses as many of as I could find.

Second is that author implicitly expects to_offset('W') to return pd.offsets.Week(weekday=None) (which will after this PR be comparable as that author expects), but to_offset('W') actually returns pd.offsets.Week(weekday=6), which remains non-comparable. This latter part of that issue is not addressed in this PR, and I want to leave it up to @edrevo to decide if this fully solves the problem.

@codecov
Copy link

codecov bot commented Dec 12, 2017

Codecov Report

Merging #18738 into master will decrease coverage by <.01%.
The diff coverage is 82.05%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #18738      +/-   ##
==========================================
- Coverage   91.67%   91.66%   -0.01%     
==========================================
  Files         154      154              
  Lines       51368    51388      +20     
==========================================
+ Hits        47092    47106      +14     
- Misses       4276     4282       +6
Flag Coverage Δ
#multiple 89.53% <82.05%> (-0.01%) ⬇️
#single 40.83% <35.89%> (-0.01%) ⬇️
Impacted Files Coverage Δ
pandas/tseries/offsets.py 96.53% <82.05%> (-0.42%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 75b97a7...c3f7a1f. Read the comment docs.

@edrevo
Copy link

edrevo commented Dec 13, 2017

@jbrockmendel , I'm OK with this fix. Many thanks for the PR!!

This was referenced Dec 13, 2017
off <= Day(n=7)


def test_comparison_names(offset_types):
Copy link
Contributor

Choose a reason for hiding this comment

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

can you parametrize

Copy link
Contributor

@jreback jreback left a comment

Choose a reason for hiding this comment

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

add a whatsnew note for #8386 see if anything listed there which should be a tests, if not lmk that.

@@ -3147,3 +3147,52 @@ def test_require_integers(offset_types):
cls = offset_types
with pytest.raises(ValueError):
cls(n=1.5)


def test_comparisons(offset_types):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this needs to be broken up a bit, its very hard to read / determine what is allowed and what is not.

IOW you need 3 tests
Week, Tick, Everything else

Copy link
Contributor

Choose a reason for hiding this comment

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

and the Tick test should be in tests_ticks (and you have one already there)

off < DateOffset(month=7)


def test_week_comparison():
Copy link
Contributor

Choose a reason for hiding this comment

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

and you already have week (yes this is constructed differently), but pls group these together.


def cmp_func(self, other):
if type(self) == Week and self.weekday is None:
# Week without weekday behaves like a Tick
Copy link
Contributor

Choose a reason for hiding this comment

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

blank line

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, but for clarification, I thought that wasn't necessary when the pre-comment line has different indentation. Is there a reference for this I can bookmark?

# Week without weekday behaves like a Tick
tick = Day(n=7 * self.n, normalize=self.normalize)
return op(tick, other)
else:
Copy link
Contributor

Choose a reason for hiding this comment

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

no need for the else

other_delta = other.delta
elif isinstance(other, (timedelta, np.timedelta64)):
other_delta = other
elif isinstance(other, Week) and other.weekday is None:
Copy link
Contributor

Choose a reason for hiding this comment

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

what happens when other.weekday is not None?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would lump all Week in this case (use if statements inside if needed)

Copy link
Member Author

Choose a reason for hiding this comment

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

what happens when other.weekday is not None?

TypeError

I would lump all Week in this case (use if statements inside if needed)

Leads to a bunch of duplicated code if we do it that way.

@jreback jreback added API Design Compat pandas objects compatability with Numpy or Python functions labels Dec 21, 2017
@jbrockmendel
Copy link
Member Author

Handling array-like comparisons correctly is going to take some additional work. Temporarily closing to clear the queue.

@jbrockmendel jbrockmendel deleted the offsets_cmp branch April 5, 2020 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API Design Compat pandas objects compatability with Numpy or Python functions Frequency DateOffsets
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Week.onOffset looks fishy Not all DateOffsets are comparable
3 participants