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 10 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
21 changes: 21 additions & 0 deletions 21 doc/users/next_whats_new/pass_pandasDataFrame_into_table.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
``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 data frame for the ``cellText`` method, which
anijjar marked this conversation as resolved.
Show resolved Hide resolved
it attempts to render with column headers set by ``df.columns.to_numpy()`` and cell data set by ``df.to_numpy()``.
anijjar 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 @@
return fmt % (value,)
except (TypeError, ValueError):
return fmt.format(value)


Check warning on line 2448 in lib/matplotlib/cbook.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/cbook.py#L2448

Added line #L2448 was not covered by tests
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
11 changes: 10 additions & 1 deletion 11 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 @@ def table(ax,

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,13 @@ def table(ax,
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.

# Convert to numpy array
header = cellText.columns.to_numpy()
data = cellText.to_numpy()
cellText = np.vstack([header, data])

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
22 changes: 22 additions & 0 deletions 22 lib/matplotlib/tests/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,25 @@ 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')

assert table[0, 0].get_text().get_text() == 'Letter'
assert table[0, 1].get_text().get_text() == 'Number'
assert table[1, 0].get_text().get_text() == 'A'
assert table[1, 1].get_text().get_text() == str(100)
assert table[2, 0].get_text().get_text() == 'B'
assert table[2, 1].get_text().get_text() == str(200)
assert table[3, 0].get_text().get_text() == 'C'
assert table[3, 1].get_text().get_text() == str(300)
anijjar marked this conversation as resolved.
Show resolved Hide resolved
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.