1from __future__ import annotations
2
3from collections.abc import (
4 Callable,
5 Hashable,
6 Iterable,
7 Mapping,
8 Sequence,
9)
10import datetime
11from decimal import Decimal
12from functools import partial
13import os
14from typing import (
15 IO,
16 TYPE_CHECKING,
17 Any,
18 Generic,
19 Literal,
20 Self,
21 TypeVar,
22 Union,
23 cast,
24 overload,
25)
26import warnings
27import zipfile
28
29from pandas._config import config
30
31from pandas._libs import lib
32from pandas.compat._optional import (
33 get_version,
34 import_optional_dependency,
35)
36from pandas.errors import EmptyDataError
37from pandas.util._decorators import (
38 set_module,
39)
40from pandas.util._exceptions import find_stack_level
41from pandas.util._validators import check_dtype_backend
42
43from pandas.core.dtypes.common import (
44 is_bool,
45 is_decimal,
46 is_file_like,
47 is_float,
48 is_integer,
49 is_list_like,
50)
51
52from pandas.core.frame import DataFrame
53from pandas.util.version import Version
54
55from pandas.io.common import (
56 IOHandles,
57 get_handle,
58 stringify_path,
59 validate_header_arg,
60)
61from pandas.io.excel._util import (
62 fill_mi_header,
63 get_default_engine,
64 get_writer,
65 maybe_convert_usecols,
66 pop_header_name,
67)
68from pandas.io.parsers import TextParser
69from pandas.io.parsers.readers import validate_integer
70
71if TYPE_CHECKING:
72 from types import TracebackType
73
74 from pandas._typing import (
75 DtypeArg,
76 DtypeBackend,
77 ExcelWriterIfSheetExists,
78 FilePath,
79 HashableT,
80 IntStrT,
81 ReadBuffer,
82 SequenceNotStr,
83 StorageOptions,
84 WriteExcelBuffer,
85 )
86
87
88@overload
89def read_excel(
90 io,
91 # sheet name is str or int -> DataFrame
92 sheet_name: str | int = ...,
93 *,
94 header: int | Sequence[int] | None = ...,
95 names: SequenceNotStr[Hashable] | range | None = ...,
96 index_col: int | str | Sequence[int] | None = ...,
97 usecols: int
98 | str
99 | Sequence[int]
100 | Sequence[str]
101 | Callable[[HashableT], bool]
102 | None = ...,
103 dtype: DtypeArg | None = ...,
104 engine: Literal["xlrd", "openpyxl", "odf", "pyxlsb", "calamine"] | None = ...,
105 converters: dict[str, Callable] | dict[int, Callable] | None = ...,
106 true_values: Iterable[Hashable] | None = ...,
107 false_values: Iterable[Hashable] | None = ...,
108 skiprows: Sequence[int] | int | Callable[[int], object] | None = ...,
109 nrows: int | None = ...,
110 na_values=...,
111 keep_default_na: bool = ...,
112 na_filter: bool = ...,
113 verbose: bool = ...,
114 parse_dates: list | dict | bool = ...,
115 date_format: dict[Hashable, str] | str | None = ...,
116 thousands: str | None = ...,
117 decimal: str = ...,
118 comment: str | None = ...,
119 skipfooter: int = ...,
120 storage_options: StorageOptions = ...,
121 dtype_backend: DtypeBackend | lib.NoDefault = ...,
122) -> DataFrame: ...
123
124
125@overload
126def read_excel(
127 io,
128 # sheet name is list or None -> dict[IntStrT, DataFrame]
129 sheet_name: list[IntStrT] | None,
130 *,
131 header: int | Sequence[int] | None = ...,
132 names: SequenceNotStr[Hashable] | range | None = ...,
133 index_col: int | str | Sequence[int] | None = ...,
134 usecols: int
135 | str
136 | Sequence[int]
137 | Sequence[str]
138 | Callable[[HashableT], bool]
139 | None = ...,
140 dtype: DtypeArg | None = ...,
141 engine: Literal["xlrd", "openpyxl", "odf", "pyxlsb", "calamine"] | None = ...,
142 converters: dict[str, Callable] | dict[int, Callable] | None = ...,
143 true_values: Iterable[Hashable] | None = ...,
144 false_values: Iterable[Hashable] | None = ...,
145 skiprows: Sequence[int] | int | Callable[[int], object] | None = ...,
146 nrows: int | None = ...,
147 na_values=...,
148 keep_default_na: bool = ...,
149 na_filter: bool = ...,
150 verbose: bool = ...,
151 parse_dates: list | dict | bool = ...,
152 date_format: dict[Hashable, str] | str | None = ...,
153 thousands: str | None = ...,
154 decimal: str = ...,
155 comment: str | None = ...,
156 skipfooter: int = ...,
157 storage_options: StorageOptions = ...,
158 dtype_backend: DtypeBackend | lib.NoDefault = ...,
159) -> dict[IntStrT, DataFrame]: ...
160
161
162@set_module("pandas")
163def read_excel(
164 io,
165 sheet_name: str | int | list[IntStrT] | None = 0,
166 *,
167 header: int | Sequence[int] | None = 0,
168 names: SequenceNotStr[Hashable] | range | None = None,
169 index_col: int | str | Sequence[int] | None = None,
170 usecols: int
171 | str
172 | Sequence[int]
173 | Sequence[str]
174 | Callable[[HashableT], bool]
175 | None = None,
176 dtype: DtypeArg | None = None,
177 engine: Literal["xlrd", "openpyxl", "odf", "pyxlsb", "calamine"] | None = None,
178 converters: dict[str, Callable] | dict[int, Callable] | None = None,
179 true_values: Iterable[Hashable] | None = None,
180 false_values: Iterable[Hashable] | None = None,
181 skiprows: Sequence[int] | int | Callable[[int], object] | None = None,
182 nrows: int | None = None,
183 na_values=None,
184 keep_default_na: bool = True,
185 na_filter: bool = True,
186 verbose: bool = False,
187 parse_dates: list | dict | bool = False,
188 date_format: dict[Hashable, str] | str | None = None,
189 thousands: str | None = None,
190 decimal: str = ".",
191 comment: str | None = None,
192 skipfooter: int = 0,
193 storage_options: StorageOptions | None = None,
194 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default,
195 engine_kwargs: dict | None = None,
196) -> DataFrame | dict[IntStrT, DataFrame]:
197 """
198 Read an Excel file into a ``DataFrame``.
199
200 Supports `xls`, `xlsx`, `xlsm`, `xlsb`, `odf`, `ods` and `odt` file extensions
201 read from a local filesystem or URL. Supports an option to read
202 a single sheet or a list of sheets.
203
204 Parameters
205 ----------
206 io : str, ExcelFile, xlrd.Book, path object, or file-like object
207 Any valid string path is acceptable. The string could be a URL. Valid
208 URL schemes include http, ftp, s3, and file. For file URLs, a host is
209 expected. A local file could be: ``file://localhost/path/to/table.xlsx``.
210
211 If you want to pass in a path object, pandas accepts any ``os.PathLike``.
212
213 By file-like object, we refer to objects with a ``read()`` method,
214 such as a file handle (e.g. via builtin ``open`` function)
215 or ``StringIO``.
216
217 sheet_name : str, int, list, or None, default 0
218 Strings are used for sheet names. Integers are used in zero-indexed
219 sheet positions (chart sheets do not count as a sheet position).
220 Lists of strings/integers are used to request multiple sheets.
221 When ``None``, will return a dictionary containing DataFrames for each sheet.
222
223 Available cases:
224
225 * Defaults to ``0``: 1st sheet as a `DataFrame`
226 * ``1``: 2nd sheet as a `DataFrame`
227 * ``"Sheet1"``: Load sheet with name "Sheet1"
228 * ``[0, 1, "Sheet5"]``: Load first, second and sheet named "Sheet5"
229 as a dict of `DataFrame`
230 * ``None``: Returns a dictionary containing DataFrames for each sheet.
231
232 header : int, list of int, default 0
233 Row (0-indexed) to use for the column labels of the parsed
234 DataFrame. If a list of integers is passed those row positions will
235 be combined into a ``MultiIndex``. Use None if there is no header.
236 names : array-like, default None
237 List of column names to use. If file contains no header row,
238 then you should explicitly pass header=None.
239 index_col : int, str, list of int, default None
240 Column (0-indexed) to use as the row labels of the DataFrame.
241 Pass None if there is no such column. If a list is passed,
242 those columns will be combined into a ``MultiIndex``. If a
243 subset of data is selected with ``usecols``, index_col
244 is based on the subset.
245
246 Missing values will be forward filled to allow roundtripping with
247 ``to_excel`` for ``merged_cells=True``. To avoid forward filling the
248 missing values use ``set_index`` after reading the data instead of
249 ``index_col``.
250 usecols : str, list-like, or callable, default None
251 * If None, then parse all columns.
252 * If str, then indicates comma separated list of Excel column letters
253 and column ranges (e.g. "A:E" or "A,C,E:F"). Ranges are inclusive of
254 both sides.
255 * If list of int, then indicates list of column numbers to be parsed
256 (0-indexed).
257 * If list of string, then indicates list of column names to be parsed.
258 * If callable, then evaluate each column name against it and parse the
259 column if the callable returns ``True``.
260
261 Returns a subset of the columns according to behavior above.
262 dtype : Type name or dict of column -> type, default None
263 Data type for data or columns. E.g. {'a': np.float64, 'b': np.int32}
264 Use ``object`` to preserve data as stored in Excel and not interpret dtype,
265 which will necessarily result in ``object`` dtype.
266 If converters are specified, they will be applied INSTEAD
267 of dtype conversion.
268 If you use ``None``, it will infer the dtype of each column based on the data.
269 engine : {'openpyxl', 'calamine', 'odf', 'pyxlsb', 'xlrd'}, default None
270 If io is not a buffer or path, this must be set to identify io.
271 Engine compatibility :
272
273 - ``openpyxl`` supports newer Excel file formats.
274 - ``calamine`` supports Excel (.xls, .xlsx, .xlsm, .xlsb)
275 and OpenDocument (.ods) file formats.
276 - ``odf`` supports OpenDocument file formats (.odf, .ods, .odt).
277 - ``pyxlsb`` supports Binary Excel files.
278 - ``xlrd`` supports old-style Excel files (.xls).
279
280 When ``engine=None``, the following logic will be used to determine the engine:
281
282 - If ``path_or_buffer`` is an OpenDocument format (.odf, .ods, .odt),
283 then `odf <https://pypi.org/project/odfpy/>`_ will be used.
284 - Otherwise if ``path_or_buffer`` is an xls format, ``xlrd`` will be used.
285 - Otherwise if ``path_or_buffer`` is in xlsb format, ``pyxlsb`` will be used.
286 - Otherwise ``openpyxl`` will be used.
287
288 converters : dict, default None
289 Dict of functions for converting values in certain columns. Keys can
290 either be integers or column labels, values are functions that take one
291 input argument, the Excel cell content, and return the transformed
292 content.
293 true_values : list, default None
294 Values to consider as True.
295 false_values : list, default None
296 Values to consider as False.
297 skiprows : list-like, int, or callable, optional
298 Line numbers to skip (0-indexed) or number of lines to skip (int) at the
299 start of the file. If callable, the callable function will be evaluated
300 against the row indices, returning True if the row should be skipped and
301 False otherwise. An example of a valid callable argument would be ``lambda
302 x: x in [0, 2]``.
303 nrows : int, default None
304 Number of rows to parse. Does not include header rows.
305 na_values : scalar, str, list-like, or dict, default None
306 Additional strings to recognize as NA/NaN. If dict passed, specific
307 per-column NA values. By default the following values are interpreted
308 as NaN: '', '#N/A', '#N/A N/A', '#NA', '-1.#IND', '-1.#QNAN', '-NaN', '-nan',
309 '1.#IND', '1.#QNAN', '<NA>', 'N/A', 'NA', 'NULL', 'NaN', 'None',
310 'n/a', 'nan', 'null'.
311 keep_default_na : bool, default True
312 Whether or not to include the default NaN values when parsing the data.
313 Depending on whether ``na_values`` is passed in, the behavior is as follows:
314
315 * If ``keep_default_na`` is True, and ``na_values`` are specified,
316 ``na_values`` is appended to the default NaN values used for parsing.
317 * If ``keep_default_na`` is True, and ``na_values`` are not specified, only
318 the default NaN values are used for parsing.
319 * If ``keep_default_na`` is False, and ``na_values`` are specified, only
320 the NaN values specified ``na_values`` are used for parsing.
321 * If ``keep_default_na`` is False, and ``na_values`` are not specified, no
322 strings will be parsed as NaN.
323
324 Note that if `na_filter` is passed in as False, the ``keep_default_na`` and
325 ``na_values`` parameters will be ignored.
326 na_filter : bool, default True
327 Detect missing value markers (empty strings and the value of na_values). In
328 data without any NAs, passing ``na_filter=False`` can improve the
329 performance of reading a large file.
330 verbose : bool, default False
331 Indicate number of NA values placed in non-numeric columns.
332 parse_dates : bool, list-like, or dict, default False
333 The behavior is as follows:
334
335 * ``bool``. If True -> try parsing the index.
336 * ``list`` of int or names. e.g. If [1, 2, 3] -> try parsing columns 1, 2, 3
337 each as a separate date column.
338 * ``list`` of lists. e.g. If [[1, 3]] -> combine columns 1 and 3 and parse as
339 a single date column.
340 * ``dict``, e.g. {'foo' : [1, 3]} -> parse columns 1, 3 as date and call
341 result 'foo'
342
343 If a column or index contains an unparsable date, the entire column or
344 index will be returned unaltered as an object data type. If you don`t want to
345 parse some cells as date just change their type in Excel to "Text".
346 For non-standard datetime parsing, use ``pd.to_datetime`` after
347 ``pd.read_excel``.
348
349 Note: A fast-path exists for iso8601-formatted dates.
350 date_format : str or dict of column -> format, default ``None``
351 If used in conjunction with ``parse_dates``, will parse dates according to this
352 format. For anything more complex,
353 please read in as ``object`` and then apply :func:`to_datetime` as-needed.
354
355 .. versionadded:: 2.0.0
356
357 thousands : str, default None
358 Thousands separator for parsing string columns to numeric. Note that
359 this parameter is only necessary for columns stored as TEXT in Excel,
360 any numeric columns will automatically be parsed, regardless of display
361 format.
362 decimal : str, default '.'
363 Character to recognize as decimal point for parsing string columns to numeric.
364 Note that this parameter is only necessary for columns stored as TEXT in Excel,
365 any numeric columns will automatically be parsed, regardless of display
366 format.(e.g. use ',' for European data).
367 comment : str, default None
368 Comments out remainder of line. Pass a character or characters to this
369 argument to indicate comments in the input file. Any data between the
370 comment string and the end of the current line is ignored.
371 skipfooter : int, default 0
372 Rows at the end to skip (0-indexed).
373 storage_options : dict, optional
374 Extra options that make sense for a particular storage connection, e.g.
375 host, port, username, password, etc. For HTTP(S) URLs the key-value pairs
376 are forwarded to ``urllib.request.Request`` as header options. For other
377 URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are
378 forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more
379 details, and for more examples on storage options refer `here
380 <https://pandas.pydata.org/docs/user_guide/io.html?
381 highlight=storage_options#reading-writing-remote-files>`_.
382
383 dtype_backend : {'numpy_nullable', 'pyarrow'}
384 Back-end data type applied to the resultant :class:`DataFrame`
385 (still experimental). If not specified, the default behavior
386 is to not use nullable data types. If specified, the behavior
387 is as follows:
388
389 * ``"numpy_nullable"``: returns nullable-dtype-backed :class:`DataFrame`
390 * ``"pyarrow"``: returns pyarrow-backed nullable
391
392 :class:`ArrowDtype` :class:`DataFrame`
393
394 .. versionadded:: 2.0
395
396 engine_kwargs : dict, optional
397 Arbitrary keyword arguments passed to excel engine.
398
399 Returns
400 -------
401 DataFrame or dict of DataFrames
402 DataFrame from the passed in Excel file. See notes in sheet_name
403 argument for more information on when a dict of DataFrames is returned.
404
405 See Also
406 --------
407 DataFrame.to_excel : Write DataFrame to an Excel file.
408 DataFrame.to_csv : Write DataFrame to a comma-separated values (csv) file.
409 read_csv : Read a comma-separated values (csv) file into DataFrame.
410 read_fwf : Read a table of fixed-width formatted lines into DataFrame.
411
412 Notes
413 -----
414 For specific information on the methods used for each Excel engine, refer to the
415 pandas
416 :ref:`user guide <io.excel_reader>`
417
418 Examples
419 --------
420 The file can be read using the file name as string or an open file object:
421
422 >>> pd.read_excel("tmp.xlsx", index_col=0) # doctest: +SKIP
423 Name Value
424 0 string1 1
425 1 string2 2
426 2 #Comment 3
427
428 >>> pd.read_excel(open("tmp.xlsx", "rb"), sheet_name="Sheet3") # doctest: +SKIP
429 Unnamed: 0 Name Value
430 0 0 string1 1
431 1 1 string2 2
432 2 2 #Comment 3
433
434 Index and header can be specified via the `index_col` and `header` arguments
435
436 >>> pd.read_excel("tmp.xlsx", index_col=None, header=None) # doctest: +SKIP
437 0 1 2
438 0 NaN Name Value
439 1 0.0 string1 1
440 2 1.0 string2 2
441 3 2.0 #Comment 3
442
443 Column types are inferred but can be explicitly specified
444
445 >>> pd.read_excel(
446 ... "tmp.xlsx", index_col=0, dtype={"Name": str, "Value": float}
447 ... ) # doctest: +SKIP
448 Name Value
449 0 string1 1.0
450 1 string2 2.0
451 2 #Comment 3.0
452
453 True, False, and NA values, and thousands separators have defaults,
454 but can be explicitly specified, too. Supply the values you would like
455 as strings or lists of strings!
456
457 >>> pd.read_excel(
458 ... "tmp.xlsx", index_col=0, na_values=["string1", "string2"]
459 ... ) # doctest: +SKIP
460 Name Value
461 0 NaN 1
462 1 NaN 2
463 2 #Comment 3
464
465 Comment lines in the excel input file can be skipped using the
466 ``comment`` kwarg.
467
468 >>> pd.read_excel("tmp.xlsx", index_col=0, comment="#") # doctest: +SKIP
469 Name Value
470 0 string1 1.0
471 1 string2 2.0
472 2 None NaN
473 """
474 check_dtype_backend(dtype_backend)
475 should_close = False
476 if engine_kwargs is None:
477 engine_kwargs = {}
478
479 if not isinstance(io, ExcelFile):
480 should_close = True
481 io = ExcelFile(
482 io,
483 storage_options=storage_options,
484 engine=engine,
485 engine_kwargs=engine_kwargs,
486 )
487 elif engine and engine != io.engine:
488 raise ValueError(
489 "Engine should not be specified when passing "
490 "an ExcelFile - ExcelFile already has the engine set"
491 )
492
493 try:
494 data = io.parse(
495 sheet_name=sheet_name,
496 header=header,
497 names=names,
498 index_col=index_col,
499 usecols=usecols,
500 dtype=dtype,
501 converters=converters,
502 true_values=true_values,
503 false_values=false_values,
504 skiprows=skiprows,
505 nrows=nrows,
506 na_values=na_values,
507 keep_default_na=keep_default_na,
508 na_filter=na_filter,
509 verbose=verbose,
510 parse_dates=parse_dates,
511 date_format=date_format,
512 thousands=thousands,
513 decimal=decimal,
514 comment=comment,
515 skipfooter=skipfooter,
516 dtype_backend=dtype_backend,
517 )
518 finally:
519 # make sure to close opened file handles
520 if should_close:
521 io.close()
522 return data
523
524
525_WorkbookT = TypeVar("_WorkbookT")
526
527
528class BaseExcelReader(Generic[_WorkbookT]):
529 book: _WorkbookT
530
531 def __init__(
532 self,
533 filepath_or_buffer,
534 storage_options: StorageOptions | None = None,
535 engine_kwargs: dict | None = None,
536 ) -> None:
537 if engine_kwargs is None:
538 engine_kwargs = {}
539
540 self.handles = IOHandles(
541 handle=filepath_or_buffer, compression={"method": None}
542 )
543 if not isinstance(filepath_or_buffer, (ExcelFile, self._workbook_class)):
544 self.handles = get_handle(
545 filepath_or_buffer, "rb", storage_options=storage_options, is_text=False
546 )
547
548 if isinstance(self.handles.handle, self._workbook_class):
549 self.book = self.handles.handle
550 elif hasattr(self.handles.handle, "read"):
551 # N.B. xlrd.Book has a read attribute too
552 self.handles.handle.seek(0)
553 try:
554 self.book = self.load_workbook(self.handles.handle, engine_kwargs)
555 except Exception:
556 self.close()
557 raise
558 else:
559 raise ValueError(
560 "Must explicitly set engine if not passing in buffer or path for io."
561 )
562
563 @property
564 def _workbook_class(self) -> type[_WorkbookT]:
565 raise NotImplementedError
566
567 def load_workbook(self, filepath_or_buffer, engine_kwargs) -> _WorkbookT:
568 raise NotImplementedError
569
570 def close(self) -> None:
571 if hasattr(self, "book"):
572 if hasattr(self.book, "close"):
573 # pyxlsb: opens a TemporaryFile
574 # openpyxl: https://stackoverflow.com/questions/31416842/
575 # openpyxl-does-not-close-excel-workbook-in-read-only-mode
576 self.book.close()
577 elif hasattr(self.book, "release_resources"):
578 # xlrd
579 # https://github.com/python-excel/xlrd/blob/2.0.1/xlrd/book.py#L548
580 self.book.release_resources()
581 self.handles.close()
582
583 @property
584 def sheet_names(self) -> list[str]:
585 raise NotImplementedError
586
587 def get_sheet_by_name(self, name: str):
588 raise NotImplementedError
589
590 def get_sheet_by_index(self, index: int):
591 raise NotImplementedError
592
593 def get_sheet_data(self, sheet, rows: int | None = None):
594 raise NotImplementedError
595
596 def raise_if_bad_sheet_by_index(self, index: int) -> None:
597 n_sheets = len(self.sheet_names)
598 if index >= n_sheets:
599 raise ValueError(
600 f"Worksheet index {index} is invalid, {n_sheets} worksheets found"
601 )
602
603 def raise_if_bad_sheet_by_name(self, name: str) -> None:
604 if name not in self.sheet_names:
605 raise ValueError(f"Worksheet named '{name}' not found")
606
607 def _check_skiprows_func(
608 self,
609 skiprows: Callable,
610 rows_to_use: int,
611 ) -> int:
612 """
613 Determine how many file rows are required to obtain `nrows` data
614 rows when `skiprows` is a function.
615
616 Parameters
617 ----------
618 skiprows : function
619 The function passed to read_excel by the user.
620 rows_to_use : int
621 The number of rows that will be needed for the header and
622 the data.
623
624 Returns
625 -------
626 int
627 """
628 i = 0
629 rows_used_so_far = 0
630 while rows_used_so_far < rows_to_use:
631 if not skiprows(i):
632 rows_used_so_far += 1
633 i += 1
634 return i
635
636 def _calc_rows(
637 self,
638 header: int | Sequence[int] | None,
639 index_col: int | Sequence[int] | None,
640 skiprows: Sequence[int] | int | Callable[[int], object] | None,
641 nrows: int | None,
642 ) -> int | None:
643 """
644 If nrows specified, find the number of rows needed from the
645 file, otherwise return None.
646
647
648 Parameters
649 ----------
650 header : int, list of int, or None
651 See read_excel docstring.
652 index_col : int, str, list of int, or None
653 See read_excel docstring.
654 skiprows : list-like, int, callable, or None
655 See read_excel docstring.
656 nrows : int or None
657 See read_excel docstring.
658
659 Returns
660 -------
661 int or None
662 """
663 if nrows is None:
664 return None
665 if header is None:
666 header_rows = 1
667 elif is_integer(header):
668 header = cast(int, header)
669 header_rows = 1 + header
670 else:
671 header = cast(Sequence, header)
672 header_rows = 1 + header[-1]
673 # If there is a MultiIndex header and an index then there is also
674 # a row containing just the index name(s)
675 if is_list_like(header) and index_col is not None:
676 header = cast(Sequence, header)
677 if len(header) > 1:
678 header_rows += 1
679 if skiprows is None:
680 return header_rows + nrows
681 if is_integer(skiprows):
682 skiprows = cast(int, skiprows)
683 return header_rows + nrows + skiprows
684 if is_list_like(skiprows):
685
686 def f(skiprows: Sequence, x: int) -> bool:
687 return x in skiprows
688
689 skiprows = cast(Sequence, skiprows)
690 return self._check_skiprows_func(partial(f, skiprows), header_rows + nrows)
691 if callable(skiprows):
692 return self._check_skiprows_func(
693 skiprows,
694 header_rows + nrows,
695 )
696 # else unexpected skiprows type: read_excel will not optimize
697 # the number of rows read from file
698 return None
699
700 def parse(
701 self,
702 sheet_name: str | int | list[int] | list[str] | None = 0,
703 header: int | Sequence[int] | None = 0,
704 names: SequenceNotStr[Hashable] | range | None = None,
705 index_col: int | Sequence[int] | None = None,
706 usecols=None,
707 dtype: DtypeArg | None = None,
708 true_values: Iterable[Hashable] | None = None,
709 false_values: Iterable[Hashable] | None = None,
710 skiprows: Sequence[int] | int | Callable[[int], object] | None = None,
711 nrows: int | None = None,
712 na_values=None,
713 verbose: bool = False,
714 parse_dates: list | dict | bool = False,
715 date_format: dict[Hashable, str] | str | None = None,
716 thousands: str | None = None,
717 decimal: str = ".",
718 comment: str | None = None,
719 skipfooter: int = 0,
720 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default,
721 **kwds,
722 ):
723 validate_header_arg(header)
724 validate_integer("nrows", nrows)
725
726 ret_dict = False
727
728 # Keep sheetname to maintain backwards compatibility.
729 sheets: list[int] | list[str]
730 if isinstance(sheet_name, list):
731 sheets = sheet_name
732 ret_dict = True
733 elif sheet_name is None:
734 sheets = self.sheet_names
735 ret_dict = True
736 elif isinstance(sheet_name, str):
737 sheets = [sheet_name]
738 else:
739 sheets = [sheet_name]
740
741 # handle same-type duplicates.
742 sheets = cast(Union[list[int], list[str]], list(dict.fromkeys(sheets).keys()))
743
744 output = {}
745
746 last_sheetname = None
747 for asheetname in sheets:
748 last_sheetname = asheetname
749 if verbose:
750 print(f"Reading sheet {asheetname}")
751
752 if isinstance(asheetname, str):
753 sheet = self.get_sheet_by_name(asheetname)
754 else: # assume an integer if not a string
755 sheet = self.get_sheet_by_index(asheetname)
756
757 file_rows_needed = self._calc_rows(header, index_col, skiprows, nrows)
758 data = self.get_sheet_data(sheet, file_rows_needed)
759 if hasattr(sheet, "close"):
760 # pyxlsb opens two TemporaryFiles
761 sheet.close()
762 usecols = maybe_convert_usecols(usecols)
763
764 if not data:
765 output[asheetname] = DataFrame()
766 continue
767
768 output = self._parse_sheet(
769 data=data,
770 output=output,
771 asheetname=asheetname,
772 header=header,
773 names=names,
774 index_col=index_col,
775 usecols=usecols,
776 dtype=dtype,
777 skiprows=skiprows,
778 nrows=nrows,
779 true_values=true_values,
780 false_values=false_values,
781 na_values=na_values,
782 parse_dates=parse_dates,
783 date_format=date_format,
784 thousands=thousands,
785 decimal=decimal,
786 comment=comment,
787 skipfooter=skipfooter,
788 dtype_backend=dtype_backend,
789 **kwds,
790 )
791
792 if last_sheetname is None:
793 raise ValueError("Sheet name is an empty list")
794
795 if ret_dict:
796 return output
797 else:
798 return output[last_sheetname]
799
800 def _parse_sheet(
801 self,
802 data: list,
803 output: dict,
804 asheetname: str | int | None = None,
805 header: int | Sequence[int] | None = 0,
806 names: SequenceNotStr[Hashable] | range | None = None,
807 index_col: int | Sequence[int] | None = None,
808 usecols=None,
809 dtype: DtypeArg | None = None,
810 skiprows: Sequence[int] | int | Callable[[int], object] | None = None,
811 nrows: int | None = None,
812 true_values: Iterable[Hashable] | None = None,
813 false_values: Iterable[Hashable] | None = None,
814 na_values=None,
815 parse_dates: list | dict | bool = False,
816 date_format: dict[Hashable, str] | str | None = None,
817 thousands: str | None = None,
818 decimal: str = ".",
819 comment: str | None = None,
820 skipfooter: int = 0,
821 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default,
822 **kwds,
823 ):
824 is_list_header = False
825 is_len_one_list_header = False
826 if is_list_like(header):
827 assert isinstance(header, Sequence)
828 is_list_header = True
829 if len(header) == 1:
830 is_len_one_list_header = True
831
832 if is_len_one_list_header:
833 header = cast(Sequence[int], header)[0]
834
835 # forward fill and pull out names for MultiIndex column
836 header_names = None
837 if header is not None and is_list_like(header):
838 assert isinstance(header, Sequence)
839
840 header_names = []
841 control_row = [True] * len(data[0])
842
843 for row in header:
844 if is_integer(skiprows):
845 assert isinstance(skiprows, int)
846 row += skiprows
847
848 if row > len(data) - 1:
849 raise ValueError(
850 f"header index {row} exceeds maximum index "
851 f"{len(data) - 1} of data.",
852 )
853
854 data[row], control_row = fill_mi_header(data[row], control_row)
855
856 if index_col is not None:
857 header_name, _ = pop_header_name(data[row], index_col)
858 header_names.append(header_name)
859
860 # If there is a MultiIndex header and an index then there is also
861 # a row containing just the index name(s)
862 has_index_names = False
863 if is_list_header and not is_len_one_list_header and index_col is not None:
864 index_col_set: set[int]
865 if isinstance(index_col, int):
866 index_col_set = {index_col}
867 else:
868 assert isinstance(index_col, Sequence)
869 index_col_set = set(index_col)
870
871 # We have to handle mi without names. If any of the entries in the data
872 # columns are not empty, this is a regular row
873 assert isinstance(header, Sequence)
874 if len(header) < len(data):
875 potential_index_names = data[len(header)]
876 has_index_names = all(
877 x == "" or x is None
878 for i, x in enumerate(potential_index_names)
879 if not control_row[i] and i not in index_col_set
880 )
881
882 if is_list_like(index_col):
883 # Forward fill values for MultiIndex index.
884 if header is None:
885 offset = 0
886 elif isinstance(header, int):
887 offset = 1 + header
888 else:
889 offset = 1 + max(header)
890
891 # GH34673: if MultiIndex names present and not defined in the header,
892 # offset needs to be incremented so that forward filling starts
893 # from the first MI value instead of the name
894 if has_index_names:
895 offset += 1
896
897 # Check if we have an empty dataset
898 # before trying to collect data.
899 if offset < len(data):
900 assert isinstance(index_col, Sequence)
901
902 for col in index_col:
903 last = data[offset][col]
904
905 for row in range(offset + 1, len(data)):
906 if data[row][col] == "" or data[row][col] is None:
907 data[row][col] = last
908 else:
909 last = data[row][col]
910
911 # GH 12292 : error when read one empty column from excel file
912 try:
913 parser = TextParser(
914 data,
915 names=names,
916 header=header,
917 index_col=index_col,
918 has_index_names=has_index_names,
919 dtype=dtype,
920 true_values=true_values,
921 false_values=false_values,
922 skiprows=skiprows,
923 nrows=nrows,
924 na_values=na_values,
925 skip_blank_lines=False, # GH 39808
926 parse_dates=parse_dates,
927 date_format=date_format,
928 thousands=thousands,
929 decimal=decimal,
930 comment=comment,
931 skipfooter=skipfooter,
932 usecols=usecols,
933 dtype_backend=dtype_backend,
934 **kwds,
935 )
936
937 output[asheetname] = parser.read(nrows=nrows)
938
939 if header_names:
940 output[asheetname].columns = output[asheetname].columns.set_names(
941 header_names
942 )
943
944 except EmptyDataError:
945 # No Data, return an empty DataFrame
946 output[asheetname] = DataFrame()
947
948 except Exception as err:
949 err.args = (f"{err.args[0]} (sheet: {asheetname})", *err.args[1:])
950 raise err
951
952 return output
953
954
955@set_module("pandas")
956class ExcelWriter(Generic[_WorkbookT]):
957 """
958 Class for writing DataFrame objects into excel sheets.
959
960 Default is to use:
961
962 * `xlsxwriter <https://pypi.org/project/XlsxWriter/>`__ for xlsx files if xlsxwriter
963 is installed otherwise `openpyxl <https://pypi.org/project/openpyxl/>`__
964 * `odf <https://pypi.org/project/odfpy/>`__ for ods files
965
966 See :meth:`DataFrame.to_excel` for typical usage.
967
968 The writer should be used as a context manager. Otherwise, call `close()` to save
969 and close any opened file handles.
970
971 Parameters
972 ----------
973 path : str or typing.BinaryIO
974 Path to xls or xlsx or ods file.
975 engine : str (optional)
976 Engine to use for writing. If None, defaults to
977 ``io.excel.<extension>.writer``. NOTE: can only be passed as a keyword
978 argument.
979 date_format : str, default None
980 Format string for dates written into Excel files (e.g. 'YYYY-MM-DD').
981 datetime_format : str, default None
982 Format string for datetime objects written into Excel files.
983 (e.g. 'YYYY-MM-DD HH:MM:SS').
984 mode : {'w', 'a'}, default 'w'
985 File mode to use (write or append). Append does not work with fsspec URLs.
986 storage_options : dict, optional
987 Extra options that make sense for a particular storage connection, e.g.
988 host, port, username, password, etc. For HTTP(S) URLs the key-value pairs
989 are forwarded to ``urllib.request.Request`` as header options. For other
990 URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are
991 forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more
992 details, and for more examples on storage options refer `here
993 <https://pandas.pydata.org/docs/user_guide/io.html?
994 highlight=storage_options#reading-writing-remote-files>`_.
995
996 if_sheet_exists : {'error', 'new', 'replace', 'overlay'}, default 'error'
997 How to behave when trying to write to a sheet that already
998 exists (append mode only).
999
1000 * error: raise a ValueError.
1001 * new: Create a new sheet, with a name determined by the engine.
1002 * replace: Delete the contents of the sheet before writing to it.
1003 * overlay: Write contents to the existing sheet without first removing,
1004 but possibly over top of, the existing contents.
1005
1006 engine_kwargs : dict, optional
1007 Keyword arguments to be passed into the engine. These will be passed to
1008 the following functions of the respective engines:
1009
1010 * xlsxwriter: ``xlsxwriter.Workbook(file, **engine_kwargs)``
1011 * openpyxl (write mode): ``openpyxl.Workbook(**engine_kwargs)``
1012 * openpyxl (append mode): ``openpyxl.load_workbook(file, **engine_kwargs)``
1013 * odf: ``odf.opendocument.OpenDocumentSpreadsheet(**engine_kwargs)``
1014
1015 See Also
1016 --------
1017 read_excel : Read an Excel sheet values (xlsx) file into DataFrame.
1018 read_csv : Read a comma-separated values (csv) file into DataFrame.
1019 read_fwf : Read a table of fixed-width formatted lines into DataFrame.
1020
1021 Notes
1022 -----
1023 For compatibility with CSV writers, ExcelWriter serializes lists
1024 and dicts to strings before writing.
1025
1026 Examples
1027 --------
1028 Default usage:
1029
1030 >>> df = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) # doctest: +SKIP
1031 >>> with pd.ExcelWriter("path_to_file.xlsx") as writer:
1032 ... df.to_excel(writer) # doctest: +SKIP
1033
1034 To write to separate sheets in a single file:
1035
1036 >>> df1 = pd.DataFrame([["AAA", "BBB"]], columns=["Spam", "Egg"]) # doctest: +SKIP
1037 >>> df2 = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) # doctest: +SKIP
1038 >>> with pd.ExcelWriter("path_to_file.xlsx") as writer:
1039 ... df1.to_excel(writer, sheet_name="Sheet1") # doctest: +SKIP
1040 ... df2.to_excel(writer, sheet_name="Sheet2") # doctest: +SKIP
1041
1042 You can set the date format or datetime format:
1043
1044 >>> from datetime import date, datetime # doctest: +SKIP
1045 >>> df = pd.DataFrame(
1046 ... [
1047 ... [date(2014, 1, 31), date(1999, 9, 24)],
1048 ... [datetime(1998, 5, 26, 23, 33, 4), datetime(2014, 2, 28, 13, 5, 13)],
1049 ... ],
1050 ... index=["Date", "Datetime"],
1051 ... columns=["X", "Y"],
1052 ... ) # doctest: +SKIP
1053 >>> with pd.ExcelWriter(
1054 ... "path_to_file.xlsx",
1055 ... date_format="YYYY-MM-DD",
1056 ... datetime_format="YYYY-MM-DD HH:MM:SS",
1057 ... ) as writer:
1058 ... df.to_excel(writer) # doctest: +SKIP
1059
1060 You can also append to an existing Excel file:
1061
1062 >>> with pd.ExcelWriter("path_to_file.xlsx", mode="a", engine="openpyxl") as writer:
1063 ... df.to_excel(writer, sheet_name="Sheet3") # doctest: +SKIP
1064
1065 Here, the `if_sheet_exists` parameter can be set to replace a sheet if it
1066 already exists:
1067
1068 >>> with pd.ExcelWriter(
1069 ... "path_to_file.xlsx",
1070 ... mode="a",
1071 ... engine="openpyxl",
1072 ... if_sheet_exists="replace",
1073 ... ) as writer:
1074 ... df.to_excel(writer, sheet_name="Sheet1") # doctest: +SKIP
1075
1076 You can also write multiple DataFrames to a single sheet. Note that the
1077 ``if_sheet_exists`` parameter needs to be set to ``overlay``:
1078
1079 >>> with pd.ExcelWriter(
1080 ... "path_to_file.xlsx",
1081 ... mode="a",
1082 ... engine="openpyxl",
1083 ... if_sheet_exists="overlay",
1084 ... ) as writer:
1085 ... df1.to_excel(writer, sheet_name="Sheet1")
1086 ... df2.to_excel(writer, sheet_name="Sheet1", startcol=3) # doctest: +SKIP
1087
1088 You can store Excel file in RAM:
1089
1090 >>> import io
1091 >>> df = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"])
1092 >>> buffer = io.BytesIO()
1093 >>> with pd.ExcelWriter(buffer) as writer:
1094 ... df.to_excel(writer)
1095
1096 You can pack Excel file into zip archive:
1097
1098 >>> import zipfile # doctest: +SKIP
1099 >>> df = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) # doctest: +SKIP
1100 >>> with zipfile.ZipFile("path_to_file.zip", "w") as zf:
1101 ... with zf.open("filename.xlsx", "w") as buffer:
1102 ... with pd.ExcelWriter(buffer) as writer:
1103 ... df.to_excel(writer) # doctest: +SKIP
1104
1105 You can specify additional arguments to the underlying engine:
1106
1107 >>> with pd.ExcelWriter(
1108 ... "path_to_file.xlsx",
1109 ... engine="xlsxwriter",
1110 ... engine_kwargs={"options": {"nan_inf_to_errors": True}},
1111 ... ) as writer:
1112 ... df.to_excel(writer) # doctest: +SKIP
1113
1114 In append mode, ``engine_kwargs`` are passed through to
1115 openpyxl's ``load_workbook``:
1116
1117 >>> with pd.ExcelWriter(
1118 ... "path_to_file.xlsx",
1119 ... engine="openpyxl",
1120 ... mode="a",
1121 ... engine_kwargs={"keep_vba": True},
1122 ... ) as writer:
1123 ... df.to_excel(writer, sheet_name="Sheet2") # doctest: +SKIP
1124 """
1125
1126 # Defining an ExcelWriter implementation (see abstract methods for more...)
1127
1128 # - Mandatory
1129 # - ``write_cells(self, cells, sheet_name=None, startrow=0, startcol=0)``
1130 # --> called to write additional DataFrames to disk
1131 # - ``_supported_extensions`` (tuple of supported extensions), used to
1132 # check that engine supports the given extension.
1133 # - ``_engine`` - string that gives the engine name. Necessary to
1134 # instantiate class directly and bypass ``ExcelWriterMeta`` engine
1135 # lookup.
1136 # - ``save(self)`` --> called to save file to disk
1137 # - Mostly mandatory (i.e. should at least exist)
1138 # - book, cur_sheet, path
1139
1140 # - Optional:
1141 # - ``__init__(self, path, engine=None, **kwargs)`` --> always called
1142 # with path as first argument.
1143
1144 # You also need to register the class with ``register_writer()``.
1145 # Technically, ExcelWriter implementations don't need to subclass
1146 # ExcelWriter.
1147
1148 _engine: str
1149 _supported_extensions: tuple[str, ...]
1150
1151 def __new__(
1152 cls,
1153 path: FilePath | WriteExcelBuffer | ExcelWriter,
1154 engine: str | None = None,
1155 date_format: str | None = None,
1156 datetime_format: str | None = None,
1157 mode: str = "w",
1158 storage_options: StorageOptions | None = None,
1159 if_sheet_exists: ExcelWriterIfSheetExists | None = None,
1160 engine_kwargs: dict | None = None,
1161 ) -> Self:
1162 # only switch class if generic(ExcelWriter)
1163 if cls is ExcelWriter:
1164 if engine is None or (isinstance(engine, str) and engine == "auto"):
1165 if isinstance(path, str):
1166 ext = os.path.splitext(path)[-1][1:]
1167 else:
1168 ext = "xlsx"
1169
1170 try:
1171 engine = config.get_option(f"io.excel.{ext}.writer")
1172 if engine == "auto":
1173 engine = get_default_engine(ext, mode="writer")
1174 except KeyError as err:
1175 raise ValueError(f"No engine for filetype: '{ext}'") from err
1176
1177 # for mypy
1178 assert engine is not None
1179 # error: Incompatible types in assignment (expression has type
1180 # "type[ExcelWriter[Any]]", variable has type "type[Self]")
1181 cls = get_writer(engine) # type: ignore[assignment]
1182
1183 return object.__new__(cls)
1184
1185 @property
1186 def supported_extensions(self) -> tuple[str, ...]:
1187 """Extensions that writer engine supports."""
1188 return self._supported_extensions
1189
1190 @property
1191 def engine(self) -> str:
1192 """Name of engine."""
1193 return self._engine
1194
1195 @property
1196 def sheets(self) -> dict[str, Any]:
1197 """Mapping of sheet names to sheet objects."""
1198 raise NotImplementedError
1199
1200 @property
1201 def book(self) -> _WorkbookT:
1202 """
1203 Book instance. Class type will depend on the engine used.
1204
1205 This attribute can be used to access engine-specific features.
1206 """
1207 raise NotImplementedError
1208
1209 def _write_cells(
1210 self,
1211 cells,
1212 sheet_name: str | None = None,
1213 startrow: int = 0,
1214 startcol: int = 0,
1215 freeze_panes: tuple[int, int] | None = None,
1216 autofilter_range: str | None = None,
1217 ) -> None:
1218 """
1219 Write given formatted cells into Excel an excel sheet
1220
1221 Parameters
1222 ----------
1223 cells : generator
1224 cell of formatted data to save to Excel sheet
1225 sheet_name : str, default None
1226 Name of Excel sheet, if None, then use self.cur_sheet
1227 startrow : upper left cell row to dump data frame
1228 startcol : upper left cell column to dump data frame
1229 freeze_panes: int tuple of length 2
1230 contains the bottom-most row and right-most column to freeze
1231 autofilter_range: str, default None
1232 column ranges to add automatic filters to, for example "A1:D5"
1233 """
1234 raise NotImplementedError
1235
1236 def _save(self) -> None:
1237 """
1238 Save workbook to disk.
1239 """
1240 raise NotImplementedError
1241
1242 def __init__(
1243 self,
1244 path: FilePath | WriteExcelBuffer | ExcelWriter,
1245 engine: str | None = None,
1246 date_format: str | None = None,
1247 datetime_format: str | None = None,
1248 mode: str = "w",
1249 storage_options: StorageOptions | None = None,
1250 if_sheet_exists: ExcelWriterIfSheetExists | None = None,
1251 engine_kwargs: dict[str, Any] | None = None,
1252 ) -> None:
1253 # validate that this engine can handle the extension
1254 if isinstance(path, str):
1255 ext = os.path.splitext(path)[-1]
1256 self.check_extension(ext)
1257
1258 # use mode to open the file
1259 if "b" not in mode:
1260 mode += "b"
1261 # use "a" for the user to append data to excel but internally use "r+" to let
1262 # the excel backend first read the existing file and then write any data to it
1263 mode = mode.replace("a", "r+")
1264
1265 if if_sheet_exists not in (None, "error", "new", "replace", "overlay"):
1266 raise ValueError(
1267 f"'{if_sheet_exists}' is not valid for if_sheet_exists. "
1268 "Valid options are 'error', 'new', 'replace' and 'overlay'."
1269 )
1270 if if_sheet_exists and "r+" not in mode:
1271 raise ValueError("if_sheet_exists is only valid in append mode (mode='a')")
1272 if if_sheet_exists is None:
1273 if_sheet_exists = "error"
1274 self._if_sheet_exists = if_sheet_exists
1275
1276 # cast ExcelWriter to avoid adding 'if self._handles is not None'
1277 self._handles = IOHandles(
1278 cast(IO[bytes], path), compression={"compression": None}
1279 )
1280 if not isinstance(path, ExcelWriter):
1281 self._handles = get_handle(
1282 path, mode, storage_options=storage_options, is_text=False
1283 )
1284 self._cur_sheet = None
1285
1286 if date_format is None:
1287 self._date_format = "YYYY-MM-DD"
1288 else:
1289 self._date_format = date_format
1290 if datetime_format is None:
1291 self._datetime_format = "YYYY-MM-DD HH:MM:SS"
1292 else:
1293 self._datetime_format = datetime_format
1294
1295 self._mode = mode
1296
1297 @property
1298 def date_format(self) -> str:
1299 """
1300 Format string for dates written into Excel files (e.g. 'YYYY-MM-DD').
1301 """
1302 return self._date_format
1303
1304 @property
1305 def datetime_format(self) -> str:
1306 """
1307 Format string for dates written into Excel files (e.g. 'YYYY-MM-DD').
1308 """
1309 return self._datetime_format
1310
1311 @property
1312 def if_sheet_exists(self) -> str:
1313 """
1314 How to behave when writing to a sheet that already exists in append mode.
1315 """
1316 return self._if_sheet_exists
1317
1318 def __fspath__(self) -> str:
1319 return getattr(self._handles.handle, "name", "")
1320
1321 def _get_sheet_name(self, sheet_name: str | None) -> str:
1322 if sheet_name is None:
1323 sheet_name = self._cur_sheet
1324 if sheet_name is None: # pragma: no cover
1325 raise ValueError("Must pass explicit sheet_name or set _cur_sheet property")
1326 return sheet_name
1327
1328 def _value_with_fmt(
1329 self, val
1330 ) -> tuple[
1331 int | float | bool | str | datetime.datetime | datetime.date, str | None
1332 ]:
1333 """
1334 Convert numpy types to Python types for the Excel writers.
1335
1336 Parameters
1337 ----------
1338 val : object
1339 Value to be written into cells
1340
1341 Returns
1342 -------
1343 Tuple with the first element being the converted value and the second
1344 being an optional format
1345 """
1346 fmt = None
1347
1348 if is_integer(val):
1349 val = int(val)
1350 elif is_float(val):
1351 val = float(val)
1352 elif is_bool(val):
1353 val = bool(val)
1354 elif is_decimal(val):
1355 val = Decimal(val)
1356 elif isinstance(val, datetime.datetime):
1357 fmt = self._datetime_format
1358 elif isinstance(val, datetime.date):
1359 fmt = self._date_format
1360 elif isinstance(val, datetime.timedelta):
1361 val = val.total_seconds() / 86400
1362 fmt = "0"
1363 else:
1364 val = str(val)
1365 # GH#56954
1366 # Excel's limitation on cell contents is 32767 characters
1367 # xref https://support.microsoft.com/en-au/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3
1368 if len(val) > 32767:
1369 warnings.warn(
1370 f"Cell contents too long ({len(val)}), "
1371 "truncated to 32767 characters",
1372 UserWarning,
1373 stacklevel=find_stack_level(),
1374 )
1375 return val, fmt
1376
1377 @classmethod
1378 def check_extension(cls, ext: str) -> Literal[True]:
1379 """
1380 checks that path's extension against the Writer's supported
1381 extensions. If it isn't supported, raises UnsupportedFiletypeError.
1382 """
1383 if ext.startswith("."):
1384 ext = ext[1:]
1385 if not any(ext in extension for extension in cls._supported_extensions):
1386 raise ValueError(f"Invalid extension for engine '{cls.engine}': '{ext}'")
1387 return True
1388
1389 # Allow use as a contextmanager
1390 def __enter__(self) -> Self:
1391 return self
1392
1393 def __exit__(
1394 self,
1395 exc_type: type[BaseException] | None,
1396 exc_value: BaseException | None,
1397 traceback: TracebackType | None,
1398 ) -> None:
1399 self.close()
1400
1401 def close(self) -> None:
1402 """synonym for save, to make it more file-like"""
1403 self._save()
1404 self._handles.close()
1405
1406
1407XLS_SIGNATURES = (
1408 b"\x09\x00\x04\x00\x07\x00\x10\x00", # BIFF2
1409 b"\x09\x02\x06\x00\x00\x00\x10\x00", # BIFF3
1410 b"\x09\x04\x06\x00\x00\x00\x10\x00", # BIFF4
1411 b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1", # Compound File Binary
1412)
1413ZIP_SIGNATURE = b"PK\x03\x04"
1414PEEK_SIZE = max(map(len, (*XLS_SIGNATURES, ZIP_SIGNATURE)))
1415
1416
1417def inspect_excel_format(
1418 content_or_path: FilePath | ReadBuffer[bytes],
1419 storage_options: StorageOptions | None = None,
1420) -> str | None:
1421 """
1422 Inspect the path or content of an excel file and get its format.
1423
1424 Adopted from xlrd: https://github.com/python-excel/xlrd.
1425
1426 Parameters
1427 ----------
1428 content_or_path : str or file-like object
1429 Path to file or content of file to inspect. May be a URL.
1430 storage_options : dict, optional
1431 Extra options that make sense for a particular storage connection, e.g.
1432 host, port, username, password, etc. For HTTP(S) URLs the key-value pairs
1433 are forwarded to ``urllib.request.Request`` as header options. For other
1434 URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are
1435 forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more
1436 details, and for more examples on storage options refer `here
1437 <https://pandas.pydata.org/docs/user_guide/io.html?
1438 highlight=storage_options#reading-writing-remote-files>`_.
1439
1440 Returns
1441 -------
1442 str or None
1443 Format of file if it can be determined.
1444
1445 Raises
1446 ------
1447 ValueError
1448 If resulting stream is empty.
1449 BadZipFile
1450 If resulting stream does not have an XLS signature and is not a valid zipfile.
1451 """
1452 with get_handle(
1453 content_or_path, "rb", storage_options=storage_options, is_text=False
1454 ) as handle:
1455 stream = handle.handle
1456 stream.seek(0)
1457 buf = stream.read(PEEK_SIZE)
1458 if buf is None:
1459 raise ValueError("stream is empty")
1460 assert isinstance(buf, bytes)
1461 peek = buf
1462 stream.seek(0)
1463
1464 if any(peek.startswith(sig) for sig in XLS_SIGNATURES):
1465 return "xls"
1466 elif not peek.startswith(ZIP_SIGNATURE):
1467 return None
1468
1469 with zipfile.ZipFile(stream) as zf:
1470 # Workaround for some third party files that use forward slashes and
1471 # lower case names.
1472 component_names = {
1473 name.replace("\\", "/").lower() for name in zf.namelist()
1474 }
1475
1476 if "xl/workbook.xml" in component_names:
1477 return "xlsx"
1478 if "xl/workbook.bin" in component_names:
1479 return "xlsb"
1480 if "content.xml" in component_names:
1481 return "ods"
1482 return "zip"
1483
1484
1485@set_module("pandas")
1486class ExcelFile:
1487 """
1488 Class for parsing tabular Excel sheets into DataFrame objects.
1489
1490 See read_excel for more documentation.
1491
1492 Parameters
1493 ----------
1494 path_or_buffer : str, bytes, pathlib.Path,
1495 A file-like object, xlrd workbook or openpyxl workbook.
1496 If a string or path object, expected to be a path to a
1497 .xls, .xlsx, .xlsb, .xlsm, .odf, .ods, or .odt file.
1498 engine : str, default None
1499 If io is not a buffer or path, this must be set to identify io.
1500 Supported engines: ``xlrd``, ``openpyxl``, ``odf``, ``pyxlsb``, ``calamine``
1501 Engine compatibility :
1502
1503 - ``xlrd`` supports old-style Excel files (.xls).
1504 - ``openpyxl`` supports newer Excel file formats.
1505 - ``odf`` supports OpenDocument file formats (.odf, .ods, .odt).
1506 - ``pyxlsb`` supports Binary Excel files.
1507 - ``calamine`` supports Excel (.xls, .xlsx, .xlsm, .xlsb)
1508 and OpenDocument (.ods) file formats.
1509
1510 The engine `xlrd <https://xlrd.readthedocs.io/en/latest/>`_
1511 now only supports old-style ``.xls`` files.
1512 When ``engine=None``, the following logic will be
1513 used to determine the engine:
1514
1515 - If ``path_or_buffer`` is an OpenDocument format (.odf, .ods, .odt),
1516 then `odf <https://pypi.org/project/odfpy/>`_ will be used.
1517 - Otherwise if ``path_or_buffer`` is an xls format,
1518 ``xlrd`` will be used.
1519 - Otherwise if ``path_or_buffer`` is in xlsb format,
1520 `pyxlsb <https://pypi.org/project/pyxlsb/>`_ will be used.
1521 - Otherwise if `openpyxl <https://pypi.org/project/openpyxl/>`_ is installed,
1522 then ``openpyxl`` will be used.
1523 - Otherwise if ``xlrd >= 2.0`` is installed, a ``ValueError`` will be raised.
1524
1525 .. warning::
1526
1527 Please do not report issues when using ``xlrd`` to read ``.xlsx`` files.
1528 This is not supported, switch to using ``openpyxl`` instead.
1529 storage_options : dict, optional
1530 Extra options that make sense for a particular storage connection, e.g.
1531 host, port, username, password, etc. For HTTP(S) URLs the key-value pairs
1532 are forwarded to ``urllib.request.Request`` as header options. For other
1533 URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are
1534 forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more
1535 details, and for more examples on storage options refer `here
1536 <https://pandas.pydata.org/docs/user_guide/io.html?
1537 highlight=storage_options#reading-writing-remote-files>`_.
1538 engine_kwargs : dict, optional
1539 Arbitrary keyword arguments passed to excel engine.
1540
1541 See Also
1542 --------
1543 DataFrame.to_excel : Write DataFrame to an Excel file.
1544 DataFrame.to_csv : Write DataFrame to a comma-separated values (csv) file.
1545 read_csv : Read a comma-separated values (csv) file into DataFrame.
1546 read_fwf : Read a table of fixed-width formatted lines into DataFrame.
1547
1548 Examples
1549 --------
1550 >>> file = pd.ExcelFile("myfile.xlsx") # doctest: +SKIP
1551 >>> with pd.ExcelFile("myfile.xls") as xls: # doctest: +SKIP
1552 ... df1 = pd.read_excel(xls, "Sheet1") # doctest: +SKIP
1553 """
1554
1555 from pandas.io.excel._calamine import CalamineReader
1556 from pandas.io.excel._odfreader import ODFReader
1557 from pandas.io.excel._openpyxl import OpenpyxlReader
1558 from pandas.io.excel._pyxlsb import PyxlsbReader
1559 from pandas.io.excel._xlrd import XlrdReader
1560
1561 _engines: Mapping[str, Any] = {
1562 "xlrd": XlrdReader,
1563 "openpyxl": OpenpyxlReader,
1564 "odf": ODFReader,
1565 "pyxlsb": PyxlsbReader,
1566 "calamine": CalamineReader,
1567 }
1568
1569 def __init__(
1570 self,
1571 path_or_buffer,
1572 engine: str | None = None,
1573 storage_options: StorageOptions | None = None,
1574 engine_kwargs: dict | None = None,
1575 ) -> None:
1576 if engine_kwargs is None:
1577 engine_kwargs = {}
1578
1579 if engine is not None and engine not in self._engines:
1580 raise ValueError(f"Unknown engine: {engine}")
1581
1582 # Always a string
1583 self._io = stringify_path(path_or_buffer)
1584
1585 if engine is None:
1586 # Only determine ext if it is needed
1587 ext: str | None = None
1588
1589 if not isinstance(
1590 path_or_buffer, (str, os.PathLike, ExcelFile)
1591 ) and not is_file_like(path_or_buffer):
1592 # GH#56692 - avoid importing xlrd if possible
1593 if import_optional_dependency("xlrd", errors="ignore") is None:
1594 xlrd_version = None
1595 else:
1596 import xlrd
1597
1598 xlrd_version = Version(get_version(xlrd))
1599
1600 if xlrd_version is not None and isinstance(path_or_buffer, xlrd.Book):
1601 ext = "xls"
1602
1603 if ext is None:
1604 ext = inspect_excel_format(
1605 content_or_path=path_or_buffer, storage_options=storage_options
1606 )
1607 if ext is None:
1608 raise ValueError(
1609 "Excel file format cannot be determined, you must specify "
1610 "an engine manually."
1611 )
1612
1613 engine = config.get_option(f"io.excel.{ext}.reader")
1614 if engine == "auto":
1615 engine = get_default_engine(ext, mode="reader")
1616
1617 assert engine is not None
1618 self.engine = engine
1619 self.storage_options = storage_options
1620
1621 self._reader = self._engines[engine](
1622 self._io,
1623 storage_options=storage_options,
1624 engine_kwargs=engine_kwargs,
1625 )
1626
1627 def __fspath__(self):
1628 return self._io
1629
1630 def parse(
1631 self,
1632 sheet_name: str | int | list[int] | list[str] | None = 0,
1633 header: int | Sequence[int] | None = 0,
1634 names: SequenceNotStr[Hashable] | range | None = None,
1635 index_col: int | Sequence[int] | None = None,
1636 usecols=None,
1637 converters=None,
1638 true_values: Iterable[Hashable] | None = None,
1639 false_values: Iterable[Hashable] | None = None,
1640 skiprows: Sequence[int] | int | Callable[[int], object] | None = None,
1641 nrows: int | None = None,
1642 na_values=None,
1643 parse_dates: list | dict | bool = False,
1644 date_format: str | dict[Hashable, str] | None = None,
1645 thousands: str | None = None,
1646 comment: str | None = None,
1647 skipfooter: int = 0,
1648 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default,
1649 **kwds,
1650 ) -> DataFrame | dict[str, DataFrame] | dict[int, DataFrame]:
1651 """
1652 Parse specified sheet(s) into a DataFrame.
1653
1654 Equivalent to read_excel(ExcelFile, ...) See the read_excel
1655 docstring for more info on accepted parameters.
1656
1657 Parameters
1658 ----------
1659 sheet_name : str, int, list, or None, default 0
1660 Strings are used for sheet names. Integers are used in zero-indexed
1661 sheet positions (chart sheets do not count as a sheet position).
1662 Lists of strings/integers are used to request multiple sheets.
1663 When ``None``, will return a dictionary containing DataFrames for
1664 each sheet.
1665 header : int, list of int, default 0
1666 Row (0-indexed) to use for the column labels of the parsed
1667 DataFrame. If a list of integers is passed those row positions will
1668 be combined into a ``MultiIndex``. Use None if there is no header.
1669 names : array-like, default None
1670 List of column names to use. If file contains no header row,
1671 then you should explicitly pass header=None.
1672 index_col : int, str, list of int, default None
1673 Column (0-indexed) to use as the row labels of the DataFrame.
1674 Pass None if there is no such column. If a list is passed,
1675 those columns will be combined into a ``MultiIndex``. If a
1676 subset of data is selected with ``usecols``, index_col
1677 is based on the subset.
1678
1679 Missing values will be forward filled to allow roundtripping with
1680 ``to_excel`` for ``merged_cells=True``. To avoid forward filling the
1681 missing values use ``set_index`` after reading the data instead of
1682 ``index_col``.
1683 usecols : str, list-like, or callable, default None
1684 * If None, then parse all columns.
1685 * If str, then indicates comma separated list of Excel column letters
1686 and column ranges (e.g. "A:E" or "A,C,E:F"). Ranges are inclusive of
1687 both sides.
1688 * If list of int, then indicates list of column numbers to be parsed
1689 (0-indexed).
1690 * If list of string, then indicates list of column names to be parsed.
1691 * If callable, then evaluate each column name against it and parse the
1692 column if the callable returns ``True``.
1693
1694 Returns a subset of the columns according to behavior above.
1695 converters : dict, default None
1696 Dict of functions for converting values in certain columns. Keys can
1697 either be integers or column labels, values are functions that take one
1698 input argument, the Excel cell content, and return the transformed
1699 content.
1700 true_values : list, default None
1701 Values to consider as True.
1702 false_values : list, default None
1703 Values to consider as False.
1704 skiprows : list-like, int, or callable, optional
1705 Line numbers to skip (0-indexed) or number of lines to skip (int) at the
1706 start of the file. If callable, the callable function will be evaluated
1707 against the row indices, returning True if the row should be skipped and
1708 False otherwise. An example of a valid callable argument would be ``lambda
1709 x: x in [0, 2]``.
1710 nrows : int, default None
1711 Number of rows to parse.
1712 na_values : scalar, str, list-like, or dict, default None
1713 Additional strings to recognize as NA/NaN. If dict passed, specific
1714 per-column NA values.
1715 parse_dates : bool, list-like, or dict, default False
1716 The behavior is as follows:
1717
1718 * ``bool``. If True -> try parsing the index.
1719 * ``list`` of int or names. e.g. If [1, 2, 3] -> try parsing columns 1, 2, 3
1720 each as a separate date column.
1721 * ``list`` of lists. e.g. If [[1, 3]] -> combine columns 1 and 3 and
1722 parse as a single date column.
1723 * ``dict``, e.g. {'foo' : [1, 3]} -> parse columns 1, 3 as date and call
1724 result 'foo'
1725
1726 If a column or index contains an unparsable date, the entire column or
1727 index will be returned unaltered as an object data type. If you
1728 don`t want to parse some cells as date just change their type
1729 in Excel to "Text".For non-standard datetime parsing, use
1730 ``pd.to_datetime`` after ``pd.read_excel``.
1731
1732 Note: A fast-path exists for iso8601-formatted dates.
1733 date_format : str or dict of column -> format, default ``None``
1734 If used in conjunction with ``parse_dates``, will parse dates
1735 according to this format. For anything more complex,
1736 please read in as ``object`` and then apply :func:`to_datetime` as-needed.
1737 thousands : str, default None
1738 Thousands separator for parsing string columns to numeric. Note that
1739 this parameter is only necessary for columns stored as TEXT in Excel,
1740 any numeric columns will automatically be parsed, regardless of display
1741 format.
1742 comment : str, default None
1743 Comments out remainder of line. Pass a character or characters to this
1744 argument to indicate comments in the input file. Any data between the
1745 comment string and the end of the current line is ignored.
1746 skipfooter : int, default 0
1747 Rows at the end to skip (0-indexed).
1748 dtype_backend : {'numpy_nullable', 'pyarrow'}
1749 Back-end data type applied to the resultant :class:`DataFrame`
1750 (still experimental). If not specified, the default behavior
1751 is to not use nullable data types. If specified, the behavior
1752 is as follows:
1753
1754 * ``"numpy_nullable"``: returns nullable-dtype-backed :class:`DataFrame`
1755 * ``"pyarrow"``: returns pyarrow-backed nullable
1756 :class:`ArrowDtype` :class:`DataFrame`
1757
1758 .. versionadded:: 2.0
1759 **kwds : dict, optional
1760 Arbitrary keyword arguments passed to excel engine.
1761
1762 Returns
1763 -------
1764 DataFrame or dict of DataFrames
1765 DataFrame from the passed in Excel file.
1766
1767 See Also
1768 --------
1769 read_excel : Read an Excel sheet values (xlsx) file into DataFrame.
1770 read_csv : Read a comma-separated values (csv) file into DataFrame.
1771 read_fwf : Read a table of fixed-width formatted lines into DataFrame.
1772
1773 Examples
1774 --------
1775 >>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=["A", "B", "C"])
1776 >>> df.to_excel("myfile.xlsx") # doctest: +SKIP
1777 >>> file = pd.ExcelFile("myfile.xlsx") # doctest: +SKIP
1778 >>> file.parse() # doctest: +SKIP
1779 """
1780 return self._reader.parse(
1781 sheet_name=sheet_name,
1782 header=header,
1783 names=names,
1784 index_col=index_col,
1785 usecols=usecols,
1786 converters=converters,
1787 true_values=true_values,
1788 false_values=false_values,
1789 skiprows=skiprows,
1790 nrows=nrows,
1791 na_values=na_values,
1792 parse_dates=parse_dates,
1793 date_format=date_format,
1794 thousands=thousands,
1795 comment=comment,
1796 skipfooter=skipfooter,
1797 dtype_backend=dtype_backend,
1798 **kwds,
1799 )
1800
1801 @property
1802 def book(self):
1803 """
1804 Gets the Excel workbook.
1805
1806 Workbook is the top-level container for all document information.
1807
1808 Returns
1809 -------
1810 Excel Workbook
1811 The workbook object of the type defined by the engine being used.
1812
1813 See Also
1814 --------
1815 read_excel : Read an Excel file into a pandas DataFrame.
1816
1817 Examples
1818 --------
1819 >>> file = pd.ExcelFile("myfile.xlsx") # doctest: +SKIP
1820 >>> file.book # doctest: +SKIP
1821 <openpyxl.workbook.workbook.Workbook object at 0x11eb5ad70>
1822 >>> file.book.path # doctest: +SKIP
1823 '/xl/workbook.xml'
1824 >>> file.book.active # doctest: +SKIP
1825 <openpyxl.worksheet._read_only.ReadOnlyWorksheet object at 0x11eb5b370>
1826 >>> file.book.sheetnames # doctest: +SKIP
1827 ['Sheet1', 'Sheet2']
1828 """
1829 return self._reader.book
1830
1831 @property
1832 def sheet_names(self):
1833 """
1834 Names of the sheets in the document.
1835
1836 This is particularly useful for loading a specific sheet into a DataFrame when
1837 you do not know the sheet names beforehand.
1838
1839 Returns
1840 -------
1841 list of str
1842 List of sheet names in the document.
1843
1844 See Also
1845 --------
1846 ExcelFile.parse : Parse a sheet into a DataFrame.
1847 read_excel : Read an Excel file into a pandas DataFrame. If you know the sheet
1848 names, it may be easier to specify them directly to read_excel.
1849
1850 Examples
1851 --------
1852 >>> file = pd.ExcelFile("myfile.xlsx") # doctest: +SKIP
1853 >>> file.sheet_names # doctest: +SKIP
1854 ["Sheet1", "Sheet2"]
1855 """
1856 return self._reader.sheet_names
1857
1858 def close(self) -> None:
1859 """close io if necessary"""
1860 self._reader.close()
1861
1862 def __enter__(self) -> Self:
1863 return self
1864
1865 def __exit__(
1866 self,
1867 exc_type: type[BaseException] | None,
1868 exc_value: BaseException | None,
1869 traceback: TracebackType | None,
1870 ) -> None:
1871 self.close()