From 30c4e9f1278d53b36cfaecbe56c5fb4baa9a7592 Mon Sep 17 00:00:00 2001 From: Sskift <163287349+Sskift@users.noreply.github.com> Date: Tue, 3 Nov 2026 01:39:14 +0800 Subject: [PATCH] Omit hidden DataFrame index from first-row headers --- README.md | 3 +++ tabulate/__init__.py | 4 +++- test/test_dataframe_firstrow.py | 38 +++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 test/test_dataframe_firstrow.py diff --git a/README.md b/README.md index 0283a0c..7ea5617 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,9 @@ or `showindex=False`. To add a custom row index column, pass ``` +With `headers="firstrow"`, hiding a DataFrame's row index also omits its +first index value from the header. + ### Table format There is more than one way to format a table in plain text. The third diff --git a/tabulate/__init__.py b/tabulate/__init__.py index 12a2950..e8a7029 100644 --- a/tabulate/__init__.py +++ b/tabulate/__init__.py @@ -1574,6 +1574,7 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"): headers = list(map(str, range(len(rows[0])))) # take headers from the first row if necessary + index_header_from_firstrow = headers == "firstrow" and index is not None if headers == "firstrow" and len(rows) > 0: if index is not None: headers = [index[0]] + list(rows[0]) @@ -1602,7 +1603,8 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"): index = list(range(len(rows))) rows = _prepend_row_index(rows, index) elif showindex == "never" or (not _bool(showindex) and not showindex_is_a_str): - pass + if index_header_from_firstrow: + headers = headers[1:] # pad with empty headers for initial columns if necessary headers_pad = 0 diff --git a/test/test_dataframe_firstrow.py b/test/test_dataframe_firstrow.py new file mode 100644 index 0000000..0da00ec --- /dev/null +++ b/test/test_dataframe_firstrow.py @@ -0,0 +1,38 @@ +"""First-row headers must respect whether the DataFrame index is shown.""" + +import pytest + +from tabulate import tabulate + + +@pytest.mark.parametrize("tablefmt", ["simple", "grid", "html"]) +@pytest.mark.parametrize("row_count", [0, 1, 2]) +@pytest.mark.parametrize("showindex", [False, "never"]) +def test_dataframe_firstrow_without_index(tablefmt, row_count, showindex): + pandas = pytest.importorskip("pandas") + rows = [["Name", "Age"]] + [[f"Person {i}", i] for i in range(row_count)] + frame = pandas.DataFrame(rows, index=["row label"] + list(range(row_count))) + + assert tabulate(frame, headers="firstrow", showindex=showindex, tablefmt=tablefmt) == tabulate( + rows, headers="firstrow", tablefmt=tablefmt + ) + + +@pytest.mark.parametrize("showindex", [True, "always", "default"]) +def test_dataframe_firstrow_with_index(showindex): + pandas = pytest.importorskip("pandas") + frame = pandas.DataFrame([["Name", "Age"], ["Alice", 30]], index=["row label", "first"]) + + assert tabulate(frame, headers="firstrow", showindex=showindex) == tabulate( + [["row label", "Name", "Age"], ["first", "Alice", 30]], headers="firstrow" + ) + + +def test_dataframe_firstrow_numpy_false(): + numpy = pytest.importorskip("numpy") + pandas = pytest.importorskip("pandas") + rows = [["Name", "Age"], ["Alice", 30]] + + assert tabulate( + pandas.DataFrame(rows), headers="firstrow", showindex=numpy.bool_(False) + ) == tabulate(rows, headers="firstrow")