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

DEPR: raise deprecation warning in numpy ufuncs on DataFrames if not aligned + fallback to <1.2.0 behaviour #39239

Conversation

jorisvandenbossche
Copy link
Member

@jorisvandenbossche jorisvandenbossche commented Jan 17, 2021

Closes #39184

This is obviously a last-minute change, but if people agree on the deprecation, I think we should try to include it in v1.2.1. I think my patch is relatively safe, since converting the input to numpy arrays (what I am doing now manually as fallback) is what happened before adding DataFrame.__array_ufunc__ as well.

It adds quite some lines of code, but it's mostly some simple checking of the exact case which is a bit verbose.

The specific tests I added were verified to pass on pandas 1.1.5, so they codify the previous behaviour (minus the warnings).

cc @TomAugspurger

@jorisvandenbossche jorisvandenbossche marked this pull request as ready for review January 18, 2021 08:19
@simonjayhawkins simonjayhawkins added this to the 1.2.1 milestone Jan 18, 2021
@jorisvandenbossche jorisvandenbossche added the Deprecate Functionality to remove in pandas label Jan 18, 2021
@jorisvandenbossche jorisvandenbossche mentioned this pull request Jan 18, 2021
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.

this is not timely
pls wait for 1.2.2 if you must

@jorisvandenbossche
Copy link
Member Author

@jreback could you at least read what it is about and give your opinion about that? Because it is reverting behavior that was changed in 1.2.0, it exactly is timely to do it for 1.2.1

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.

i have to say i am not against the deprecation itself

i like the change and these should align - and i suppose can be deprecated

but the timing is terrible and this is a large amount of code

pls just wait till 1.2.2

@jreback
Copy link
Contributor

jreback commented Jan 18, 2021

it is not timely to do things at the last minute

we have had several last minute changes in the past which have been a disaster

-1000 on merging this now

@jorisvandenbossche
Copy link
Member Author

Thanks for the feedback @simonjayhawkins, pushed an update

@simonjayhawkins
Copy link
Member

we are pretty much on top of the regressions from 1.2, so if a short delay enables to clear the list, it may be worth considering.

but agreed should not be rushed

@simonjayhawkins
Copy link
Member

FWIW I'm leaning to prefer keeping the breaking change from the consistency with Series argument and avoiding the code changes by users to avoid the warnings.

