Skip to content

Commit ecfd509

Browse files
committed
fix: Ensure that ndex size is considered when setting autofilter range.
feat: Throw an error when autofilter set for 'odf' engine. feat: Shows warning when autofilter is set together with merge_cells. chore: Added tests for index=True, startrow/startcol, multindex with merge_cells.
1 parent f69c62f commit ecfd509

File tree

3 files changed

+94
-4
lines changed

3 files changed

+94
-4
lines changed

pandas/io/excel/_odswriter.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ def _write_cells(
104104
"""
105105
Write the frame cells using odf
106106
"""
107+
108+
if autofilter_range:
109+
raise ValueError("Autofilter is not supported with odf!")
110+
107111
from odf.table import (
108112
Table,
109113
TableCell,

pandas/io/formats/excel.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,8 +949,26 @@ def write(
949949
)
950950

951951
if self.autofilter:
952+
if num_cols == 0:
953+
indexoffset = 0
954+
elif self.index:
955+
if isinstance(self.df.index, MultiIndex):
956+
indexoffset = self.df.index.nlevels - 1
957+
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(),
964+
)
965+
else:
966+
indexoffset = 0
967+
else:
968+
indexoffset = -1
952969
start = f"{self._num2excel(startcol)}{startrow + 1}"
953-
end = f"{self._num2excel(startcol + num_cols)}{startrow + num_rows + 1}"
970+
autofilter_end_column = self._num2excel(startcol + num_cols + indexoffset)
971+
end = f"{autofilter_end_column}{startrow + num_rows + 1}"
954972
autofilter_range = f"{start}:{end}"
955973
else:
956974
autofilter_range = None

pandas/tests/io/excel/test_style.py

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,16 +353,84 @@ def test_format_hierarchical_rows_periodindex(merge_cells):
353353

354354

355355
@pytest.mark.parametrize("engine", ["xlsxwriter", "openpyxl"])
356-
def test_autofilter(engine, tmp_excel):
356+
@pytest.mark.parametrize("with_index", [True, False])
357+
def test_autofilter(engine, with_index, tmp_excel):
357358
# GH 61194
358359
df = DataFrame.from_dict([{"A": 1, "B": 2, "C": 3}, {"A": 4, "B": 5, "C": 6}])
359360

360361
with ExcelWriter(tmp_excel, engine=engine) as writer:
361-
df.to_excel(writer, autofilter=True, index=False)
362+
df.to_excel(writer, autofilter=True, index=with_index)
362363

363364
openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
364365
with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
365366
ws = wb.active
366367

367368
assert ws.auto_filter.ref is not None
368-
assert ws.auto_filter.ref == "A1:D3"
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+
mi_df = df.set_index(["animal", "color of fur"])
409+
with ExcelWriter(tmp_excel, engine=engine) as writer:
410+
mi_df.to_excel(writer, autofilter=True, index=True, merge_cells=False)
411+
412+
openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
413+
with contextlib.closing(openpyxl.load_workbook(tmp_excel)) as wb:
414+
ws = wb.active
415+
416+
assert ws.auto_filter.ref is not None
417+
assert ws.auto_filter.ref == "A1:C5"
418+
419+
420+
def test_autofilter_with_multiindex_and_merge_cells_shows_warning(tmp_excel):
421+
# GH 61194
422+
df = DataFrame(
423+
{
424+
"animal": ("horse", "horse", "dog", "dog"),
425+
"color of fur": ("black", "white", "grey", "black"),
426+
"name": ("Blacky", "Wendy", "Rufus", "Catchy"),
427+
}
428+
)
429+
mi_df = df.set_index(["animal", "color of fur"])
430+
with ExcelWriter(tmp_excel, engine="openpyxl") as writer:
431+
with tm.assert_produces_warning(
432+
UserWarning,
433+
match="Excel filters merged cells by showing only the first row."
434+
"'autofiler' and 'merge_cells' should not be used simultaneously.",
435+
):
436+
mi_df.to_excel(writer, autofilter=True, index=True, merge_cells=True)

0 commit comments

Comments
 (0)