From f0b7fba697c5a57e590e810ba4958b6326591e2c Mon Sep 17 00:00:00 2001 From: Francesco Bonazzi Date: Fri, 16 Jul 2021 12:05:02 +0200 Subject: [PATCH 1/3] ENH: Added autofilter parameter to Excel output for both xlsxwriter and openpyxl engines. --- pandas/core/generic.py | 5 +++++ pandas/io/excel/_base.py | 3 ++- pandas/io/excel/_odswriter.py | 1 + pandas/io/excel/_openpyxl.py | 15 ++++++++++++++- pandas/io/excel/_xlsxwriter.py | 14 +++++++++++++- pandas/io/excel/_xlwt.py | 3 ++- pandas/io/formats/excel.py | 5 +++++ pandas/tests/io/excel/test_writers.py | 7 +++++++ 8 files changed, 49 insertions(+), 4 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index c63aeb736d16a..5077d3bc56e89 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2145,6 +2145,7 @@ def to_excel( inf_rep="inf", verbose=True, freeze_panes=None, + autofilter=False, storage_options: StorageOptions = None, ) -> None: """ @@ -2210,6 +2211,9 @@ def to_excel( freeze_panes : tuple of int (length 2), optional Specifies the one-based bottommost row and rightmost column that is to be frozen. + autofilter : bool, default False + Specifies whether filters should be added to the columns in the + spreadsheet. {storage_options} .. versionadded:: 1.2.0 @@ -2286,6 +2290,7 @@ def to_excel( startrow=startrow, startcol=startcol, freeze_panes=freeze_panes, + autofilter=autofilter, engine=engine, storage_options=storage_options, ) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index 4d6a766ad6cfa..cdf65459a16f1 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -871,7 +871,8 @@ def engine(self): @abc.abstractmethod def write_cells( - self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None + self, cells, sheet_name=None, startrow=0, startcol=0, + freeze_panes=None, autofilter=None ): """ Write given formatted cells into Excel an excel sheet diff --git a/pandas/io/excel/_odswriter.py b/pandas/io/excel/_odswriter.py index fa2779b01d681..b348bf08402eb 100644 --- a/pandas/io/excel/_odswriter.py +++ b/pandas/io/excel/_odswriter.py @@ -62,6 +62,7 @@ def write_cells( startrow: int = 0, startcol: int = 0, freeze_panes: tuple[int, int] | None = None, + autofilter: bool = False, ) -> None: """ Write the frame cells using odf diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index d499f1a5ea89f..768fb2770ab91 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -417,7 +417,8 @@ def _convert_to_protection(cls, protection_dict): return Protection(**protection_dict) def write_cells( - self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None + self, cells, sheet_name=None, startrow=0, startcol=0, + freeze_panes=None, autofilter=False ): # Write the frame cells using openpyxl. sheet_name = self._get_sheet_name(sheet_name) @@ -454,6 +455,9 @@ def write_cells( row=freeze_panes[0] + 1, column=freeze_panes[1] + 1 ) + max_row = 0 + max_col = 0 + for cell in cells: xcell = wks.cell( row=startrow + cell.row + 1, column=startcol + cell.col + 1 @@ -501,6 +505,15 @@ def write_cells( for k, v in style_kwargs.items(): setattr(xcell, k, v) + if startrow + cell.row > max_row: + max_row = startrow + cell.row + if startcol + cell.col > max_col: + max_col = startcol + cell.col + + if autofilter: + from openpyxl.utils import get_column_letter + wks.auto_filter.ref = "A1:" + get_column_letter(max_col + 1) + str(max_row) + class OpenpyxlReader(BaseExcelReader): def __init__( diff --git a/pandas/io/excel/_xlsxwriter.py b/pandas/io/excel/_xlsxwriter.py index 06c73f2c6199e..c06fabf550e06 100644 --- a/pandas/io/excel/_xlsxwriter.py +++ b/pandas/io/excel/_xlsxwriter.py @@ -208,7 +208,8 @@ def save(self): return self.book.close() def write_cells( - self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None + self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None, + autofilter=False ): # Write the frame cells using xlsxwriter. sheet_name = self._get_sheet_name(sheet_name) @@ -224,6 +225,9 @@ def write_cells( if validate_freeze_panes(freeze_panes): wks.freeze_panes(*(freeze_panes)) + max_row = 0 + max_col = 0 + for cell in cells: val, fmt = self._value_with_fmt(cell.val) @@ -248,3 +252,11 @@ def write_cells( ) else: wks.write(startrow + cell.row, startcol + cell.col, val, style) + + if startrow + cell.row > max_row: + max_row = startrow + cell.row + if startcol + cell.col > max_col: + max_col = startcol + cell.col + + if autofilter: + wks.autofilter(0, 0, max_row, max_col) diff --git a/pandas/io/excel/_xlwt.py b/pandas/io/excel/_xlwt.py index 4dadf64b44515..47c2f220d693d 100644 --- a/pandas/io/excel/_xlwt.py +++ b/pandas/io/excel/_xlwt.py @@ -66,7 +66,8 @@ def save(self): self.book.save(self.handles.handle) def write_cells( - self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None + self, cells, sheet_name=None, startrow=0, startcol=0, + freeze_panes=None, autofilter=False, ): sheet_name = self._get_sheet_name(sheet_name) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 0c625e8a68db0..071f512f775c9 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -786,6 +786,7 @@ def write( startrow=0, startcol=0, freeze_panes=None, + autofilter=False, engine=None, storage_options: StorageOptions = None, ): @@ -801,6 +802,9 @@ def write( freeze_panes : tuple of integer (length 2), default None Specifies the one-based bottommost row and rightmost column that is to be frozen + autofilter : bool, default False + Specifies whether filters should be added to the columns in the + spreadsheet. engine : string, default None write engine to use if writer is a path - you can also set this via the options ``io.excel.xlsx.writer``, ``io.excel.xls.writer``, @@ -843,6 +847,7 @@ def write( startrow=startrow, startcol=startcol, freeze_panes=freeze_panes, + autofilter=autofilter, ) finally: # make sure to close opened file handles diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 508e767a47004..097e0f86a8917 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -1270,6 +1270,13 @@ def test_freeze_panes(self, path): result = pd.read_excel(path, index_col=0) tm.assert_frame_equal(result, expected) + def test_autofilter(self, path): + expected = DataFrame([[1, 2], [3, 4]], columns=["col1", "col2"]) + expected.to_excel(path, "Sheet1", autofilter=True) + + result = pd.read_excel(path, index_col=0) + tm.assert_frame_equal(result, expected) + def test_path_path_lib(self, engine, ext): df = tm.makeDataFrame() writer = partial(df.to_excel, engine=engine) From ca13d2e262e8f05575a678d0a6e5cec5a0d0a03c Mon Sep 17 00:00:00 2001 From: Francesco Bonazzi Date: Mon, 19 Jul 2021 13:31:16 +0200 Subject: [PATCH 2/3] code reformatted --- pandas/io/excel/_base.py | 9 +++++++-- pandas/io/excel/_openpyxl.py | 10 ++++++++-- pandas/io/excel/_xlsxwriter.py | 9 +++++++-- pandas/io/excel/_xlwt.py | 9 +++++++-- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index cdf65459a16f1..dad5e75e2322d 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -871,8 +871,13 @@ def engine(self): @abc.abstractmethod def write_cells( - self, cells, sheet_name=None, startrow=0, startcol=0, - freeze_panes=None, autofilter=None + self, + cells, + sheet_name=None, + startrow=0, + startcol=0, + freeze_panes=None, + autofilter=None, ): """ Write given formatted cells into Excel an excel sheet diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index 768fb2770ab91..7702d1b4207c5 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -417,8 +417,13 @@ def _convert_to_protection(cls, protection_dict): return Protection(**protection_dict) def write_cells( - self, cells, sheet_name=None, startrow=0, startcol=0, - freeze_panes=None, autofilter=False + self, + cells, + sheet_name=None, + startrow=0, + startcol=0, + freeze_panes=None, + autofilter=False, ): # Write the frame cells using openpyxl. sheet_name = self._get_sheet_name(sheet_name) @@ -512,6 +517,7 @@ def write_cells( if autofilter: from openpyxl.utils import get_column_letter + wks.auto_filter.ref = "A1:" + get_column_letter(max_col + 1) + str(max_row) diff --git a/pandas/io/excel/_xlsxwriter.py b/pandas/io/excel/_xlsxwriter.py index c06fabf550e06..1fce340ce00a3 100644 --- a/pandas/io/excel/_xlsxwriter.py +++ b/pandas/io/excel/_xlsxwriter.py @@ -208,8 +208,13 @@ def save(self): return self.book.close() def write_cells( - self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None, - autofilter=False + self, + cells, + sheet_name=None, + startrow=0, + startcol=0, + freeze_panes=None, + autofilter=False, ): # Write the frame cells using xlsxwriter. sheet_name = self._get_sheet_name(sheet_name) diff --git a/pandas/io/excel/_xlwt.py b/pandas/io/excel/_xlwt.py index 47c2f220d693d..9996e08edbaa9 100644 --- a/pandas/io/excel/_xlwt.py +++ b/pandas/io/excel/_xlwt.py @@ -66,8 +66,13 @@ def save(self): self.book.save(self.handles.handle) def write_cells( - self, cells, sheet_name=None, startrow=0, startcol=0, - freeze_panes=None, autofilter=False, + self, + cells, + sheet_name=None, + startrow=0, + startcol=0, + freeze_panes=None, + autofilter=False, ): sheet_name = self._get_sheet_name(sheet_name) From 263b2dddda06969f0701abbb0eaa5c72ecf0ed18 Mon Sep 17 00:00:00 2001 From: Francesco Bonazzi Date: Tue, 20 Jul 2021 11:43:04 +0200 Subject: [PATCH 3/3] Added info to user guide and to whatsnew --- doc/source/user_guide/io.rst | 1 + doc/source/whatsnew/v1.4.0.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index 939fd5b832cef..bdef48a3119aa 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -3786,6 +3786,7 @@ The look and feel of Excel worksheets created from pandas can be modified using * ``float_format`` : Format string for floating point numbers (default ``None``). * ``freeze_panes`` : A tuple of two integers representing the bottommost row and rightmost column to freeze. Each of these parameters is one-based, so (1, 1) will freeze the first row and first column (default ``None``). +* ``autofilter`` : A boolean specifying whether to activate Excel's auto filters on the columns of the Excel table. Auto filters allow users to filter and sort the columns by values. Using the `Xlsxwriter`_ engine provides many options for controlling the format of an Excel worksheet created with the ``to_excel`` method. Excellent examples can be found in the diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index 68f1c78688b1d..3266aa6024670 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -34,6 +34,7 @@ Other enhancements - Additional options added to :meth:`.Styler.bar` to control alignment and display, with keyword only arguments (:issue:`26070`, :issue:`36419`) - :meth:`Styler.bar` now validates the input argument ``width`` and ``height`` (:issue:`42511`) - :meth:`Series.ewm`, :meth:`DataFrame.ewm`, now support a ``method`` argument with a ``'table'`` option that performs the windowing operation over an entire :class:`DataFrame`. See :ref:`Window Overview ` for performance and functional benefits (:issue:`42273`) +- ``DataFrame.to_excel()`` has a new ``autofilter`` parameter to turn on Auto Filter when exporting to Excel (:issue:`15307`) - .. ---------------------------------------------------------------------------