Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Feature: Support passing DataFrames to table.table#28830
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Thanks for the contribution. The solution is slightly more complex because pandas is an optional dependency.
Also this should get a test. Something like (unchecked - you may need to slightly adapt it to make it work):
def test_table_dataframe(): df = pd.DataFrame([[1, 2], [3, 4]], columns=['a', 'b']) fig, ax = plt.subplots() table = ax.table(df) assert table[0, 0] == 'a' assert table[0, 1] == 'b' assert table[1, 0] == '1' assert table[1, 1] == '2' assert table[2, 0] == '3' assert table[2, 1] == '4'
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
…andle Pandas DataFrame Objects
…y and _is_pandas_dataframe with associated test.
Pytest test_table.py::test_table_dataframeUnit Test
ResultPassed |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This should get a whats new note. Seehttps://matplotlib.org/devdocs/devel/api_changes.html#what-s-new-notes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Slight concern about the wording in the what's new so gonna give you time to address, but otherwise I think this is a great addition!
Uh oh!
There was an error while loading.Please reload this page.
story645 left a comment• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
To fix the failing tests, add pandas to the testing requirements
I think tohttps://github.com/matplotlib/matplotlib/blob/main/requirements/testing/minver.txt or limit the new tests to 3.10+ like the xarray tests
I'm honestly extra confused why those tests don't find pandas since it's in the extra testing
This is where I am confused also. For now, , Ill modify the the mini version file to include pandas and hopefully, that test can run correctly. |
I think I found the problem. Inside the tests.yml file, the extra-requirements line is missing from the other workflows. We could add pandas to all.txt, but I suggest against it because it is not a default dependency. So instead, Ill add the extra-requirements line to the other workflows.
For the minimum versions workflow, minver.txt doesn't add dependencies, but defines the package versions for the all.txt file. Ill revert my changes there. |
story645 commentedSep 23, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Sorry for the backtracking, but I think instead of changing the minimum version tests to run everything, you should flag this test to only run when pandas is installed. |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
lib/matplotlib/table.py Outdated
@@ -674,7 +676,7 @@ def table(ax, | |||
Parameters | |||
---------- | |||
cellText : 2D list of str, optional | |||
cellText : 2D list of str,Pandas.DataFrame,optional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'm not sure we want to call out pandas data frames here. How do we do this elsewhere? I'd just state "object" and then below, state that
"""
IfcellText
is not a list of texts, we attempt to accesscellText.columns.to_numpy()
for the column headers,
andcellText.to_numpy()
for the table's cells.
"""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
We don't have any other functions explicitly take DataFrames. Typically, DataFrame is covered by what we callarray-like
(and internally runs viato_numpy()
. It's different here because we take the column headers as well. Due to this particular usage, we've chosen to explicitly check for DataFrames (_is_pandas_dataframe()
). I believe that duck-typing would be a bit too vague here: An object withobj.columns.to_numpy()
andobj.to_numpy()
is very particular. I'm not sure if this matches anything but pandas.DataFrame - and if there exists other such objects, I'm not clear that accepting them is appropriate. If the need for similar objects should arise, we can always revisit. For now, the explicit type is the simplest documentation and matches implementation:
cellText :2Dlistofstr,Pandas.DataFrame,optional | |
cellText :2Dlistofstrorpandas.DataFrame,optional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I linked an example of another very popular package, polars, that has the same form of data frame. I'm sure there are others out there. I don't think we should be special casing pandas here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Polars is logically equivalent, but it does not have the same API. See#28830 (comment).
There are several other dataframes out their (https://data-apis.org/dataframe-api/draft/purpose_and_scope.html#history-and-dataframe-implementations), but the problem is that their API is currently not consistent. IMHO we therefore would have to special case to support multiple of them.
While I appreciate the attempt to duck-type instead of explicitly relying on types, I believe the current dataframe APIs are to inhomogeneous to support reasonable duck-typing. Any object that supportsobj.columns.to_numpy()
andobj.to_numpy()
is too technical - i suspect that at least half of the pandas users would not know whether that holds forpandas.DataFrame
or not. Duck-typing would work ifdataframe-like
was well-defined, but it is currently not (see the draft dataframe API standard).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I thinknp.asanyarray(df.columns)
works on both pandas and polars, and is pretty straightforward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
If you really want to push for duck-typing, go for it. But I'd require reasonable type stubs (acceptingAny
is not good enough) and type documentation (users should at least easily see that pandas.DataFrame as the most common dataframe type is supported).
Ideally, that claimed behavior would also be tested. (It's easy for pandas, because it's a test dependency. For others, you'd likely need a mock similar totest_unpack_to_numpy_from_torch
.)
I personally believe it's not worth the added effort and would advise against requiring this from a first-time contribution. When accepting the limited scope of pandas, this PR is almost ready apart from minor documentation issues and the type stub. I therefore suggest to take this PR as a limited but well scoped improvement. We don't paint us in any corner with that. You can generalize in a followup PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
But I'd require reasonable type stubs
OK, but I dont' think you will get pandas dataframes in a typestub from this PR either, will you? I'm personally pretty 👎 on losing functionality because of type stubbing issues.
@@ -744,6 +746,13 @@ def table(ax, | |||
cols = len(cellColours[0]) | |||
cellText = [[''] * cols] * rows | |||
# Check if we have a Pandas DataFrame | |||
if _is_pandas_dataframe(cellText): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'd actually make this more generic, and check ifcellText
has a columns attribute and ato_numpy
method. That lets non-pandas objects also work; for instance polars is a pandas competitor, and there is no reason its dataframes could not be passed in here:https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.columns.html
https://docs.pola.rs/api/python/stable/reference/dataframe/api/polars.DataFrame.to_numpy.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Note thatpolars.DataFrame.columns
is of typelist[str]
, so the current implementation usingcellText.columns.to_numpy()
would fail. These are exactly the subtleties I did not want to go into as part of this first-time contributor PR. Let's start with pandas, which is a clean addition. We can always generalize later, which should likely conform to thedataframe API standard (note that this is still draft)
As a side-remark: The table implementation has lots of design problems. If one was serious about tables, the wholeTable
would need to be rewritten/replaced. Therefore, I wouldn't spend too much effort on trying to improve implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This needs an update to the table stub intables.pyi
Sure, Ill revert my changes and add a flag instead. Thank you all for the thorough review of my commits. I'm learning a lot👍🏾. About the polars package, I dont mind repeating what I did here and make another PR for it. If it is as popular as you say, I can see the value in adding support. |
anijjar commentedSep 24, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
When trying to commit the tables.pyi file, I get this error from pre-commit.
from this change
Im not sure how to fix this unless I outright import the pandas module. |
Uh oh!
There was an error while loading.Please reload this page.
I believe it should work to
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Bunch of hopefully small things and also this PR will need two approvals
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
lib/matplotlib/table.pyi Outdated
from typing import Any, Literal | ||
from typing import Any, Literal, TYPE_CHECKING | ||
if TYPE_CHECKING: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Having anif TYPE_CHECKING:
in a pyi is kind of redundant, as pyi isonly used for type checking.
This does introduce a type-check time requirement onpandas
, which I don't fully love, but is notthat bad. The if/else here does not protect against not having Pandas (and not even sure a try/except would work in apyi
... it would at least be unusual)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'll reiterate that I don't think we should be special casing pandas at all, and we should not have a pandas dataframe as a type annotation. Is there anywhere else in the library that we do this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Should I import the DataFrame method directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Following#28726 (comment), I compared with the pandas implementation:
We should mingle columns and index intorowLabels
andcolLabels
: IfrowLabels
/colLabels
are not given, use index / columns as the respective labels.
We have two options for handling these with additional explicit labels:
- explicit labels take precedence and overwrite index/columns values
- we error if both are given.
While 1) could be convenient to overwrite the labels, I'm inclined to go with 2) - in the face of ambiguity, refuse the temptation to guess. We could always expand to 1) later if the need would arise.
story645 left a comment• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think this works, but I think we should absolutely support overwriting the pandas column and row labels b/c I'm pretty sure that would be my use case at least half the time.
I don't think 1. is guessing b/c they're explicitly passing in labels.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
@story645 adding the support for row/column labels is a straight forward extension that can be added later. I thought it might be nice to get a basic version in (i) because there already has been a lot of discussion and I don't want to overburden the PR with additional tweaks, in particular since this is a first-time contribution, and (ii) because it would be nice to push this into 3.10, which we most likely will miss when doing additional changes. |
I'll go and merge as is. Titles can be handled in a followup PR. |
8e6d6b6
intomatplotlib:mainUh oh!
There was an error while loading.Please reload this page.
@anijjar thanks and congratulations on your first contribution to Matplotlib 🎉. We hope to see you again. |
Uh oh!
There was an error while loading.Please reload this page.
PR summary
Resolves#28726 : feature request: support passing DataFrames to table.table
dawnwangcx wanted a feature to enable building tables using Pandas's DataFrame object. I implemented his solution and made a Unit test in test_table.py to confirm the DataFrame was correctly converted into a table.
PR checklist