Skip to content
Open
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ Mars 3390 641.85

```

An iterable of column labels, including a NumPy array, supplies literal
labels such as `"firstrow"` or `"keys"`.

If `headers="firstrow"`, then the first row of data is used:

```pycon
Expand Down Expand Up @@ -151,7 +154,9 @@ called row index. To add a similar column to any other type of table,
pass `showindex="always"` or `showindex=True` argument to `tabulate()`.
To suppress row indices for all types of data, pass `showindex="never"`
or `showindex=False`. To add a custom row index column, pass
`showindex=rowIDs`, where `rowIDs` is some iterable:
`showindex=rowIDs`, where `rowIDs` is some iterable, such as a list,
NumPy array, or pandas Index. Custom row indices replace a DataFrame's
original index and do not inherit its name:

```pycon
>>> print(tabulate([["F",24],["M",19]], showindex="always"))
Expand Down
12 changes: 7 additions & 5 deletions tabulate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1459,16 +1459,15 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"):

"""

try:
bool(headers)
except ValueError: # numpy.ndarray, pandas.core.index.Index, ...
if not isinstance(headers, (str, dict)):
headers = list(headers)

err_msg = (
"\n\nTo build a table python-tabulate requires two-dimensional data "
"like a list of lists or similar."
"\nDid you forget a pair of extra [] or ',' in ()?"
)
showindex_is_a_str = type(showindex) in [str, bytes]
index = None
if hasattr(tabular_data, "keys") and hasattr(tabular_data, "values"):
# dict-like and pandas.DataFrame?
Expand All @@ -1483,7 +1482,11 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"):
elif hasattr(tabular_data, "index"):
# values is a property, has .index => it's likely a pandas.DataFrame (pandas 0.11.0)
keys = list(tabular_data)
if showindex in ["default", "always", True] and tabular_data.index.name is not None:
if (
(showindex_is_a_str or not isinstance(showindex, (Sized, Iterable)))
and showindex in ["default", "always", True]
and tabular_data.index.name is not None
):
if isinstance(tabular_data.index.name, list):
keys[:0] = tabular_data.index.name
else:
Expand Down Expand Up @@ -1590,7 +1593,6 @@ def _normalize_tabular_data(tabular_data, headers, showindex="default"):
rows = [r if _is_separating_line(r) else list(r) for r in rows]

# add or remove an index column
showindex_is_a_str = type(showindex) in [str, bytes]
if showindex_is_a_str and showindex == "default" and index is not None:
rows = _prepend_row_index(rows, index)
elif isinstance(showindex, Sized) and not showindex_is_a_str:
Expand Down
66 changes: 66 additions & 0 deletions test/test_dataframe_indices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""DataFrame row-index options must not compare array-like values as scalars."""

import pytest

from tabulate import tabulate


@pytest.mark.parametrize("index_type", ["array", "Index", "Series"])
@pytest.mark.parametrize("index_name", [None, "original"])
@pytest.mark.parametrize("row_ids", [[], [10, 20], [1], ["default"], ["always"], [True]])
def test_dataframe_array_like_row_indices(index_type, index_name, row_ids):
numpy = pytest.importorskip("numpy")
pandas = pytest.importorskip("pandas")
frame = pandas.DataFrame({"value": range(len(row_ids))})
frame.index.name = index_name
factory = numpy.array if index_type == "array" else getattr(pandas, index_type)
indices = factory(row_ids)

expected = tabulate(frame, headers="keys", showindex=list(indices))
actual = tabulate(frame, headers="keys", showindex=indices)

assert actual == expected
assert "original" not in actual


def test_dataframe_sequence_row_indices():
pandas = pytest.importorskip("pandas")
frame = pandas.DataFrame({"value": [1, 2]})
frame.index.name = "original"

class RowIndices:
def __len__(self):
return 2

def __getitem__(self, index):
return [10, 20][index]

def __eq__(self, other):
raise AssertionError("Custom row indices must not be compared to scalar modes")

assert tabulate(frame, headers="keys", showindex=RowIndices()) == tabulate(
frame, headers="keys", showindex=[10, 20]
)


@pytest.mark.parametrize("showindex", ["default", "always", True, "never", False])
def test_dataframe_scalar_row_index_options(showindex):
pandas = pytest.importorskip("pandas")
frame = pandas.DataFrame({"value": [1, 2]})
frame.index.name = "original"

actual = tabulate(frame, headers="keys", showindex=showindex)

assert ("original" in actual) == (showindex in ["default", "always", True])


def test_dataframe_numpy_boolean_row_index_options():
numpy = pytest.importorskip("numpy")
pandas = pytest.importorskip("pandas")
frame = pandas.DataFrame({"value": [1, 2]})
frame.index.name = "original"

for showindex in (True, False):
assert tabulate(frame, headers="keys", showindex=numpy.bool_(showindex)) == tabulate(
frame, headers="keys", showindex=showindex
)
26 changes: 26 additions & 0 deletions test/test_header_iterables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Iterable column labels must not be interpreted as scalar header modes."""

import pytest

from tabulate import tabulate


@pytest.mark.parametrize("input_type", ["list", "numpy", "dict", "dataframe"])
@pytest.mark.parametrize(
"labels", [["firstrow"], ["keys"], ["literal"], ["firstrow", "keys"], [], [0], [True]]
)
def test_numpy_array_column_labels(input_type, labels):
numpy = pytest.importorskip("numpy")
rows = [["keep first"], ["keep second"]]
if input_type == "numpy":
table = numpy.array(rows)
elif input_type == "dict":
table = {"column": [row[0] for row in rows]}
elif input_type == "dataframe":
pandas = pytest.importorskip("pandas")
table = pandas.DataFrame(rows)
else:
table = rows
headers = numpy.array(labels)

assert tabulate(table, headers=headers) == tabulate(table, headers=list(headers))