`__ for xls files
* `xlsxwriter @doc(storage_options=_shared_docs["storage_options"])
class ExcelWriter(metaclass=abc.ABCMeta):
"""
Class for writing DataFrame objects into excel sheets.
Default is to use:
* `xlwt <https://pypi.org/project/xlwt/>`__ for xls files
* `xlsxwriter <https://pypi.org/project/XlsxWriter/>`__ for xlsx files if xlsxwriter
is installed otherwise `openpyxl <https://pypi.org/project/openpyxl/>`__
* `odswriter <https://pypi.org/project/odswriter/>`__ for ods files
See ``DataFrame.to_excel`` for typical usage.
The writer should be used as a context manager. Otherwise, call `close()` to save
and close any opened file handles.
Parameters
----------
path : str or typing.BinaryIO
Path to xls or xlsx or ods file.
engine : str (optional)
Engine to use for writing. If None, defaults to
``io.excel.<extension>.writer``. NOTE: can only be passed as a keyword
argument.
.. deprecated:: 1.2.0
As the `xlwt <https://pypi.org/project/xlwt/>`__ package is no longer
maintained, the ``xlwt`` engine will be removed in a future
version of pandas.
date_format : str, default None
Format string for dates written into Excel files (e.g. 'YYYY-MM-DD').
datetime_format : str, default None
Format string for datetime objects written into Excel files.
(e.g. 'YYYY-MM-DD HH:MM:SS').
mode : {{'w', 'a'}}, default 'w'
File mode to use (write or append). Append does not work with fsspec URLs.
{storage_options}
.. versionadded:: 1.2.0
if_sheet_exists : {{'error', 'new', 'replace', 'overlay'}}, default 'error'
How to behave when trying to write to a sheet that already
exists (append mode only).
* error: raise a ValueError.
* new: Create a new sheet, with a name determined by the engine.
* replace: Delete the contents of the sheet before writing to it.
* overlay: Write contents to the existing sheet without removing the old
contents.
.. versionadded:: 1.3.0
.. versionchanged:: 1.4.0
Added ``overlay`` option
engine_kwargs : dict, optional
Keyword arguments to be passed into the engine. These will be passed to
the following functions of the respective engines:
* xlsxwriter: ``xlsxwriter.Workbook(file, **engine_kwargs)``
* openpyxl (write mode): ``openpyxl.Workbook(**engine_kwargs)``
* openpyxl (append mode): ``openpyxl.load_workbook(file, **engine_kwargs)``
* odswriter: ``odf.opendocument.OpenDocumentSpreadsheet(**engine_kwargs)``
.. versionadded:: 1.3.0
**kwargs : dict, optional
Keyword arguments to be passed into the engine.
.. deprecated:: 1.3.0
Use engine_kwargs instead.
Notes
-----
For compatibility with CSV writers, ExcelWriter serializes lists
and dicts to strings before writing.
Examples
--------
Default usage:
>>> df = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) # doctest: +SKIP
>>> with pd.ExcelWriter("path_to_file.xlsx") as writer:
... df.to_excel(writer) # doctest: +SKIP
To write to separate sheets in a single file:
>>> df1 = pd.DataFrame([["AAA", "BBB"]], columns=["Spam", "Egg"]) # doctest: +SKIP
>>> df2 = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) # doctest: +SKIP
>>> with pd.ExcelWriter("path_to_file.xlsx") as writer:
... df1.to_excel(writer, sheet_name="Sheet1") # doctest: +SKIP
... df2.to_excel(writer, sheet_name="Sheet2") # doctest: +SKIP
You can set the date format or datetime format:
>>> from datetime import date, datetime # doctest: +SKIP
>>> df = pd.DataFrame(
... [
... [date(2014, 1, 31), date(1999, 9, 24)],
... [datetime(1998, 5, 26, 23, 33, 4), datetime(2014, 2, 28, 13, 5, 13)],
... ],
... index=["Date", "Datetime"],
... columns=["X", "Y"],
... ) # doctest: +SKIP
>>> with pd.ExcelWriter(
... "path_to_file.xlsx",
... date_format="YYYY-MM-DD",
... datetime_format="YYYY-MM-DD HH:MM:SS"
... ) as writer:
... df.to_excel(writer) # doctest: +SKIP
You can also append to an existing Excel file:
>>> with pd.ExcelWriter("path_to_file.xlsx", mode="a", engine="openpyxl") as writer:
... df.to_excel(writer, sheet_name="Sheet3") # doctest: +SKIP
Here, the `if_sheet_exists` parameter can be set to replace a sheet if it
already exists:
>>> with ExcelWriter(
... "path_to_file.xlsx",
... mode="a",
... engine="openpyxl",
... if_sheet_exists="replace",
... ) as writer:
... df.to_excel(writer, sheet_name="Sheet1") # doctest: +SKIP
You can also write multiple DataFrames to a single sheet. Note that the
``if_sheet_exists`` parameter needs to be set to ``overlay``:
>>> with ExcelWriter("path_to_file.xlsx",
... mode="a",
... engine="openpyxl",
... if_sheet_exists="overlay",
... ) as writer:
... df1.to_excel(writer, sheet_name="Sheet1")
... df2.to_excel(writer, sheet_name="Sheet1", startcol=3) # doctest: +SKIP
You can store Excel file in RAM:
>>> import io
>>> df = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"])
>>> buffer = io.BytesIO()
>>> with pd.ExcelWriter(buffer) as writer:
... df.to_excel(writer)
You can pack Excel file into zip archive:
>>> import zipfile # doctest: +SKIP
>>> df = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) # doctest: +SKIP
>>> with zipfile.ZipFile("path_to_file.zip", "w") as zf:
... with zf.open("filename.xlsx", "w") as buffer:
... with pd.ExcelWriter(buffer) as writer:
... df.to_excel(writer) # doctest: +SKIP
You can specify additional arguments to the underlying engine:
>>> with pd.ExcelWriter(
... "path_to_file.xlsx",
... engine="xlsxwriter",
... engine_kwargs={{"options": {{"nan_inf_to_errors": True}}}}
... ) as writer:
... df.to_excel(writer) # doctest: +SKIP
In append mode, ``engine_kwargs`` are passed through to
openpyxl's ``load_workbook``:
>>> with pd.ExcelWriter(
... "path_to_file.xlsx",
... engine="openpyxl",
... mode="a",
... engine_kwargs={{"keep_vba": True}}
... ) as writer:
... df.to_excel(writer, sheet_name="Sheet2") # doctest: +SKIP
"""
# Defining an ExcelWriter implementation (see abstract methods for more...)
# - Mandatory
# - ``write_cells(self, cells, sheet_name=None, startrow=0, startcol=0)``
# --> called to write additional DataFrames to disk
# - ``_supported_extensions`` (tuple of supported extensions), used to
# check that engine supports the given extension.
# - ``_engine`` - string that gives the engine name. Necessary to
# instantiate class directly and bypass ``ExcelWriterMeta`` engine
# lookup.
# - ``save(self)`` --> called to save file to disk
# - Mostly mandatory (i.e. should at least exist)
# - book, cur_sheet, path
# - Optional:
# - ``__init__(self, path, engine=None, **kwargs)`` --> always called
# with path as first argument.
# You also need to register the class with ``register_writer()``.
# Technically, ExcelWriter implementations don't need to subclass
# ExcelWriter.
_engine: str
_supported_extensions: tuple[str, ...]
def __new__(
cls: type[ExcelWriter],
path: FilePath | WriteExcelBuffer | ExcelWriter,
engine: str | None = None,
date_format: str | None = None,
datetime_format: str | None = None,
mode: str = "w",
storage_options: StorageOptions = None,
if_sheet_exists: Literal["error", "new", "replace", "overlay"] | None = None,
engine_kwargs: dict | None = None,
**kwargs,
) -> ExcelWriter:
if kwargs:
if engine_kwargs is not None:
raise ValueError("Cannot use both engine_kwargs and **kwargs")
warnings.warn(
"Use of **kwargs is deprecated, use engine_kwargs instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
# only switch class if generic(ExcelWriter)
if cls is ExcelWriter:
if engine is None or (isinstance(engine, str) and engine == "auto"):
if isinstance(path, str):
ext = os.path.splitext(path)[-1][1:]
else:
ext = "xlsx"
try:
engine = config.get_option(f"io.excel.{ext}.writer", silent=True)
if engine == "auto":
engine = get_default_engine(ext, mode="writer")
except KeyError as err:
raise ValueError(f"No engine for filetype: '{ext}'") from err
if engine == "xlwt":
xls_config_engine = config.get_option(
"io.excel.xls.writer", silent=True
)
# Don't warn a 2nd time if user has changed the default engine for xls
if xls_config_engine != "xlwt":
warnings.warn(
"As the xlwt package is no longer maintained, the xlwt "
"engine will be removed in a future version of pandas. "
"This is the only engine in pandas that supports writing "
"in the xls format. Install openpyxl and write to an xlsx "
"file instead. You can set the option io.excel.xls.writer "
"to 'xlwt' to silence this warning. While this option is "
"deprecated and will also raise a warning, it can "
"be globally set and the warning suppressed.",
FutureWarning,
stacklevel=find_stack_level(),
)
# for mypy
assert engine is not None
cls = get_writer(engine)
return object.__new__(cls)
# declare external properties you can count on
_path = None
@property
def supported_extensions(self) -> tuple[str, ...]:
"""Extensions that writer engine supports."""
return self._supported_extensions
@property
def engine(self) -> str:
"""Name of engine."""
return self._engine
@property
@abc.abstractmethod
def sheets(self) -> dict[str, Any]:
"""Mapping of sheet names to sheet objects."""
pass
# mypy doesn't handle abstract setters prior to 0.981
# <https://github.com/python/mypy/issues/4165>
@property # type: ignore[misc]
@abc.abstractmethod
def book(self):
"""
Book instance. Class type will depend on the engine used.
This attribute can be used to access engine-specific features.
"""
pass
# mypy doesn't handle abstract setters prior to 0.981
# <https://github.com/python/mypy/issues/4165>
@book.setter # type: ignore[misc]
@abc.abstractmethod
def book(self, other) -> None:
"""
Set book instance. Class type will depend on the engine used.
"""
pass
def write_cells(
self,
cells,
sheet_name: str | None = None,
startrow: int = 0,
startcol: int = 0,
freeze_panes: tuple[int, int] | None = None,
) -> None:
"""
Write given formatted cells into Excel an excel sheet
.. deprecated:: 1.5.0
Parameters
----------
cells : generator
cell of formatted data to save to Excel sheet
sheet_name : str, default None
Name of Excel sheet, if None, then use self.cur_sheet
startrow : upper left cell row to dump data frame
startcol : upper left cell column to dump data frame
freeze_panes: int tuple of length 2
contains the bottom-most row and right-most column to freeze
"""
self._deprecate("write_cells")
return self._write_cells(cells, sheet_name, startrow, startcol, freeze_panes)
@abc.abstractmethod
def _write_cells(
self,
cells,
sheet_name: str | None = None,
startrow: int = 0,
startcol: int = 0,
freeze_panes: tuple[int, int] | None = None,
) -> None:
"""
Write given formatted cells into Excel an excel sheet
Parameters
----------
cells : generator
cell of formatted data to save to Excel sheet
sheet_name : str, default None
Name of Excel sheet, if None, then use self.cur_sheet
startrow : upper left cell row to dump data frame
startcol : upper left cell column to dump data frame
freeze_panes: int tuple of length 2
contains the bottom-most row and right-most column to freeze
"""
pass
def save(self) -> None:
"""
Save workbook to disk.
.. deprecated:: 1.5.0
"""
self._deprecate("save")
return self._save()
@abc.abstractmethod
def _save(self) -> None:
"""
Save workbook to disk.
"""
pass
def __init__(
self,
path: FilePath | WriteExcelBuffer | ExcelWriter,
engine: str | None = None,
date_format: str | None = None,
datetime_format: str | None = None,
mode: str = "w",
storage_options: StorageOptions = None,
if_sheet_exists: str | None = None,
engine_kwargs: dict[str, Any] | None = None,
**kwargs,
) -> None:
# validate that this engine can handle the extension
if isinstance(path, str):
ext = os.path.splitext(path)[-1]
self.check_extension(ext)
# use mode to open the file
if "b" not in mode:
mode += "b"
# use "a" for the user to append data to excel but internally use "r+" to let
# the excel backend first read the existing file and then write any data to it
mode = mode.replace("a", "r+")
# cast ExcelWriter to avoid adding 'if self.handles is not None'
self._handles = IOHandles(
cast(IO[bytes], path), compression={"compression": None}
)
if not isinstance(path, ExcelWriter):
self._handles = get_handle(
path, mode, storage_options=storage_options, is_text=False
)
self._cur_sheet = None
if date_format is None:
self._date_format = "YYYY-MM-DD"
else:
self._date_format = date_format
if datetime_format is None:
self._datetime_format = "YYYY-MM-DD HH:MM:SS"
else:
self._datetime_format = datetime_format
self._mode = mode
if if_sheet_exists not in (None, "error", "new", "replace", "overlay"):
raise ValueError(
f"'{if_sheet_exists}' is not valid for if_sheet_exists. "
"Valid options are 'error', 'new', 'replace' and 'overlay'."
)
if if_sheet_exists and "r+" not in mode:
raise ValueError("if_sheet_exists is only valid in append mode (mode='a')")
if if_sheet_exists is None:
if_sheet_exists = "error"
self._if_sheet_exists = if_sheet_exists
def _deprecate(self, attr: str):
"""
Deprecate attribute or method for ExcelWriter.
"""
warnings.warn(
f"{attr} is not part of the public API, usage can give unexpected "
"results and will be removed in a future version",
FutureWarning,
stacklevel=find_stack_level(),
)
def _deprecate_set_book(self) -> None:
"""
Deprecate setting the book attribute - GH#48780.
"""
warnings.warn(
"Setting the `book` attribute is not part of the public API, "
"usage can give unexpected or corrupted results and will be "
"removed in a future version",
FutureWarning,
stacklevel=find_stack_level(),
)
@property
def date_format(self) -> str:
"""
Format string for dates written into Excel files (e.g. ‘YYYY-MM-DD’).
"""
return self._date_format
@property
def datetime_format(self) -> str:
"""
Format string for dates written into Excel files (e.g. ‘YYYY-MM-DD’).
"""
return self._datetime_format
@property
def if_sheet_exists(self) -> str:
"""
How to behave when writing to a sheet that already exists in append mode.
"""
return self._if_sheet_exists
@property
def cur_sheet(self):
"""
Current sheet for writing.
.. deprecated:: 1.5.0
"""
self._deprecate("cur_sheet")
return self._cur_sheet
@property
def handles(self) -> IOHandles[bytes]:
"""
Handles to Excel sheets.
.. deprecated:: 1.5.0
"""
self._deprecate("handles")
return self._handles
@property
def path(self):
"""
Path to Excel file.
.. deprecated:: 1.5.0
"""
self._deprecate("path")
return self._path
def __fspath__(self) -> str:
return getattr(self._handles.handle, "name", "")
def _get_sheet_name(self, sheet_name: str | None) -> str:
if sheet_name is None:
sheet_name = self._cur_sheet
if sheet_name is None: # pragma: no cover
raise ValueError("Must pass explicit sheet_name or set _cur_sheet property")
return sheet_name
def _value_with_fmt(self, val) -> tuple[object, str | None]:
"""
Convert numpy types to Python types for the Excel writers.
Parameters
----------
val : object
Value to be written into cells
Returns
-------
Tuple with the first element being the converted value and the second
being an optional format
"""
fmt = None
if is_integer(val):
val = int(val)
elif is_float(val):
val = float(val)
elif is_bool(val):
val = bool(val)
elif isinstance(val, datetime.datetime):
fmt = self._datetime_format
elif isinstance(val, datetime.date):
fmt = self._date_format
elif isinstance(val, datetime.timedelta):
val = val.total_seconds() / 86400
fmt = "0"
else:
val = str(val)
return val, fmt
@classmethod
def check_extension(cls, ext: str) -> Literal[True]:
"""
checks that path's extension against the Writer's supported
extensions. If it isn't supported, raises UnsupportedFiletypeError.
"""
if ext.startswith("."):
ext = ext[1:]
if not any(ext in extension for extension in cls._supported_extensions):
raise ValueError(f"Invalid extension for engine '{cls.engine}': '{ext}'")
else:
return True
# Allow use as a contextmanager
def __enter__(self) -> ExcelWriter:
return self
def __exit__(self, exc_type, exc_value, traceback) -> None:
self.close()
def close(self) -> None:
"""synonym for save, to make it more file-like"""
self._save()
self._handles.close()