diff --git a/doc/source/user_guide/io.rst b/doc/source/user_guide/io.rst index 91cd7315f7213..90a8bd868b60b 100644 --- a/doc/source/user_guide/io.rst +++ b/doc/source/user_guide/io.rst @@ -3785,6 +3785,15 @@ one can pass an :class:`~pandas.io.excel.ExcelWriter`. .. _io.excel_writing_buffer: +When using the ``engine_kwargs`` parameter, pandas will pass these arguments to the +engine. For this, it is important to know which function pandas is using internally. + +* For the engine openpyxl, pandas is using :func:`openpyxl.Workbook` to create a new sheet and :func:`openpyxl.load_workbook` to append data to an existing sheet. The openpyxl engine writes to (``.xlsx``) and (``.xlsm``) files. + +* For the engine xlsxwriter, pandas is using :func:`xlsxwriter.Workbook` to write to (``.xlsx``) files. + +* For the engine odf, pandas is using :func:`odf.opendocument.OpenDocumentSpreadsheet` to write to (``.ods``) files. + Writing Excel files to memory +++++++++++++++++++++++++++++ diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 52fc8512c9db3..316934c44f3ed 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -97,8 +97,10 @@ Other enhancements - Let :meth:`DataFrame.to_feather` accept a non-default :class:`Index` and non-string column names (:issue:`51787`) - Performance improvement in :func:`read_csv` (:issue:`52632`) with ``engine="c"`` - :meth:`Categorical.from_codes` has gotten a ``validate`` parameter (:issue:`50975`) +- Added ``engine_kwargs`` parameter to :meth:`DataFrame.to_excel` (:issue:`53220`) - Performance improvement in :func:`concat` with homogeneous ``np.float64`` or ``np.float32`` dtypes (:issue:`52685`) - Performance improvement in :meth:`DataFrame.filter` when ``items`` is given (:issue:`52941`) +- .. --------------------------------------------------------------------------- .. _whatsnew_210.notable_bug_fixes: diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 93fecc4a7b096..4128b6e3a24c2 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -2155,6 +2155,7 @@ def to_excel( inf_rep: str = "inf", freeze_panes: tuple[int, int] | None = None, storage_options: StorageOptions = None, + engine_kwargs: dict[str, Any] | None = None, ) -> None: """ Write {klass} to an Excel sheet. @@ -2211,6 +2212,8 @@ def to_excel( {storage_options} .. versionadded:: {storage_options_versionadded} + engine_kwargs : dict, optional + Arbitrary keyword arguments passed to excel engine. See Also -------- @@ -2263,6 +2266,8 @@ def to_excel( >>> df1.to_excel('output1.xlsx', engine='xlsxwriter') # doctest: +SKIP """ + if engine_kwargs is None: + engine_kwargs = {} df = self if isinstance(self, ABCDataFrame) else self.to_frame() @@ -2287,6 +2292,7 @@ def to_excel( freeze_panes=freeze_panes, engine=engine, storage_options=storage_options, + engine_kwargs=engine_kwargs, ) @final diff --git a/pandas/io/excel/_xlsxwriter.py b/pandas/io/excel/_xlsxwriter.py index 8cb88403b2f87..d7262c2f62d94 100644 --- a/pandas/io/excel/_xlsxwriter.py +++ b/pandas/io/excel/_xlsxwriter.py @@ -212,7 +212,11 @@ def __init__( engine_kwargs=engine_kwargs, ) - self._book = Workbook(self._handles.handle, **engine_kwargs) + try: + self._book = Workbook(self._handles.handle, **engine_kwargs) + except TypeError: + self._handles.handle.close() + raise @property def book(self): diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 457bbced87d55..41e5c22447833 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -901,6 +901,7 @@ def write( freeze_panes: tuple[int, int] | None = None, engine: str | None = None, storage_options: StorageOptions = None, + engine_kwargs: dict | None = None, ) -> None: """ writer : path-like, file-like, or ExcelWriter object @@ -922,6 +923,8 @@ def write( {storage_options} .. versionadded:: 1.2.0 + engine_kwargs: dict, optional + Arbitrary keyword arguments passed to excel engine. """ from pandas.io.excel import ExcelWriter @@ -932,6 +935,9 @@ def write( f"Max sheet size is: {self.max_rows}, {self.max_cols}" ) + if engine_kwargs is None: + engine_kwargs = {} + formatted_cells = self.get_formatted_cells() if isinstance(writer, ExcelWriter): need_save = False @@ -939,7 +945,10 @@ def write( # error: Cannot instantiate abstract class 'ExcelWriter' with abstract # attributes 'engine', 'save', 'supported_extensions' and 'write_cells' writer = ExcelWriter( # type: ignore[abstract] - writer, engine=engine, storage_options=storage_options + writer, + engine=engine, + storage_options=storage_options, + engine_kwargs=engine_kwargs, ) need_save = True diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 9a8e4eff5470a..0560e12a00bf5 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -11,6 +11,7 @@ import numpy as np import pytest +from pandas.compat._constants import PY310 import pandas.util._test_decorators as td import pandas as pd @@ -1115,6 +1116,38 @@ def test_bytes_io(self, engine): reread_df = pd.read_excel(bio, index_col=0) tm.assert_frame_equal(df, reread_df) + def test_engine_kwargs(self, engine, path): + # GH#52368 + df = DataFrame([{"A": 1, "B": 2}, {"A": 3, "B": 4}]) + + msgs = { + "odf": r"OpenDocumentSpreadsheet() got an unexpected keyword " + r"argument 'foo'", + "openpyxl": r"__init__() got an unexpected keyword argument 'foo'", + "xlsxwriter": r"__init__() got an unexpected keyword argument 'foo'", + } + + if PY310: + msgs[ + "openpyxl" + ] = "Workbook.__init__() got an unexpected keyword argument 'foo'" + msgs[ + "xlsxwriter" + ] = "Workbook.__init__() got an unexpected keyword argument 'foo'" + + # Handle change in error message for openpyxl (write and append mode) + if engine == "openpyxl" and not os.path.exists(path): + msgs[ + "openpyxl" + ] = r"load_workbook() got an unexpected keyword argument 'foo'" + + with pytest.raises(TypeError, match=re.escape(msgs[engine])): + df.to_excel( + path, + engine=engine, + engine_kwargs={"foo": "bar"}, + ) + def test_write_lists_dict(self, path): # see gh-8188. df = DataFrame(