Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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 all 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 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
--------------------------------------------

The `~.axes.Axes.table` method can now accept a Pandas DataFrame for the ``cellText`` argument.

.. 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 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)
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 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
6 changes: 4 additions & 2 deletions lib/matplotlib/table.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ 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

from pandas import DataFrame

class Cell(Rectangle):
PAD: float
Expand Down Expand Up @@ -68,7 +70,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 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