diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 55bfb044fb31d..47b673ab4a663 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -850,6 +850,7 @@ I/O - Bug in :func:`read_sas` returned ``None`` rather than an empty DataFrame for SAS7BDAT files with zero rows (:issue:`18198`) - Bug in :class:`StataWriter` where value labels were always written with default encoding (:issue:`46750`) - Bug in :class:`StataWriterUTF8` where some valid characters were removed from variable names (:issue:`47276`) +- Bug affecting :func:`read_csv`, :func:`read_excel`, :func:`read_html`, and :func:`read_table`, which accepted decreasing multi-index header arguments even though they don't work (:issue:`47011`) Period ^^^^^^ diff --git a/pandas/io/common.py b/pandas/io/common.py index 5aecc55bb363a..56bc344d47c40 100644 --- a/pandas/io/common.py +++ b/pandas/io/common.py @@ -198,6 +198,8 @@ def validate_header_arg(header: object) -> None: raise ValueError("header must be integer or list of integers") if any(i < 0 for i in header): raise ValueError("cannot specify multi-index header with negative integers") + if list(header) != sorted(set(header)): + raise ValueError("multi-index header elements must be increasing") return if is_bool(header): raise TypeError( diff --git a/pandas/tests/io/parser/test_header.py b/pandas/tests/io/parser/test_header.py index 3fc23525df89e..46e53f11ff722 100644 --- a/pandas/tests/io/parser/test_header.py +++ b/pandas/tests/io/parser/test_header.py @@ -61,6 +61,20 @@ def test_negative_multi_index_header(all_parsers, header): parser.read_csv(StringIO(data), header=header) +@pytest.mark.parametrize("header", [([0, 0]), ([1, 0])]) +def test_nonincreasing_multi_index_header(all_parsers, header): + # see gh-47011 + parser = all_parsers + data = """1,2,3,4,5 + 6,7,8,9,10 + 11,12,13,14,15 + """ + with pytest.raises( + ValueError, match="multi-index header elements must be increasing" + ): + parser.read_csv(StringIO(data), header=header) + + @pytest.mark.parametrize("header", [True, False]) def test_bool_header_arg(all_parsers, header): # see gh-6114