Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
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
20 changes: 20 additions & 0 deletions 20 doc/users/next_whats_new/pass_pandasDataFrame_into_table.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
``ax.table`` will accept a pandas dataframe
timhoffm marked this conversation as resolved.
Show resolved Hide resolved
--------------------------------------------

The `~Axes.axes.table` method can now accept a Pandas DataFrame for the ``cellText`` argument.
timhoffm marked this conversation as resolved.
Show resolved Hide resolved

.. code-block:: python

import matplotlib.pyplot as plt
import pandas as pd

data = {
'Letter': ['A', 'B', 'C'],
'Number': [100, 200, 300]
}

df = pd.DataFrame(data)
fig, ax = plt.subplots()
table = ax.table(df, loc='center') # or table = ax.table(cellText=df, loc='center')
ax.axis('off')
plt.show()
12 changes: 12 additions & 0 deletions 12 lib/matplotlib/cbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -2444,3 +2444,15 @@ def _auto_format_str(fmt, value):
return fmt % (value,)
except (TypeError, ValueError):
return fmt.format(value)


def _is_pandas_dataframe(x):
"""Check if 'x' is a Pandas DataFrame."""
try:
# we're intentionally not attempting to import Pandas. If somebody
# has created a Pandas DataFrame, Pandas should already be in sys.modules
return isinstance(x, sys.modules['pandas'].DataFrame)
anijjar marked this conversation as resolved.
Show resolved Hide resolved
except Exception: # TypeError, KeyError, AttributeError, maybe others?
# we're attempting to access attributes on imported modules which
# may have arbitrary user code, so we deliberately catch all exceptions
return False
19 changes: 18 additions & 1 deletion 19 lib/matplotlib/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from .transforms import Bbox
from .path import Path

from .cbook import _is_pandas_dataframe


class Cell(Rectangle):
"""
Expand Down Expand Up @@ -674,7 +676,7 @@

Parameters
----------
cellText : 2D list of str, optional
cellText : 2D list of str or pandas.DataFrame, optional
The texts to place into the table cells.

*Note*: Line breaks in the strings are currently not accounted for and
Expand Down Expand Up @@ -744,6 +746,21 @@
cols = len(cellColours[0])
cellText = [[''] * cols] * rows

# Check if we have a Pandas DataFrame
if _is_pandas_dataframe(cellText):
Copy link
Member

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 if cellText has a columns attribute and a to_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

Copy link
Member

@timhoffm timhoffm Sep 24, 2024

Choose a reason for hiding this comment

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

Note that polars.DataFrame.columns is of type list[str], so the current implementation using cellText.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 the dataframe 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 whole Table would need to be rewritten/replaced. Therefore, I wouldn't spend too much effort on trying to improve implementation.

# if rowLabels/colLabels are empty, use DataFrame entries.
# Otherwise, throw an error.

Check warning on line 752 in lib/matplotlib/table.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/table.py#L752

Added line #L752 was not covered by tests
if rowLabels is None:
rowLabels = cellText.index
else:
raise ValueError("rowLabels cannot be used alongside Pandas DataFrame")

Check warning on line 756 in lib/matplotlib/table.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/table.py#L756

Added line #L756 was not covered by tests
if colLabels is None:
colLabels = cellText.columns
else:
raise ValueError("colLabels cannot be used alongside Pandas DataFrame")
# Update cellText with only values
cellText = cellText.values

rows = len(cellText)
cols = len(cellText[0])
for row in cellText:
Expand Down
9 changes: 7 additions & 2 deletions 9 lib/matplotlib/table.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ from .transforms import Bbox
from .typing import ColorType

from collections.abc import Sequence
from typing import Any, Literal
from typing import Any, Literal, TYPE_CHECKING

if TYPE_CHECKING:
Copy link
Member

Choose a reason for hiding this comment

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

Having an if TYPE_CHECKING: in a pyi is kind of redundant, as pyi is only used for type checking.

This does introduce a type-check time requirement on pandas, which I don't fully love, but is not that bad. The if/else here does not protect against not having Pandas (and not even sure a try/except would work in a pyi... it would at least be unusual)

Copy link
Member

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?

Copy link
Contributor Author

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?

from pandas import DataFrame
else:
DataFrame = None
timhoffm marked this conversation as resolved.
Show resolved Hide resolved

class Cell(Rectangle):
PAD: float
Expand Down Expand Up @@ -68,7 +73,7 @@ class Table(Artist):

def table(
ax: Axes,
cellText: Sequence[Sequence[str]] | None = ...,
cellText: Sequence[Sequence[str]] | DataFrame | None = ...,
cellColours: Sequence[Sequence[ColorType]] | None = ...,
cellLoc: Literal["left", "center", "right"] = ...,
colWidths: Sequence[float] | None = ...,
Expand Down
17 changes: 17 additions & 0 deletions 17 lib/matplotlib/tests/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,20 @@ def __repr__(self):

munits.registry.pop(FakeUnit)
assert not munits.registry.get_converter(FakeUnit)


def test_table_dataframe(pd):
# Test if Pandas Data Frame can be passed in cellText

data = {
'Letter': ['A', 'B', 'C'],
'Number': [100, 200, 300]
}

df = pd.DataFrame(data)
fig, ax = plt.subplots()
table = ax.table(df, loc='center')

for r, (index, row) in enumerate(df.iterrows()):
for c, col in enumerate(df.columns if r == 0 else row.values):
assert table[r if r == 0 else r+1, c].get_text().get_text() == str(col)
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.