Repository: matplotlib/matplotlib

Issue:
feature request: support passing DataFrames to table.table
### Bug summary

When trying to input a dataframe to a table, it will raise keyerror. Since cellText [0] will not give the first row of the dataframe and thus not returning the correct column length. So, I made a modification to recognize cellText as dataframe.
contact: chenxuw@alumni.cmu.edu

### Code for reproduction

```Python
df = pd.DataFrame() #any dataframe
table = table.table(ax,df,loc='center')
```


### Actual outcome

Traceback (most recent call last):
  File "C:\Users\053232\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\indexes\base.py", line 3653, in get_loc
    return self._engine.get_loc(casted_key)
  File "pandas\_libs\index.pyx", line 147, in pandas._libs.index.IndexEngine.get_loc
  File "pandas\_libs\index.pyx", line 176, in pandas._libs.index.IndexEngine.get_loc
  File "pandas\_libs\hashtable_class_helper.pxi", line 7080, in pandas._libs.hashtable.PyObjectHashTable.get_item
  File "pandas\_libs\hashtable_class_helper.pxi", line 7088, in pandas._libs.hashtable.PyObjectHashTable.get_item
KeyError: 0


The above exception was the direct cause of the following exception:


Traceback (most recent call last):
  File "c:/Users/Downloads/suspension_check.py", line 147, in <module>
    generate_image(suspended_etfs,'suspended_etfs')
  File "c:/Users/Downloads/suspension_check.py", line 106, in generate_image
    tbl = table.table(ax, df, loc='center')
  File "C:\Users\AppData\Local\Programs\Python\Python38\lib\site-packages\matplotlib\table.py", line 754, in table
    cols = len(cellText[0])
  File "C:\Users\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\frame.py", line 3761, in __getitem__
    indexer = self.columns.get_loc(key)
  File "C:\Users\AppData\Local\Programs\Python\Python38\lib\site-packages\pandas\core\indexes\base.py", line 3655, in get_loc
    raise KeyError(key) from err
KeyError: 0

### Expected outcome

 return a table contains a dataframe 

### Additional information

# Check we have some cellText
    if cellText is None:
        # assume just colours are needed
        rows = len(cellColours)
        cols = len(cellColours[0])
        cellText = [[''] * cols] * rows
    elif isinstance(cellText, pd.DataFrame):
        # 获取DataFrame的列名
        column_names = cellText.columns.values
        cellText = np.vstack([column_names, cellText.values])

### Operating system

_No response_

### Matplotlib Version

3.7.5

### Matplotlib Backend

_No response_

### Python version

_No response_

### Jupyter version

_No response_

### Installation

pip


Discussion:
I would not consider this a bug a the input is documented as a 2D list of strings (https://matplotlib.org/stable/api/table_api.html#matplotlib.table.table) .

That said, this may be a reasonable feature request as I don't think it would be too hard to detect if we were passed a `pd.DataFrame` and then convert it to a list-of-list-of-str at the top of `table.table`.
@dawnwangcx You are welcome to submit you proposal as a pull request.



Patch:
diff --git a/doc/users/next_whats_new/pass_pandasDataFrame_into_table.rst b/doc/users/next_whats_new/pass_pandasDataFrame_into_table.rst
new file mode 100644
index 000000000000..aaefa1b2011b
--- /dev/null
+++ b/doc/users/next_whats_new/pass_pandasDataFrame_into_table.rst
@@ -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()
diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py
index 2411784af3ec..4267c99aa34f 100644
--- a/lib/matplotlib/cbook.py
+++ b/lib/matplotlib/cbook.py
@@ -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
diff --git a/lib/matplotlib/table.py b/lib/matplotlib/table.py
index 0f75021926fd..a344351ecc0d 100644
--- a/lib/matplotlib/table.py
+++ b/lib/matplotlib/table.py
@@ -33,6 +33,8 @@
 from .transforms import Bbox
 from .path import Path
 
+from .cbook import _is_pandas_dataframe
+
 
 class Cell(Rectangle):
     """
@@ -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
@@ -744,6 +746,21 @@ def table(ax,
         cols = len(cellColours[0])
         cellText = [[''] * cols] * rows
 
+    # Check if we have a Pandas DataFrame
+    if _is_pandas_dataframe(cellText):
+        # if rowLabels/colLabels are empty, use DataFrame entries.
+        # Otherwise, throw an error.
+        if rowLabels is None:
+            rowLabels = cellText.index
+        else:
+            raise ValueError("rowLabels cannot be used alongside Pandas DataFrame")
+        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:
diff --git a/lib/matplotlib/table.pyi b/lib/matplotlib/table.pyi
index 0108ecd99f89..07d2427f66dc 100644
--- a/lib/matplotlib/table.pyi
+++ b/lib/matplotlib/table.pyi
@@ -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
@@ -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 = ...,


Test patch:
diff --git a/lib/matplotlib/tests/test_table.py b/lib/matplotlib/tests/test_table.py
index ea31ac124e4a..61d8ff2aad47 100644
--- a/lib/matplotlib/tests/test_table.py
+++ b/lib/matplotlib/tests/test_table.py
@@ -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)