# if at least one is not aligned -> warn and fallback to array behaviour
if non_aligned:
warnings.warn(
"Calling a ufunc on non-aligned DataFrames/Series. Currently, the "
Copy link
Member

Choose a reason for hiding this comment

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

because the Series behavior is different, this warning could be misleading?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, yes. The most explicit is "non-aligned DataFrames or DataFrame/Series combination" or something like that, but wanted to keep it shorter ..
I agree the current can be misleading though (although you will of course never see the warning with only series)

@simonjayhawkins
Copy link
Member

See #39184

this would close?

@jorisvandenbossche
Copy link
Member Author

Yes, this closes that issue, will update the top post.

@jorisvandenbossche
Copy link
Member Author

we are pretty much on top of the regressions from 1.2, so if a short delay enables to clear the list, it may be worth considering.

If delaying the release with 1 or 2 days helps getting this merged, I think that is worth it.

@simonjayhawkins
Copy link
Member

Maybe add something in 1.2 release notes after Calling a binary-input NumPy ufunc on multiple DataFrame objects now aligns, matching the behavior of binary operations and ufuncs on Series (:issue:23743). like: This change has been reverted and deprecated instead in pandas 1.2.1, see :doc:v1.2.1 " or link direct to sub section

@simonjayhawkins
Copy link
Member

If delaying the release with 1 or 2 days helps getting this merged, I think that is worth it.

i'll add the blocker tag here until there is consensus to re-open up the 1.2.1 milestone for new issues/PRs e.g. #39253 that need not block, but could potentially be completed before this.


.. code-block:: python

>>> df1 = pd.DataFrame({"a": [1, 2], "b": [3, 4]}, index=[0, 1])
Copy link
Contributor

Choose a reason for hiding this comment

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

this is an incorrect format


.. code-block:: python

>>> df1 + df2
Copy link
Contributor

Choose a reason for hiding this comment

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

make an actual ipython block

Copy link
Member Author

Choose a reason for hiding this comment

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

I need to use some plain code-blocks since part of the example is showing old behaviour (or behaviour that will change in the future), and so prefer to use then code-blocks for all examples, for consistency within this section

Copy link
Contributor

Choose a reason for hiding this comment

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

we use ipython blocks everywhere, pls do this

Copy link
Contributor

Choose a reason for hiding this comment

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

would like to change these to be consistent

doc/source/whatsnew/v1.2.1.rst Show resolved Hide resolved
doc/source/whatsnew/v1.2.1.rst Show resolved Hide resolved

.. code-block:: python

>>> np.add(df1, np.asarray(df2))
Copy link
Contributor

Choose a reason for hiding this comment

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

use an actual ipython format

from pandas.core.generic import NDFrame
from pandas.core.internals import BlockManager

cls = type(self)

is_ndframe = [isinstance(x, NDFrame) for x in inputs]
Copy link
Contributor

Choose a reason for hiding this comment

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

why would you do this? simply check is_series. this is amazingly confusing.

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 is is_series ?

Copy link
Contributor

Choose a reason for hiding this comment

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

we have dataframes and series

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, and NDFrame is the parent class for both? Do you want me to put isinstance(x, (Series, DataFrame)) instead of isinstance(x, NDFrame) ?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes i think its more clear

Copy link
Member Author

Choose a reason for hiding this comment

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

Note that below in this array_ufunc function, we are also using NDFrame for this purpose

Copy link
Contributor

Choose a reason for hiding this comment

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

so rename this to is_series_or_frame i think is more clear

Copy link
Member Author

Choose a reason for hiding this comment

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

I renamed it now to n_alignable, because alignable is the variable name that is already used below, for consistency. And it also matches the explanation in the comment (which says this is Series or DataFrame).
(but can also rename to n_series_or_frame if you prefer)

pandas/core/arraylike.py Show resolved Hide resolved
pandas/core/arraylike.py Outdated Show resolved Hide resolved
"Calling a ufunc on non-aligned DataFrames (or DataFrame/Series "
"combination). Currently, the indices are ignored and the result "
"takes the index/columns of the first DataFrame. In the future "
"(pandas 2.0), the DataFrames/Series will be aligned before "
Copy link
Contributor

Choose a reason for hiding this comment

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

dont' need to mention the version

Copy link
Contributor

Choose a reason for hiding this comment

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

would not mention here

pandas/core/arraylike.py Outdated Show resolved Hide resolved
@simonjayhawkins
Copy link
Member

@jorisvandenbossche did you see #39239 (comment)

(also since this PR is the blocker could maybe also update the release date in the notes to maybe save an extra ci/backport cycle)

@jorisvandenbossche
Copy link
Member Author

jorisvandenbossche commented Jan 19, 2021

@simonjayhawkins yeah, I saw that, thanks for the reminder, as I still need to do that.
I was maybe thinking to also add a subsection (or mainly the title + short summary) to the deprecations section, linking to the 1.2.1 page. Although the deprecation was not in 1.2.0 itself, people wanting to know what changed in 1.2.x in general will still mostly look at the 1.2.0 page.

(also since this PR is the blocker could maybe also update the release date in the notes to maybe save an extra ci/backport cycle)

Good idea. What's your current idea about the timeline? (eg try to merge this PR this evening, and start release process tomorrow morning? in which case I pick the date of tomorrow)

@jorisvandenbossche
Copy link
Member Author

Actaully, we don't have subsections yet in the deprecations section in v1.2.0.rst, so just did the clarification of the original whatsnew note as you suggested @simonjayhawkins

@simonjayhawkins
Copy link
Member

simonjayhawkins commented Jan 19, 2021

I normally like to start the release nearer to the start of the day, but could get going on the final pre-release checks #38721 (comment) as soon as this is backported.

I think the date should match the tag (and we discussed templating this #21050 (comment)) which may not match the github release if the release spans a couple of days.

If we're not sure, maybe best to leave out of this PR and I could expedite the change by not waiting for ci to complete (i normally wait for ci to complete which can add a couple of hours to the release process, for the change to master and again for the backport PR)

"""
Helper to check if a DataFrame is aligned with another DataFrame or Series.
"""
from pandas.core.frame import DataFrame
Copy link
Contributor

Choose a reason for hiding this comment

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

might as well just import from pandas here, this is only the import if you can import at the top of the file (not sure if you can), also maybe can use ABCDataFrame

Copy link
Member Author

Choose a reason for hiding this comment

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

pandas.core.frame.py import from this file, so I don't think I can move the import to the top of the file

Copy link
Contributor

Choose a reason for hiding this comment

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

i get that you cannot put the import at the top. However when inside the function the style is to
from pandas import DataFrame

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, changed the imports

pandas/core/arraylike.py Outdated Show resolved Hide resolved
is_ndframe = [isinstance(x, NDFrame) for x in inputs]
is_frame = [isinstance(x, DataFrame) for x in inputs]

if (sum(is_ndframe) >= 2) and (sum(is_frame) >= 1):
Copy link
Contributor

Choose a reason for hiding this comment

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

this condition is impossible to reason about. pls make it simpler. you just want to know if you have 2 or more dataframes right? (or series)? if so, just say that

Copy link
Member Author

Choose a reason for hiding this comment

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

No, I want to know if at least two alignable objects (DataFrame or Series) and at least one DataFrame, which is what the above line does, and which is what is explained on the line just below. I can try to clarify that comment if something is not clear about that?

Copy link
Contributor

Choose a reason for hiding this comment

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

try to simplify.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, Jeff, if you don't give me a clue about what exactly is unclear for you or about how you would do it differently, I have no idea how to improve this. The code reflects exactly what I just explained it needs checking, and it is explained in the line below as well.

Would eg change sum(is_frame) into a variable n_frames help? (and moving the sum to the list comprehension where now is_frame is defined)

Copy link
Contributor

Choose a reason for hiding this comment

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

well, the problem that this is getting so complicated that you need to comment. I honestly don't think this is worth doing this much change at this late hour.

if you want to do for 1.2.2 or better yet 1.3.ok

waiting for the nth change is extremely painful and disruptive.

these are supposed to be lightweight backports. this is turning in to a nightmare.

this is likely going to be extremely fragile and break again. and will then have to be patched again.

Copy link
Member Author

Choose a reason for hiding this comment

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

Waiting for 1.2.2 or 1.3 is not going to make this change any simpler, if you don't help me find out what you don't like about it

waiting for the nth change is extremely painful and disruptive.

What is this about?

these are supposed to be lightweight backports. this is turning in to a nightmare.

The changes in this PR is a rather clean additional check in the array_ufunc function, to use a different code path in certain cases. It almost doesn't touch any existing code, so I would say it is a clean patch to backport.

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.

ok i suggested a couple of things to make it more clear. if you can fix the docs as suggested ok to merge.

doc/source/whatsnew/v1.2.0.rst Show resolved Hide resolved
"""
Helper to check if a DataFrame is aligned with another DataFrame or Series.
"""
from pandas.core.frame import DataFrame
Copy link
Contributor

Choose a reason for hiding this comment

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

i get that you cannot put the import at the top. However when inside the function the style is to
from pandas import DataFrame

from pandas.core.generic import NDFrame
from pandas.core.internals import BlockManager

cls = type(self)

is_ndframe = [isinstance(x, NDFrame) for x in inputs]
Copy link
Contributor

Choose a reason for hiding this comment

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

so rename this to is_series_or_frame i think is more clear

pandas/core/arraylike.py Outdated Show resolved Hide resolved
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.

ok lgtm on code / tests. 2 doc comments.

doc/source/whatsnew/v1.2.0.rst Show resolved Hide resolved
"Calling a ufunc on non-aligned DataFrames (or DataFrame/Series "
"combination). Currently, the indices are ignored and the result "
"takes the index/columns of the first DataFrame. In the future "
"(pandas 2.0), the DataFrames/Series will be aligned before "
Copy link
Contributor

Choose a reason for hiding this comment

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

would not mention here


.. code-block:: python

>>> df1 + df2
Copy link
Contributor

Choose a reason for hiding this comment

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

would like to change these to be consistent

@jorisvandenbossche
Copy link
Member Author

AFAIK the remaining comment is a doc comment on the whatsnew notes, and since this is a somewhat subjective style discussion / not critical IMO, I am going to take the liberty to merge this, so @simonjayhawkins can start the release process early in the day once this is backported and builds have passed.
(but, Jeff, I am happy to further discuss it and do a follow-up PR, we can still update those docs in the 1.2.x cycle and those will already be out in a few weeks)

@simonjayhawkins I also updated the date in the release notes here.

@jorisvandenbossche jorisvandenbossche merged commit ff628b1 into pandas-dev:master Jan 20, 2021
@jorisvandenbossche jorisvandenbossche deleted the array-ufunc-alignment-deprecation branch January 20, 2021 07:23
@jorisvandenbossche
Copy link
Member Author

@meeseeksdev backport to 1.2.x

meeseeksmachine pushed a commit to meeseeksmachine/pandas that referenced this pull request Jan 20, 2021
…y ufuncs on DataFrames if not aligned + fallback to <1.2.0 behaviour
jorisvandenbossche added a commit that referenced this pull request Jan 20, 2021
…n DataFrames if not aligned + fallback to <1.2.0 behaviour (#39288)

Co-authored-by: Joris Van den Bossche <[email protected]>
@jreback
Copy link
Contributor

jreback commented Jan 20, 2021

@jorisvandenbossche pls following up and fix the docs

it's not a style issue rather this is completely inconsistent with the current docs

we NEVER use the style - always ipython docs style

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Blocker Blocking issue or pull request for an upcoming release Deprecate Functionality to remove in pandas
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BUG: Numpy ufuncs e.g. np.[op](df1, df2) aligns columns in pandas 1.2.0 where it did not before
4 participants