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 205a49e7786a7..a4c9260f50a1b 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -91,6 +91,7 @@ Other enhancements - Add support for assigning values to ``by`` argument in :meth:`DataFrame.plot.hist` and :meth:`DataFrame.plot.box` (:issue:`15079`) - :meth:`Series.sample`, :meth:`DataFrame.sample`, and :meth:`.GroupBy.sample` now accept a ``np.random.Generator`` as input to ``random_state``. A generator will be more performant, especially with ``replace=False`` (:issue:`38100`) - :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`) - :meth:`.GroupBy.cummin` and :meth:`.GroupBy.cummax` now support the argument ``skipna`` (:issue:`34047`) - diff --git a/pandas/core/generic.py b/pandas/core/generic.py index e3a54825c5fbc..aef43f76f2967 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2148,6 +2148,7 @@ def to_excel( inf_rep="inf", verbose=True, freeze_panes=None, + autofilter=False, storage_options: StorageOptions = None, ) -> None: """ @@ -2213,6 +2214,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 @@ -2289,6 +2293,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..dad5e75e2322d 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -871,7 +871,13 @@ 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..7702d1b4207c5 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -417,7 +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 + 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 +460,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 +510,16 @@ 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..1fce340ce00a3 100644 --- a/pandas/io/excel/_xlsxwriter.py +++ b/pandas/io/excel/_xlsxwriter.py @@ -208,7 +208,13 @@ 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 +230,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 +257,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..9996e08edbaa9 100644 --- a/pandas/io/excel/_xlwt.py +++ b/pandas/io/excel/_xlwt.py @@ -66,7 +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 + 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)