Skip to content

Commit 1f33aad

Browse files
committed
chore: move autofilter tests from test_style to test_writers
fix: Handle multindex columns offset feat: If autofilter is set together with merge_cells, raise an exception
1 parent d87e245 commit 1f33aad

File tree

3 files changed

+184
-97
lines changed

3 files changed

+184
-97
lines changed

pandas/io/formats/excel.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -949,26 +949,41 @@ def write(
949949
)
950950

951951
if self.autofilter:
952+
# default offset for header row
953+
startrowsoffset = 1
954+
endrowsoffset = 1
955+
952956
if num_cols == 0:
953957
indexoffset = 0
954958
elif self.index:
959+
indexoffset = 0
955960
if isinstance(self.df.index, MultiIndex):
956-
indexoffset = self.df.index.nlevels - 1
957961
if self.merge_cells:
958-
warnings.warn(
959-
"Excel filters merged cells by showing only the first row."
960-
"'autofiler' and 'merge_cells' should not "
961-
"be used simultaneously.",
962-
UserWarning,
963-
stacklevel=find_stack_level(),
962+
raise ValueError(
963+
"Excel filters merged cells by showing only the first row. "
964+
"'autofilter' and 'merge_cells' cannot "
965+
"be used simultaneously."
966+
)
967+
else:
968+
indexoffset = self.df.index.nlevels - 1
969+
970+
if isinstance(self.columns, MultiIndex):
971+
if self.merge_cells:
972+
raise ValueError(
973+
"Excel filters merged cells by showing only the first row. "
974+
"'autofilter' and 'merge_cells' cannot "
975+
"be used simultaneously."
964976
)
965-
else:
966-
indexoffset = 0
977+
else:
978+
startrowsoffset = self.columns.nlevels
979+
# multindex columns add a blank row between header and data
980+
endrowsoffset = self.columns.nlevels + 1
967981
else:
982+
# no index column
968983
indexoffset = -1
969-
start = f"{self._num2excel(startcol)}{startrow + 1}"
984+
start = f"{self._num2excel(startcol)}{startrow + startrowsoffset}"
970985
autofilter_end_column = self._num2excel(startcol + num_cols + indexoffset)
971-
end = f"{autofilter_end_column}{startrow + num_rows + 1}"
986+
end = f"{autofilter_end_column}{startrow + num_rows + endrowsoffset}"
972987
autofilter_range = f"{start}:{end}"
973988
else:
974989
autofilter_range = None

pandas/tests/io/excel/test_style.py

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -350,89 +350,3 @@ def test_format_hierarchical_rows_periodindex(merge_cells):
350350
assert isinstance(cell.val, Timestamp), (
351351
"Period should be converted to Timestamp"
352352
)
353-
354-
355-
@pytest.mark.parametrize("engine", ["xlsxwriter", "openpyxl"])
356-
@pytest.mark.parametrize("with_index", [True, False])
357-
def test_autofilter(engine, with_index, tmp_excel):
358-
# GH 61194
359-
df = DataFrame.from_dict([{"A": 1, "B": 2, "C": 3}, {"A": 4, "B": 5, "C": 6}])
360-
361-
with ExcelWriter(tmp_excel, engine=engine) as writer:
362-
df.to_excel(writer, autofilter=True, index=with_index)
363-
364-
openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
365-
with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
366-
ws = wb.active
367-
368-
assert ws.auto_filter.ref is not None
369-
assert ws.auto_filter.ref == "A1:D3" if with_index else "A1:C3"
370-
371-
372-
@pytest.mark.parametrize("engine", ["xlsxwriter", "openpyxl"])
373-
def test_autofilter_with_startrow_startcol(engine, tmp_excel):
374-
# GH 61194
375-
df = DataFrame.from_dict([{"A": 1, "B": 2, "C": 3}, {"A": 4, "B": 5, "C": 6}])
376-
with ExcelWriter(tmp_excel, engine=engine) as writer:
377-
df.to_excel(writer, autofilter=True, startrow=10, startcol=10)
378-
379-
openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
380-
with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
381-
ws = wb.active
382-
assert ws.auto_filter.ref is not None
383-
# Autofiler range moved by 10x10 cells
384-
assert ws.auto_filter.ref == "K11:N13"
385-
386-
387-
def test_autofilter_not_supported_by_odf(tmp_path):
388-
# GH 61194
389-
# odf needs 'ods' extension
390-
tmp_excel_ods = tmp_path / f"{uuid.uuid4()}.ods"
391-
tmp_excel_ods.touch()
392-
393-
with pytest.raises(ValueError, match="Autofilter is not supported with odf!"):
394-
with ExcelWriter(str(tmp_excel_ods), engine="odf") as writer:
395-
DataFrame().to_excel(writer, autofilter=True, index=False)
396-
397-
398-
@pytest.mark.parametrize("engine", ["xlsxwriter", "openpyxl"])
399-
def test_autofilter_with_multiindex(engine, tmp_excel):
400-
# GH 61194
401-
df = DataFrame(
402-
{
403-
"animal": ("horse", "horse", "dog", "dog"),
404-
"color of fur": ("black", "white", "grey", "black"),
405-
"name": ("Blacky", "Wendy", "Rufus", "Catchy"),
406-
}
407-
)
408-
# setup hierarchical index
409-
mi_df = df.set_index(["animal", "color of fur"])
410-
with ExcelWriter(tmp_excel, engine=engine) as writer:
411-
mi_df.to_excel(writer, autofilter=True, index=True, merge_cells=False)
412-
413-
openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
414-
with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
415-
ws = wb.active
416-
417-
assert ws.auto_filter.ref is not None
418-
assert ws.auto_filter.ref == "A1:C5"
419-
420-
421-
def test_autofilter_with_multiindex_and_merge_cells_shows_warning(tmp_excel):
422-
# GH 61194
423-
df = DataFrame(
424-
{
425-
"animal": ("horse", "horse", "dog", "dog"),
426-
"color of fur": ("black", "white", "grey", "black"),
427-
"name": ("Blacky", "Wendy", "Rufus", "Catchy"),
428-
}
429-
)
430-
# setup hierarchical index
431-
mi_df = df.set_index(["animal", "color of fur"])
432-
with ExcelWriter(tmp_excel, engine="openpyxl") as writer:
433-
with tm.assert_produces_warning(
434-
UserWarning,
435-
match="Excel filters merged cells by showing only the first row."
436-
"'autofiler' and 'merge_cells' should not be used simultaneously.",
437-
):
438-
mi_df.to_excel(writer, autofilter=True, index=True, merge_cells=True)

pandas/tests/io/excel/test_writers.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
from datetime import (
23
date,
34
datetime,
@@ -1510,6 +1511,163 @@ def test_to_excel_raising_warning_when_cell_character_exceed_limit(self):
15101511
buf = BytesIO()
15111512
df.to_excel(buf)
15121513

1514+
@pytest.mark.parametrize("with_index", [True, False])
1515+
def test_autofilter(self, engine, with_index, tmp_excel):
1516+
# GH 61194
1517+
df = DataFrame.from_dict([{"A": 1, "B": 2, "C": 3}, {"A": 4, "B": 5, "C": 6}])
1518+
1519+
if engine in ["odf"]:
1520+
with pytest.raises(
1521+
ValueError, match="Autofilter is not supported with odf!"
1522+
):
1523+
df.to_excel(tmp_excel, engine=engine, autofilter=True, index=False)
1524+
else:
1525+
df.to_excel(tmp_excel, engine=engine, autofilter=True, index=with_index)
1526+
1527+
openpyxl = pytest.importorskip(
1528+
"openpyxl"
1529+
) # test loading only with openpyxl
1530+
with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
1531+
ws = wb.active
1532+
1533+
assert ws.auto_filter.ref is not None
1534+
assert ws.auto_filter.ref == "A1:D3" if with_index else "A1:C3"
1535+
1536+
def test_autofilter_with_startrow_startcol(self, engine, tmp_excel):
1537+
# GH 61194
1538+
df = DataFrame.from_dict([{"A": 1, "B": 2, "C": 3}, {"A": 4, "B": 5, "C": 6}])
1539+
1540+
if engine in ["odf"]:
1541+
# odf does not support autofilter
1542+
with pytest.raises(
1543+
ValueError, match="Autofilter is not supported with odf!"
1544+
):
1545+
df.to_excel(tmp_excel, engine=engine, autofilter=True, index=False)
1546+
else:
1547+
df.to_excel(
1548+
tmp_excel, engine=engine, autofilter=True, startrow=10, startcol=10
1549+
)
1550+
1551+
openpyxl = pytest.importorskip(
1552+
"openpyxl"
1553+
) # test loading only with openpyxl
1554+
with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
1555+
ws = wb.active
1556+
assert ws.auto_filter.ref is not None
1557+
# Autofiler range moved by 10x10 cells
1558+
assert ws.auto_filter.ref == "K11:N13"
1559+
1560+
@pytest.mark.parametrize("merge_cells", [True, False])
1561+
def test_autofilter_with_multiindex_index(self, engine, tmp_excel, merge_cells):
1562+
# GH 61194
1563+
df = DataFrame(
1564+
{
1565+
"animal": ("horse", "horse", "dog", "dog"),
1566+
"color of fur": ("black", "white", "grey", "black"),
1567+
"name": ("Blacky", "Wendy", "Rufus", "Catchy"),
1568+
}
1569+
)
1570+
# setup hierarchical index
1571+
mi_df = df.set_index(["animal", "color of fur"])
1572+
if engine in ["odf"]:
1573+
# odf does not support autofilter
1574+
with pytest.raises(
1575+
ValueError, match="Autofilter is not supported with odf!"
1576+
):
1577+
mi_df.to_excel(
1578+
tmp_excel,
1579+
engine=engine,
1580+
autofilter=True,
1581+
index=False,
1582+
merge_cells=merge_cells,
1583+
)
1584+
elif merge_cells:
1585+
# multiindex and merge cells cannot be used simultaneously
1586+
with pytest.raises(
1587+
ValueError,
1588+
match="Excel filters merged cells by showing only the first row. "
1589+
"'autofilter' and 'merge_cells' cannot be used simultaneously.",
1590+
):
1591+
mi_df.to_excel(
1592+
tmp_excel,
1593+
engine=engine,
1594+
autofilter=True,
1595+
index=True,
1596+
merge_cells=merge_cells,
1597+
)
1598+
else:
1599+
mi_df.to_excel(
1600+
tmp_excel,
1601+
engine=engine,
1602+
autofilter=True,
1603+
index=True,
1604+
merge_cells=merge_cells,
1605+
)
1606+
1607+
# validate autofilter range
1608+
openpyxl = pytest.importorskip(
1609+
"openpyxl"
1610+
) # test loading only with openpyxl
1611+
with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
1612+
ws = wb.active
1613+
1614+
assert ws.auto_filter.ref is not None
1615+
assert ws.auto_filter.ref == "A1:C5"
1616+
1617+
@pytest.mark.parametrize("merge_cells", [True, False])
1618+
def test_autofilter_with_multiindex_columns(self, engine, tmp_excel, merge_cells):
1619+
# GH 61194
1620+
columns = MultiIndex(
1621+
levels=[["x", "y"], ["w", "t"]],
1622+
codes=[[0, 0, 1], [0, 1, 0]],
1623+
)
1624+
df = DataFrame([[1, 2, 3], [4, 5, 6]], columns=columns)
1625+
1626+
if engine in ["odf"]:
1627+
# odf does not support autofilter
1628+
with pytest.raises(
1629+
ValueError, match="Autofilter is not supported with odf!"
1630+
):
1631+
df.to_excel(
1632+
tmp_excel,
1633+
engine=engine,
1634+
autofilter=True,
1635+
index=False,
1636+
merge_cells=merge_cells,
1637+
)
1638+
elif merge_cells:
1639+
# multiindex and merge cells cannot be used simultaneously
1640+
with pytest.raises(
1641+
ValueError,
1642+
match="Excel filters merged cells by showing only the first row. "
1643+
"'autofilter' and 'merge_cells' cannot be used simultaneously.",
1644+
):
1645+
df.to_excel(
1646+
tmp_excel,
1647+
engine=engine,
1648+
autofilter=True,
1649+
index=True,
1650+
merge_cells=merge_cells,
1651+
)
1652+
else:
1653+
df.to_excel(
1654+
tmp_excel,
1655+
engine=engine,
1656+
autofilter=True,
1657+
index=True,
1658+
merge_cells=merge_cells,
1659+
)
1660+
1661+
# validate autofilter range
1662+
openpyxl = pytest.importorskip(
1663+
"openpyxl"
1664+
) # test loading only with openpyxl
1665+
with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
1666+
ws = wb.active
1667+
1668+
assert ws.auto_filter.ref is not None
1669+
assert ws.auto_filter.ref == "A2:D5"
1670+
15131671

15141672
class TestExcelWriterEngineTests:
15151673
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)