1"""
2Module for applying conditional formatting to DataFrames and Series.
3"""
4from __future__ import annotations
5
6from contextlib import contextmanager
7import copy
8from functools import partial
9import operator
10from typing import (
11 TYPE_CHECKING,
12 Any,
13 Callable,
14 overload,
15)
16import warnings
17
18import numpy as np
19
20from pandas._config import get_option
21
22from pandas.compat._optional import import_optional_dependency
23from pandas.util._decorators import (
24 Substitution,
25 doc,
26)
27from pandas.util._exceptions import find_stack_level
28
29import pandas as pd
30from pandas import (
31 IndexSlice,
32 RangeIndex,
33)
34import pandas.core.common as com
35from pandas.core.frame import (
36 DataFrame,
37 Series,
38)
39from pandas.core.generic import NDFrame
40from pandas.core.shared_docs import _shared_docs
41
42from pandas.io.formats.format import save_to_buffer
43
44jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.")
45
46from pandas.io.formats.style_render import (
47 CSSProperties,
48 CSSStyles,
49 ExtFormatter,
50 StylerRenderer,
51 Subset,
52 Tooltips,
53 format_table_styles,
54 maybe_convert_css_to_tuples,
55 non_reducing_slice,
56 refactor_levels,
57)
58
59if TYPE_CHECKING:
60 from collections.abc import (
61 Generator,
62 Hashable,
63 Sequence,
64 )
65
66 from matplotlib.colors import Colormap
67
68 from pandas._typing import (
69 Axis,
70 AxisInt,
71 FilePath,
72 IndexLabel,
73 IntervalClosedType,
74 Level,
75 QuantileInterpolation,
76 Scalar,
77 StorageOptions,
78 WriteBuffer,
79 WriteExcelBuffer,
80 )
81
82 from pandas import ExcelWriter
83
84try:
85 import matplotlib as mpl
86 import matplotlib.pyplot as plt
87
88 has_mpl = True
89except ImportError:
90 has_mpl = False
91
92
93@contextmanager
94def _mpl(func: Callable) -> Generator[tuple[Any, Any], None, None]:
95 if has_mpl:
96 yield plt, mpl
97 else:
98 raise ImportError(f"{func.__name__} requires matplotlib.")
99
100
101####
102# Shared Doc Strings
103
104subset_args = """subset : label, array-like, IndexSlice, optional
105 A valid 2d input to `DataFrame.loc[<subset>]`, or, in the case of a 1d input
106 or single key, to `DataFrame.loc[:, <subset>]` where the columns are
107 prioritised, to limit ``data`` to *before* applying the function."""
108
109properties_args = """props : str, default None
110 CSS properties to use for highlighting. If ``props`` is given, ``color``
111 is not used."""
112
113coloring_args = """color : str, default '{default}'
114 Background color to use for highlighting."""
115
116buffering_args = """buf : str, path object, file-like object, optional
117 String, path object (implementing ``os.PathLike[str]``), or file-like
118 object implementing a string ``write()`` function. If ``None``, the result is
119 returned as a string."""
120
121encoding_args = """encoding : str, optional
122 Character encoding setting for file output (and meta tags if available).
123 Defaults to ``pandas.options.styler.render.encoding`` value of "utf-8"."""
124
125#
126###
127
128
129class Styler(StylerRenderer):
130 r"""
131 Helps style a DataFrame or Series according to the data with HTML and CSS.
132
133 Parameters
134 ----------
135 data : Series or DataFrame
136 Data to be styled - either a Series or DataFrame.
137 precision : int, optional
138 Precision to round floats to. If not given defaults to
139 ``pandas.options.styler.format.precision``.
140
141 .. versionchanged:: 1.4.0
142 table_styles : list-like, default None
143 List of {selector: (attr, value)} dicts; see Notes.
144 uuid : str, default None
145 A unique identifier to avoid CSS collisions; generated automatically.
146 caption : str, tuple, default None
147 String caption to attach to the table. Tuple only used for LaTeX dual captions.
148 table_attributes : str, default None
149 Items that show up in the opening ``<table>`` tag
150 in addition to automatic (by default) id.
151 cell_ids : bool, default True
152 If True, each cell will have an ``id`` attribute in their HTML tag.
153 The ``id`` takes the form ``T_<uuid>_row<num_row>_col<num_col>``
154 where ``<uuid>`` is the unique identifier, ``<num_row>`` is the row
155 number and ``<num_col>`` is the column number.
156 na_rep : str, optional
157 Representation for missing values.
158 If ``na_rep`` is None, no special formatting is applied, and falls back to
159 ``pandas.options.styler.format.na_rep``.
160
161 uuid_len : int, default 5
162 If ``uuid`` is not specified, the length of the ``uuid`` to randomly generate
163 expressed in hex characters, in range [0, 32].
164 decimal : str, optional
165 Character used as decimal separator for floats, complex and integers. If not
166 given uses ``pandas.options.styler.format.decimal``.
167
168 .. versionadded:: 1.3.0
169
170 thousands : str, optional, default None
171 Character used as thousands separator for floats, complex and integers. If not
172 given uses ``pandas.options.styler.format.thousands``.
173
174 .. versionadded:: 1.3.0
175
176 escape : str, optional
177 Use 'html' to replace the characters ``&``, ``<``, ``>``, ``'``, and ``"``
178 in cell display string with HTML-safe sequences.
179 Use 'latex' to replace the characters ``&``, ``%``, ``$``, ``#``, ``_``,
180 ``{``, ``}``, ``~``, ``^``, and ``\`` in the cell display string with
181 LaTeX-safe sequences. Use 'latex-math' to replace the characters
182 the same way as in 'latex' mode, except for math substrings,
183 which either are surrounded by two characters ``$`` or start with
184 the character ``\(`` and end with ``\)``.
185 If not given uses ``pandas.options.styler.format.escape``.
186
187 .. versionadded:: 1.3.0
188 formatter : str, callable, dict, optional
189 Object to define how values are displayed. See ``Styler.format``. If not given
190 uses ``pandas.options.styler.format.formatter``.
191
192 .. versionadded:: 1.4.0
193
194 Attributes
195 ----------
196 env : Jinja2 jinja2.Environment
197 template_html : Jinja2 Template
198 template_html_table : Jinja2 Template
199 template_html_style : Jinja2 Template
200 template_latex : Jinja2 Template
201 loader : Jinja2 Loader
202
203 See Also
204 --------
205 DataFrame.style : Return a Styler object containing methods for building
206 a styled HTML representation for the DataFrame.
207
208 Notes
209 -----
210 Most styling will be done by passing style functions into
211 ``Styler.apply`` or ``Styler.map``. Style functions should
212 return values with strings containing CSS ``'attr: value'`` that will
213 be applied to the indicated cells.
214
215 If using in the Jupyter notebook, Styler has defined a ``_repr_html_``
216 to automatically render itself. Otherwise call Styler.to_html to get
217 the generated HTML.
218
219 CSS classes are attached to the generated HTML
220
221 * Index and Column names include ``index_name`` and ``level<k>``
222 where `k` is its level in a MultiIndex
223 * Index label cells include
224
225 * ``row_heading``
226 * ``row<n>`` where `n` is the numeric position of the row
227 * ``level<k>`` where `k` is the level in a MultiIndex
228
229 * Column label cells include
230 * ``col_heading``
231 * ``col<n>`` where `n` is the numeric position of the column
232 * ``level<k>`` where `k` is the level in a MultiIndex
233
234 * Blank cells include ``blank``
235 * Data cells include ``data``
236 * Trimmed cells include ``col_trim`` or ``row_trim``.
237
238 Any, or all, or these classes can be renamed by using the ``css_class_names``
239 argument in ``Styler.set_table_classes``, giving a value such as
240 *{"row": "MY_ROW_CLASS", "col_trim": "", "row_trim": ""}*.
241
242 Examples
243 --------
244 >>> df = pd.DataFrame([[1.0, 2.0, 3.0], [4, 5, 6]], index=['a', 'b'],
245 ... columns=['A', 'B', 'C'])
246 >>> pd.io.formats.style.Styler(df, precision=2,
247 ... caption="My table") # doctest: +SKIP
248
249 Please see:
250 `Table Visualization <../../user_guide/style.ipynb>`_ for more examples.
251 """
252
253 def __init__(
254 self,
255 data: DataFrame | Series,
256 precision: int | None = None,
257 table_styles: CSSStyles | None = None,
258 uuid: str | None = None,
259 caption: str | tuple | list | None = None,
260 table_attributes: str | None = None,
261 cell_ids: bool = True,
262 na_rep: str | None = None,
263 uuid_len: int = 5,
264 decimal: str | None = None,
265 thousands: str | None = None,
266 escape: str | None = None,
267 formatter: ExtFormatter | None = None,
268 ) -> None:
269 super().__init__(
270 data=data,
271 uuid=uuid,
272 uuid_len=uuid_len,
273 table_styles=table_styles,
274 table_attributes=table_attributes,
275 caption=caption,
276 cell_ids=cell_ids,
277 precision=precision,
278 )
279
280 # validate ordered args
281 thousands = thousands or get_option("styler.format.thousands")
282 decimal = decimal or get_option("styler.format.decimal")
283 na_rep = na_rep or get_option("styler.format.na_rep")
284 escape = escape or get_option("styler.format.escape")
285 formatter = formatter or get_option("styler.format.formatter")
286 # precision is handled by superclass as default for performance
287
288 self.format(
289 formatter=formatter,
290 precision=precision,
291 na_rep=na_rep,
292 escape=escape,
293 decimal=decimal,
294 thousands=thousands,
295 )
296
297 def concat(self, other: Styler) -> Styler:
298 """
299 Append another Styler to combine the output into a single table.
300
301 .. versionadded:: 1.5.0
302
303 Parameters
304 ----------
305 other : Styler
306 The other Styler object which has already been styled and formatted. The
307 data for this Styler must have the same columns as the original, and the
308 number of index levels must also be the same to render correctly.
309
310 Returns
311 -------
312 Styler
313
314 Notes
315 -----
316 The purpose of this method is to extend existing styled dataframes with other
317 metrics that may be useful but may not conform to the original's structure.
318 For example adding a sub total row, or displaying metrics such as means,
319 variance or counts.
320
321 Styles that are applied using the ``apply``, ``map``, ``apply_index``
322 and ``map_index``, and formatting applied with ``format`` and
323 ``format_index`` will be preserved.
324
325 .. warning::
326 Only the output methods ``to_html``, ``to_string`` and ``to_latex``
327 currently work with concatenated Stylers.
328
329 Other output methods, including ``to_excel``, **do not** work with
330 concatenated Stylers.
331
332 The following should be noted:
333
334 - ``table_styles``, ``table_attributes``, ``caption`` and ``uuid`` are all
335 inherited from the original Styler and not ``other``.
336 - hidden columns and hidden index levels will be inherited from the
337 original Styler
338 - ``css`` will be inherited from the original Styler, and the value of
339 keys ``data``, ``row_heading`` and ``row`` will be prepended with
340 ``foot0_``. If more concats are chained, their styles will be prepended
341 with ``foot1_``, ''foot_2'', etc., and if a concatenated style have
342 another concatanated style, the second style will be prepended with
343 ``foot{parent}_foot{child}_``.
344
345 A common use case is to concatenate user defined functions with
346 ``DataFrame.agg`` or with described statistics via ``DataFrame.describe``.
347 See examples.
348
349 Examples
350 --------
351 A common use case is adding totals rows, or otherwise, via methods calculated
352 in ``DataFrame.agg``.
353
354 >>> df = pd.DataFrame([[4, 6], [1, 9], [3, 4], [5, 5], [9, 6]],
355 ... columns=["Mike", "Jim"],
356 ... index=["Mon", "Tue", "Wed", "Thurs", "Fri"])
357 >>> styler = df.style.concat(df.agg(["sum"]).style) # doctest: +SKIP
358
359 .. figure:: ../../_static/style/footer_simple.png
360
361 Since the concatenated object is a Styler the existing functionality can be
362 used to conditionally format it as well as the original.
363
364 >>> descriptors = df.agg(["sum", "mean", lambda s: s.dtype])
365 >>> descriptors.index = ["Total", "Average", "dtype"]
366 >>> other = (descriptors.style
367 ... .highlight_max(axis=1, subset=(["Total", "Average"], slice(None)))
368 ... .format(subset=("Average", slice(None)), precision=2, decimal=",")
369 ... .map(lambda v: "font-weight: bold;"))
370 >>> styler = (df.style
371 ... .highlight_max(color="salmon")
372 ... .set_table_styles([{"selector": ".foot_row0",
373 ... "props": "border-top: 1px solid black;"}]))
374 >>> styler.concat(other) # doctest: +SKIP
375
376 .. figure:: ../../_static/style/footer_extended.png
377
378 When ``other`` has fewer index levels than the original Styler it is possible
379 to extend the index in ``other``, with placeholder levels.
380
381 >>> df = pd.DataFrame([[1], [2]],
382 ... index=pd.MultiIndex.from_product([[0], [1, 2]]))
383 >>> descriptors = df.agg(["sum"])
384 >>> descriptors.index = pd.MultiIndex.from_product([[""], descriptors.index])
385 >>> df.style.concat(descriptors.style) # doctest: +SKIP
386 """
387 if not isinstance(other, Styler):
388 raise TypeError("`other` must be of type `Styler`")
389 if not self.data.columns.equals(other.data.columns):
390 raise ValueError("`other.data` must have same columns as `Styler.data`")
391 if not self.data.index.nlevels == other.data.index.nlevels:
392 raise ValueError(
393 "number of index levels must be same in `other` "
394 "as in `Styler`. See documentation for suggestions."
395 )
396 self.concatenated.append(other)
397 return self
398
399 def _repr_html_(self) -> str | None:
400 """
401 Hooks into Jupyter notebook rich display system, which calls _repr_html_ by
402 default if an object is returned at the end of a cell.
403 """
404 if get_option("styler.render.repr") == "html":
405 return self.to_html()
406 return None
407
408 def _repr_latex_(self) -> str | None:
409 if get_option("styler.render.repr") == "latex":
410 return self.to_latex()
411 return None
412
413 def set_tooltips(
414 self,
415 ttips: DataFrame,
416 props: CSSProperties | None = None,
417 css_class: str | None = None,
418 ) -> Styler:
419 """
420 Set the DataFrame of strings on ``Styler`` generating ``:hover`` tooltips.
421
422 These string based tooltips are only applicable to ``<td>`` HTML elements,
423 and cannot be used for column or index headers.
424
425 .. versionadded:: 1.3.0
426
427 Parameters
428 ----------
429 ttips : DataFrame
430 DataFrame containing strings that will be translated to tooltips, mapped
431 by identical column and index values that must exist on the underlying
432 Styler data. None, NaN values, and empty strings will be ignored and
433 not affect the rendered HTML.
434 props : list-like or str, optional
435 List of (attr, value) tuples or a valid CSS string. If ``None`` adopts
436 the internal default values described in notes.
437 css_class : str, optional
438 Name of the tooltip class used in CSS, should conform to HTML standards.
439 Only useful if integrating tooltips with external CSS. If ``None`` uses the
440 internal default value 'pd-t'.
441
442 Returns
443 -------
444 Styler
445
446 Notes
447 -----
448 Tooltips are created by adding `<span class="pd-t"></span>` to each data cell
449 and then manipulating the table level CSS to attach pseudo hover and pseudo
450 after selectors to produce the required the results.
451
452 The default properties for the tooltip CSS class are:
453
454 - visibility: hidden
455 - position: absolute
456 - z-index: 1
457 - background-color: black
458 - color: white
459 - transform: translate(-20px, -20px)
460
461 The property 'visibility: hidden;' is a key prerequisite to the hover
462 functionality, and should always be included in any manual properties
463 specification, using the ``props`` argument.
464
465 Tooltips are not designed to be efficient, and can add large amounts of
466 additional HTML for larger tables, since they also require that ``cell_ids``
467 is forced to `True`.
468
469 Examples
470 --------
471 Basic application
472
473 >>> df = pd.DataFrame(data=[[0, 1], [2, 3]])
474 >>> ttips = pd.DataFrame(
475 ... data=[["Min", ""], [np.nan, "Max"]], columns=df.columns, index=df.index
476 ... )
477 >>> s = df.style.set_tooltips(ttips).to_html()
478
479 Optionally controlling the tooltip visual display
480
481 >>> df.style.set_tooltips(ttips, css_class='tt-add', props=[
482 ... ('visibility', 'hidden'),
483 ... ('position', 'absolute'),
484 ... ('z-index', 1)]) # doctest: +SKIP
485 >>> df.style.set_tooltips(ttips, css_class='tt-add',
486 ... props='visibility:hidden; position:absolute; z-index:1;')
487 ... # doctest: +SKIP
488 """
489 if not self.cell_ids:
490 # tooltips not optimised for individual cell check. requires reasonable
491 # redesign and more extensive code for a feature that might be rarely used.
492 raise NotImplementedError(
493 "Tooltips can only render with 'cell_ids' is True."
494 )
495 if not ttips.index.is_unique or not ttips.columns.is_unique:
496 raise KeyError(
497 "Tooltips render only if `ttips` has unique index and columns."
498 )
499 if self.tooltips is None: # create a default instance if necessary
500 self.tooltips = Tooltips()
501 self.tooltips.tt_data = ttips
502 if props:
503 self.tooltips.class_properties = props
504 if css_class:
505 self.tooltips.class_name = css_class
506
507 return self
508
509 @doc(
510 NDFrame.to_excel,
511 klass="Styler",
512 storage_options=_shared_docs["storage_options"],
513 storage_options_versionadded="1.5.0",
514 )
515 def to_excel(
516 self,
517 excel_writer: FilePath | WriteExcelBuffer | ExcelWriter,
518 sheet_name: str = "Sheet1",
519 na_rep: str = "",
520 float_format: str | None = None,
521 columns: Sequence[Hashable] | None = None,
522 header: Sequence[Hashable] | bool = True,
523 index: bool = True,
524 index_label: IndexLabel | None = None,
525 startrow: int = 0,
526 startcol: int = 0,
527 engine: str | None = None,
528 merge_cells: bool = True,
529 encoding: str | None = None,
530 inf_rep: str = "inf",
531 verbose: bool = True,
532 freeze_panes: tuple[int, int] | None = None,
533 storage_options: StorageOptions | None = None,
534 ) -> None:
535 from pandas.io.formats.excel import ExcelFormatter
536
537 formatter = ExcelFormatter(
538 self,
539 na_rep=na_rep,
540 cols=columns,
541 header=header,
542 float_format=float_format,
543 index=index,
544 index_label=index_label,
545 merge_cells=merge_cells,
546 inf_rep=inf_rep,
547 )
548 formatter.write(
549 excel_writer,
550 sheet_name=sheet_name,
551 startrow=startrow,
552 startcol=startcol,
553 freeze_panes=freeze_panes,
554 engine=engine,
555 storage_options=storage_options,
556 )
557
558 @overload
559 def to_latex(
560 self,
561 buf: FilePath | WriteBuffer[str],
562 *,
563 column_format: str | None = ...,
564 position: str | None = ...,
565 position_float: str | None = ...,
566 hrules: bool | None = ...,
567 clines: str | None = ...,
568 label: str | None = ...,
569 caption: str | tuple | None = ...,
570 sparse_index: bool | None = ...,
571 sparse_columns: bool | None = ...,
572 multirow_align: str | None = ...,
573 multicol_align: str | None = ...,
574 siunitx: bool = ...,
575 environment: str | None = ...,
576 encoding: str | None = ...,
577 convert_css: bool = ...,
578 ) -> None:
579 ...
580
581 @overload
582 def to_latex(
583 self,
584 buf: None = ...,
585 *,
586 column_format: str | None = ...,
587 position: str | None = ...,
588 position_float: str | None = ...,
589 hrules: bool | None = ...,
590 clines: str | None = ...,
591 label: str | None = ...,
592 caption: str | tuple | None = ...,
593 sparse_index: bool | None = ...,
594 sparse_columns: bool | None = ...,
595 multirow_align: str | None = ...,
596 multicol_align: str | None = ...,
597 siunitx: bool = ...,
598 environment: str | None = ...,
599 encoding: str | None = ...,
600 convert_css: bool = ...,
601 ) -> str:
602 ...
603
604 def to_latex(
605 self,
606 buf: FilePath | WriteBuffer[str] | None = None,
607 *,
608 column_format: str | None = None,
609 position: str | None = None,
610 position_float: str | None = None,
611 hrules: bool | None = None,
612 clines: str | None = None,
613 label: str | None = None,
614 caption: str | tuple | None = None,
615 sparse_index: bool | None = None,
616 sparse_columns: bool | None = None,
617 multirow_align: str | None = None,
618 multicol_align: str | None = None,
619 siunitx: bool = False,
620 environment: str | None = None,
621 encoding: str | None = None,
622 convert_css: bool = False,
623 ) -> str | None:
624 r"""
625 Write Styler to a file, buffer or string in LaTeX format.
626
627 .. versionadded:: 1.3.0
628
629 Parameters
630 ----------
631 buf : str, path object, file-like object, or None, default None
632 String, path object (implementing ``os.PathLike[str]``), or file-like
633 object implementing a string ``write()`` function. If None, the result is
634 returned as a string.
635 column_format : str, optional
636 The LaTeX column specification placed in location:
637
638 \\begin{tabular}{<column_format>}
639
640 Defaults to 'l' for index and
641 non-numeric data columns, and, for numeric data columns,
642 to 'r' by default, or 'S' if ``siunitx`` is ``True``.
643 position : str, optional
644 The LaTeX positional argument (e.g. 'h!') for tables, placed in location:
645
646 ``\\begin{table}[<position>]``.
647 position_float : {"centering", "raggedleft", "raggedright"}, optional
648 The LaTeX float command placed in location:
649
650 \\begin{table}[<position>]
651
652 \\<position_float>
653
654 Cannot be used if ``environment`` is "longtable".
655 hrules : bool
656 Set to `True` to add \\toprule, \\midrule and \\bottomrule from the
657 {booktabs} LaTeX package.
658 Defaults to ``pandas.options.styler.latex.hrules``, which is `False`.
659
660 .. versionchanged:: 1.4.0
661 clines : str, optional
662 Use to control adding \\cline commands for the index labels separation.
663 Possible values are:
664
665 - `None`: no cline commands are added (default).
666 - `"all;data"`: a cline is added for every index value extending the
667 width of the table, including data entries.
668 - `"all;index"`: as above with lines extending only the width of the
669 index entries.
670 - `"skip-last;data"`: a cline is added for each index value except the
671 last level (which is never sparsified), extending the widtn of the
672 table.
673 - `"skip-last;index"`: as above with lines extending only the width of the
674 index entries.
675
676 .. versionadded:: 1.4.0
677 label : str, optional
678 The LaTeX label included as: \\label{<label>}.
679 This is used with \\ref{<label>} in the main .tex file.
680 caption : str, tuple, optional
681 If string, the LaTeX table caption included as: \\caption{<caption>}.
682 If tuple, i.e ("full caption", "short caption"), the caption included
683 as: \\caption[<caption[1]>]{<caption[0]>}.
684 sparse_index : bool, optional
685 Whether to sparsify the display of a hierarchical index. Setting to False
686 will display each explicit level element in a hierarchical key for each row.
687 Defaults to ``pandas.options.styler.sparse.index``, which is `True`.
688 sparse_columns : bool, optional
689 Whether to sparsify the display of a hierarchical index. Setting to False
690 will display each explicit level element in a hierarchical key for each
691 column. Defaults to ``pandas.options.styler.sparse.columns``, which
692 is `True`.
693 multirow_align : {"c", "t", "b", "naive"}, optional
694 If sparsifying hierarchical MultiIndexes whether to align text centrally,
695 at the top or bottom using the multirow package. If not given defaults to
696 ``pandas.options.styler.latex.multirow_align``, which is `"c"`.
697 If "naive" is given renders without multirow.
698
699 .. versionchanged:: 1.4.0
700 multicol_align : {"r", "c", "l", "naive-l", "naive-r"}, optional
701 If sparsifying hierarchical MultiIndex columns whether to align text at
702 the left, centrally, or at the right. If not given defaults to
703 ``pandas.options.styler.latex.multicol_align``, which is "r".
704 If a naive option is given renders without multicol.
705 Pipe decorators can also be added to non-naive values to draw vertical
706 rules, e.g. "\|r" will draw a rule on the left side of right aligned merged
707 cells.
708
709 .. versionchanged:: 1.4.0
710 siunitx : bool, default False
711 Set to ``True`` to structure LaTeX compatible with the {siunitx} package.
712 environment : str, optional
713 If given, the environment that will replace 'table' in ``\\begin{table}``.
714 If 'longtable' is specified then a more suitable template is
715 rendered. If not given defaults to
716 ``pandas.options.styler.latex.environment``, which is `None`.
717
718 .. versionadded:: 1.4.0
719 encoding : str, optional
720 Character encoding setting. Defaults
721 to ``pandas.options.styler.render.encoding``, which is "utf-8".
722 convert_css : bool, default False
723 Convert simple cell-styles from CSS to LaTeX format. Any CSS not found in
724 conversion table is dropped. A style can be forced by adding option
725 `--latex`. See notes.
726
727 Returns
728 -------
729 str or None
730 If `buf` is None, returns the result as a string. Otherwise returns `None`.
731
732 See Also
733 --------
734 Styler.format: Format the text display value of cells.
735
736 Notes
737 -----
738 **Latex Packages**
739
740 For the following features we recommend the following LaTeX inclusions:
741
742 ===================== ==========================================================
743 Feature Inclusion
744 ===================== ==========================================================
745 sparse columns none: included within default {tabular} environment
746 sparse rows \\usepackage{multirow}
747 hrules \\usepackage{booktabs}
748 colors \\usepackage[table]{xcolor}
749 siunitx \\usepackage{siunitx}
750 bold (with siunitx) | \\usepackage{etoolbox}
751 | \\robustify\\bfseries
752 | \\sisetup{detect-all = true} *(within {document})*
753 italic (with siunitx) | \\usepackage{etoolbox}
754 | \\robustify\\itshape
755 | \\sisetup{detect-all = true} *(within {document})*
756 environment \\usepackage{longtable} if arg is "longtable"
757 | or any other relevant environment package
758 hyperlinks \\usepackage{hyperref}
759 ===================== ==========================================================
760
761 **Cell Styles**
762
763 LaTeX styling can only be rendered if the accompanying styling functions have
764 been constructed with appropriate LaTeX commands. All styling
765 functionality is built around the concept of a CSS ``(<attribute>, <value>)``
766 pair (see `Table Visualization <../../user_guide/style.ipynb>`_), and this
767 should be replaced by a LaTeX
768 ``(<command>, <options>)`` approach. Each cell will be styled individually
769 using nested LaTeX commands with their accompanied options.
770
771 For example the following code will highlight and bold a cell in HTML-CSS:
772
773 >>> df = pd.DataFrame([[1,2], [3,4]])
774 >>> s = df.style.highlight_max(axis=None,
775 ... props='background-color:red; font-weight:bold;')
776 >>> s.to_html() # doctest: +SKIP
777
778 The equivalent using LaTeX only commands is the following:
779
780 >>> s = df.style.highlight_max(axis=None,
781 ... props='cellcolor:{red}; bfseries: ;')
782 >>> s.to_latex() # doctest: +SKIP
783
784 Internally these structured LaTeX ``(<command>, <options>)`` pairs
785 are translated to the
786 ``display_value`` with the default structure:
787 ``\<command><options> <display_value>``.
788 Where there are multiple commands the latter is nested recursively, so that
789 the above example highlighted cell is rendered as
790 ``\cellcolor{red} \bfseries 4``.
791
792 Occasionally this format does not suit the applied command, or
793 combination of LaTeX packages that is in use, so additional flags can be
794 added to the ``<options>``, within the tuple, to result in different
795 positions of required braces (the **default** being the same as ``--nowrap``):
796
797 =================================== ============================================
798 Tuple Format Output Structure
799 =================================== ============================================
800 (<command>,<options>) \\<command><options> <display_value>
801 (<command>,<options> ``--nowrap``) \\<command><options> <display_value>
802 (<command>,<options> ``--rwrap``) \\<command><options>{<display_value>}
803 (<command>,<options> ``--wrap``) {\\<command><options> <display_value>}
804 (<command>,<options> ``--lwrap``) {\\<command><options>} <display_value>
805 (<command>,<options> ``--dwrap``) {\\<command><options>}{<display_value>}
806 =================================== ============================================
807
808 For example the `textbf` command for font-weight
809 should always be used with `--rwrap` so ``('textbf', '--rwrap')`` will render a
810 working cell, wrapped with braces, as ``\textbf{<display_value>}``.
811
812 A more comprehensive example is as follows:
813
814 >>> df = pd.DataFrame([[1, 2.2, "dogs"], [3, 4.4, "cats"], [2, 6.6, "cows"]],
815 ... index=["ix1", "ix2", "ix3"],
816 ... columns=["Integers", "Floats", "Strings"])
817 >>> s = df.style.highlight_max(
818 ... props='cellcolor:[HTML]{FFFF00}; color:{red};'
819 ... 'textit:--rwrap; textbf:--rwrap;'
820 ... )
821 >>> s.to_latex() # doctest: +SKIP
822
823 .. figure:: ../../_static/style/latex_1.png
824
825 **Table Styles**
826
827 Internally Styler uses its ``table_styles`` object to parse the
828 ``column_format``, ``position``, ``position_float``, and ``label``
829 input arguments. These arguments are added to table styles in the format:
830
831 .. code-block:: python
832
833 set_table_styles([
834 {"selector": "column_format", "props": f":{column_format};"},
835 {"selector": "position", "props": f":{position};"},
836 {"selector": "position_float", "props": f":{position_float};"},
837 {"selector": "label", "props": f":{{{label.replace(':','§')}}};"}
838 ], overwrite=False)
839
840 Exception is made for the ``hrules`` argument which, in fact, controls all three
841 commands: ``toprule``, ``bottomrule`` and ``midrule`` simultaneously. Instead of
842 setting ``hrules`` to ``True``, it is also possible to set each
843 individual rule definition, by manually setting the ``table_styles``,
844 for example below we set a regular ``toprule``, set an ``hline`` for
845 ``bottomrule`` and exclude the ``midrule``:
846
847 .. code-block:: python
848
849 set_table_styles([
850 {'selector': 'toprule', 'props': ':toprule;'},
851 {'selector': 'bottomrule', 'props': ':hline;'},
852 ], overwrite=False)
853
854 If other ``commands`` are added to table styles they will be detected, and
855 positioned immediately above the '\\begin{tabular}' command. For example to
856 add odd and even row coloring, from the {colortbl} package, in format
857 ``\rowcolors{1}{pink}{red}``, use:
858
859 .. code-block:: python
860
861 set_table_styles([
862 {'selector': 'rowcolors', 'props': ':{1}{pink}{red};'}
863 ], overwrite=False)
864
865 A more comprehensive example using these arguments is as follows:
866
867 >>> df.columns = pd.MultiIndex.from_tuples([
868 ... ("Numeric", "Integers"),
869 ... ("Numeric", "Floats"),
870 ... ("Non-Numeric", "Strings")
871 ... ])
872 >>> df.index = pd.MultiIndex.from_tuples([
873 ... ("L0", "ix1"), ("L0", "ix2"), ("L1", "ix3")
874 ... ])
875 >>> s = df.style.highlight_max(
876 ... props='cellcolor:[HTML]{FFFF00}; color:{red}; itshape:; bfseries:;'
877 ... )
878 >>> s.to_latex(
879 ... column_format="rrrrr", position="h", position_float="centering",
880 ... hrules=True, label="table:5", caption="Styled LaTeX Table",
881 ... multirow_align="t", multicol_align="r"
882 ... ) # doctest: +SKIP
883
884 .. figure:: ../../_static/style/latex_2.png
885
886 **Formatting**
887
888 To format values :meth:`Styler.format` should be used prior to calling
889 `Styler.to_latex`, as well as other methods such as :meth:`Styler.hide`
890 for example:
891
892 >>> s.clear()
893 >>> s.table_styles = []
894 >>> s.caption = None
895 >>> s.format({
896 ... ("Numeric", "Integers"): '\${}',
897 ... ("Numeric", "Floats"): '{:.3f}',
898 ... ("Non-Numeric", "Strings"): str.upper
899 ... }) # doctest: +SKIP
900 Numeric Non-Numeric
901 Integers Floats Strings
902 L0 ix1 $1 2.200 DOGS
903 ix2 $3 4.400 CATS
904 L1 ix3 $2 6.600 COWS
905
906 >>> s.to_latex() # doctest: +SKIP
907 \begin{tabular}{llrrl}
908 {} & {} & \multicolumn{2}{r}{Numeric} & {Non-Numeric} \\
909 {} & {} & {Integers} & {Floats} & {Strings} \\
910 \multirow[c]{2}{*}{L0} & ix1 & \\$1 & 2.200 & DOGS \\
911 & ix2 & \$3 & 4.400 & CATS \\
912 L1 & ix3 & \$2 & 6.600 & COWS \\
913 \end{tabular}
914
915 **CSS Conversion**
916
917 This method can convert a Styler constructured with HTML-CSS to LaTeX using
918 the following limited conversions.
919
920 ================== ==================== ============= ==========================
921 CSS Attribute CSS value LaTeX Command LaTeX Options
922 ================== ==================== ============= ==========================
923 font-weight | bold | bfseries
924 | bolder | bfseries
925 font-style | italic | itshape
926 | oblique | slshape
927 background-color | red cellcolor | {red}--lwrap
928 | #fe01ea | [HTML]{FE01EA}--lwrap
929 | #f0e | [HTML]{FF00EE}--lwrap
930 | rgb(128,255,0) | [rgb]{0.5,1,0}--lwrap
931 | rgba(128,0,0,0.5) | [rgb]{0.5,0,0}--lwrap
932 | rgb(25%,255,50%) | [rgb]{0.25,1,0.5}--lwrap
933 color | red color | {red}
934 | #fe01ea | [HTML]{FE01EA}
935 | #f0e | [HTML]{FF00EE}
936 | rgb(128,255,0) | [rgb]{0.5,1,0}
937 | rgba(128,0,0,0.5) | [rgb]{0.5,0,0}
938 | rgb(25%,255,50%) | [rgb]{0.25,1,0.5}
939 ================== ==================== ============= ==========================
940
941 It is also possible to add user-defined LaTeX only styles to a HTML-CSS Styler
942 using the ``--latex`` flag, and to add LaTeX parsing options that the
943 converter will detect within a CSS-comment.
944
945 >>> df = pd.DataFrame([[1]])
946 >>> df.style.set_properties(
947 ... **{"font-weight": "bold /* --dwrap */", "Huge": "--latex--rwrap"}
948 ... ).to_latex(convert_css=True) # doctest: +SKIP
949 \begin{tabular}{lr}
950 {} & {0} \\
951 0 & {\bfseries}{\Huge{1}} \\
952 \end{tabular}
953
954 Examples
955 --------
956 Below we give a complete step by step example adding some advanced features
957 and noting some common gotchas.
958
959 First we create the DataFrame and Styler as usual, including MultiIndex rows
960 and columns, which allow for more advanced formatting options:
961
962 >>> cidx = pd.MultiIndex.from_arrays([
963 ... ["Equity", "Equity", "Equity", "Equity",
964 ... "Stats", "Stats", "Stats", "Stats", "Rating"],
965 ... ["Energy", "Energy", "Consumer", "Consumer", "", "", "", "", ""],
966 ... ["BP", "Shell", "H&M", "Unilever",
967 ... "Std Dev", "Variance", "52w High", "52w Low", ""]
968 ... ])
969 >>> iidx = pd.MultiIndex.from_arrays([
970 ... ["Equity", "Equity", "Equity", "Equity"],
971 ... ["Energy", "Energy", "Consumer", "Consumer"],
972 ... ["BP", "Shell", "H&M", "Unilever"]
973 ... ])
974 >>> styler = pd.DataFrame([
975 ... [1, 0.8, 0.66, 0.72, 32.1678, 32.1678**2, 335.12, 240.89, "Buy"],
976 ... [0.8, 1.0, 0.69, 0.79, 1.876, 1.876**2, 14.12, 19.78, "Hold"],
977 ... [0.66, 0.69, 1.0, 0.86, 7, 7**2, 210.9, 140.6, "Buy"],
978 ... [0.72, 0.79, 0.86, 1.0, 213.76, 213.76**2, 2807, 3678, "Sell"],
979 ... ], columns=cidx, index=iidx).style
980
981 Second we will format the display and, since our table is quite wide, will
982 hide the repeated level-0 of the index:
983
984 >>> (styler.format(subset="Equity", precision=2)
985 ... .format(subset="Stats", precision=1, thousands=",")
986 ... .format(subset="Rating", formatter=str.upper)
987 ... .format_index(escape="latex", axis=1)
988 ... .format_index(escape="latex", axis=0)
989 ... .hide(level=0, axis=0)) # doctest: +SKIP
990
991 Note that one of the string entries of the index and column headers is "H&M".
992 Without applying the `escape="latex"` option to the `format_index` method the
993 resultant LaTeX will fail to render, and the error returned is quite
994 difficult to debug. Using the appropriate escape the "&" is converted to "\\&".
995
996 Thirdly we will apply some (CSS-HTML) styles to our object. We will use a
997 builtin method and also define our own method to highlight the stock
998 recommendation:
999
1000 >>> def rating_color(v):
1001 ... if v == "Buy": color = "#33ff85"
1002 ... elif v == "Sell": color = "#ff5933"
1003 ... else: color = "#ffdd33"
1004 ... return f"color: {color}; font-weight: bold;"
1005 >>> (styler.background_gradient(cmap="inferno", subset="Equity", vmin=0, vmax=1)
1006 ... .map(rating_color, subset="Rating")) # doctest: +SKIP
1007
1008 All the above styles will work with HTML (see below) and LaTeX upon conversion:
1009
1010 .. figure:: ../../_static/style/latex_stocks_html.png
1011
1012 However, we finally want to add one LaTeX only style
1013 (from the {graphicx} package), that is not easy to convert from CSS and
1014 pandas does not support it. Notice the `--latex` flag used here,
1015 as well as `--rwrap` to ensure this is formatted correctly and
1016 not ignored upon conversion.
1017
1018 >>> styler.map_index(
1019 ... lambda v: "rotatebox:{45}--rwrap--latex;", level=2, axis=1
1020 ... ) # doctest: +SKIP
1021
1022 Finally we render our LaTeX adding in other options as required:
1023
1024 >>> styler.to_latex(
1025 ... caption="Selected stock correlation and simple statistics.",
1026 ... clines="skip-last;data",
1027 ... convert_css=True,
1028 ... position_float="centering",
1029 ... multicol_align="|c|",
1030 ... hrules=True,
1031 ... ) # doctest: +SKIP
1032 \begin{table}
1033 \centering
1034 \caption{Selected stock correlation and simple statistics.}
1035 \begin{tabular}{llrrrrrrrrl}
1036 \toprule
1037 & & \multicolumn{4}{|c|}{Equity} & \multicolumn{4}{|c|}{Stats} & Rating \\
1038 & & \multicolumn{2}{|c|}{Energy} & \multicolumn{2}{|c|}{Consumer} &
1039 \multicolumn{4}{|c|}{} & \\
1040 & & \rotatebox{45}{BP} & \rotatebox{45}{Shell} & \rotatebox{45}{H\&M} &
1041 \rotatebox{45}{Unilever} & \rotatebox{45}{Std Dev} & \rotatebox{45}{Variance} &
1042 \rotatebox{45}{52w High} & \rotatebox{45}{52w Low} & \rotatebox{45}{} \\
1043 \midrule
1044 \multirow[c]{2}{*}{Energy} & BP & {\cellcolor[HTML]{FCFFA4}}
1045 \color[HTML]{000000} 1.00 & {\cellcolor[HTML]{FCA50A}} \color[HTML]{000000}
1046 0.80 & {\cellcolor[HTML]{EB6628}} \color[HTML]{F1F1F1} 0.66 &
1047 {\cellcolor[HTML]{F68013}} \color[HTML]{F1F1F1} 0.72 & 32.2 & 1,034.8 & 335.1
1048 & 240.9 & \color[HTML]{33FF85} \bfseries BUY \\
1049 & Shell & {\cellcolor[HTML]{FCA50A}} \color[HTML]{000000} 0.80 &
1050 {\cellcolor[HTML]{FCFFA4}} \color[HTML]{000000} 1.00 &
1051 {\cellcolor[HTML]{F1731D}} \color[HTML]{F1F1F1} 0.69 &
1052 {\cellcolor[HTML]{FCA108}} \color[HTML]{000000} 0.79 & 1.9 & 3.5 & 14.1 &
1053 19.8 & \color[HTML]{FFDD33} \bfseries HOLD \\
1054 \cline{1-11}
1055 \multirow[c]{2}{*}{Consumer} & H\&M & {\cellcolor[HTML]{EB6628}}
1056 \color[HTML]{F1F1F1} 0.66 & {\cellcolor[HTML]{F1731D}} \color[HTML]{F1F1F1}
1057 0.69 & {\cellcolor[HTML]{FCFFA4}} \color[HTML]{000000} 1.00 &
1058 {\cellcolor[HTML]{FAC42A}} \color[HTML]{000000} 0.86 & 7.0 & 49.0 & 210.9 &
1059 140.6 & \color[HTML]{33FF85} \bfseries BUY \\
1060 & Unilever & {\cellcolor[HTML]{F68013}} \color[HTML]{F1F1F1} 0.72 &
1061 {\cellcolor[HTML]{FCA108}} \color[HTML]{000000} 0.79 &
1062 {\cellcolor[HTML]{FAC42A}} \color[HTML]{000000} 0.86 &
1063 {\cellcolor[HTML]{FCFFA4}} \color[HTML]{000000} 1.00 & 213.8 & 45,693.3 &
1064 2,807.0 & 3,678.0 & \color[HTML]{FF5933} \bfseries SELL \\
1065 \cline{1-11}
1066 \bottomrule
1067 \end{tabular}
1068 \end{table}
1069
1070 .. figure:: ../../_static/style/latex_stocks.png
1071 """
1072 obj = self._copy(deepcopy=True) # manipulate table_styles on obj, not self
1073
1074 table_selectors = (
1075 [style["selector"] for style in self.table_styles]
1076 if self.table_styles is not None
1077 else []
1078 )
1079
1080 if column_format is not None:
1081 # add more recent setting to table_styles
1082 obj.set_table_styles(
1083 [{"selector": "column_format", "props": f":{column_format}"}],
1084 overwrite=False,
1085 )
1086 elif "column_format" in table_selectors:
1087 pass # adopt what has been previously set in table_styles
1088 else:
1089 # create a default: set float, complex, int cols to 'r' ('S'), index to 'l'
1090 _original_columns = self.data.columns
1091 self.data.columns = RangeIndex(stop=len(self.data.columns))
1092 numeric_cols = self.data._get_numeric_data().columns.to_list()
1093 self.data.columns = _original_columns
1094 column_format = ""
1095 for level in range(self.index.nlevels):
1096 column_format += "" if self.hide_index_[level] else "l"
1097 for ci, _ in enumerate(self.data.columns):
1098 if ci not in self.hidden_columns:
1099 column_format += (
1100 ("r" if not siunitx else "S") if ci in numeric_cols else "l"
1101 )
1102 obj.set_table_styles(
1103 [{"selector": "column_format", "props": f":{column_format}"}],
1104 overwrite=False,
1105 )
1106
1107 if position:
1108 obj.set_table_styles(
1109 [{"selector": "position", "props": f":{position}"}],
1110 overwrite=False,
1111 )
1112
1113 if position_float:
1114 if environment == "longtable":
1115 raise ValueError(
1116 "`position_float` cannot be used in 'longtable' `environment`"
1117 )
1118 if position_float not in ["raggedright", "raggedleft", "centering"]:
1119 raise ValueError(
1120 f"`position_float` should be one of "
1121 f"'raggedright', 'raggedleft', 'centering', "
1122 f"got: '{position_float}'"
1123 )
1124 obj.set_table_styles(
1125 [{"selector": "position_float", "props": f":{position_float}"}],
1126 overwrite=False,
1127 )
1128
1129 hrules = get_option("styler.latex.hrules") if hrules is None else hrules
1130 if hrules:
1131 obj.set_table_styles(
1132 [
1133 {"selector": "toprule", "props": ":toprule"},
1134 {"selector": "midrule", "props": ":midrule"},
1135 {"selector": "bottomrule", "props": ":bottomrule"},
1136 ],
1137 overwrite=False,
1138 )
1139
1140 if label:
1141 obj.set_table_styles(
1142 [{"selector": "label", "props": f":{{{label.replace(':', '§')}}}"}],
1143 overwrite=False,
1144 )
1145
1146 if caption:
1147 obj.set_caption(caption)
1148
1149 if sparse_index is None:
1150 sparse_index = get_option("styler.sparse.index")
1151 if sparse_columns is None:
1152 sparse_columns = get_option("styler.sparse.columns")
1153 environment = environment or get_option("styler.latex.environment")
1154 multicol_align = multicol_align or get_option("styler.latex.multicol_align")
1155 multirow_align = multirow_align or get_option("styler.latex.multirow_align")
1156 latex = obj._render_latex(
1157 sparse_index=sparse_index,
1158 sparse_columns=sparse_columns,
1159 multirow_align=multirow_align,
1160 multicol_align=multicol_align,
1161 environment=environment,
1162 convert_css=convert_css,
1163 siunitx=siunitx,
1164 clines=clines,
1165 )
1166
1167 encoding = (
1168 (encoding or get_option("styler.render.encoding"))
1169 if isinstance(buf, str) # i.e. a filepath
1170 else encoding
1171 )
1172 return save_to_buffer(latex, buf=buf, encoding=encoding)
1173
1174 @overload
1175 def to_html(
1176 self,
1177 buf: FilePath | WriteBuffer[str],
1178 *,
1179 table_uuid: str | None = ...,
1180 table_attributes: str | None = ...,
1181 sparse_index: bool | None = ...,
1182 sparse_columns: bool | None = ...,
1183 bold_headers: bool = ...,
1184 caption: str | None = ...,
1185 max_rows: int | None = ...,
1186 max_columns: int | None = ...,
1187 encoding: str | None = ...,
1188 doctype_html: bool = ...,
1189 exclude_styles: bool = ...,
1190 **kwargs,
1191 ) -> None:
1192 ...
1193
1194 @overload
1195 def to_html(
1196 self,
1197 buf: None = ...,
1198 *,
1199 table_uuid: str | None = ...,
1200 table_attributes: str | None = ...,
1201 sparse_index: bool | None = ...,
1202 sparse_columns: bool | None = ...,
1203 bold_headers: bool = ...,
1204 caption: str | None = ...,
1205 max_rows: int | None = ...,
1206 max_columns: int | None = ...,
1207 encoding: str | None = ...,
1208 doctype_html: bool = ...,
1209 exclude_styles: bool = ...,
1210 **kwargs,
1211 ) -> str:
1212 ...
1213
1214 @Substitution(buf=buffering_args, encoding=encoding_args)
1215 def to_html(
1216 self,
1217 buf: FilePath | WriteBuffer[str] | None = None,
1218 *,
1219 table_uuid: str | None = None,
1220 table_attributes: str | None = None,
1221 sparse_index: bool | None = None,
1222 sparse_columns: bool | None = None,
1223 bold_headers: bool = False,
1224 caption: str | None = None,
1225 max_rows: int | None = None,
1226 max_columns: int | None = None,
1227 encoding: str | None = None,
1228 doctype_html: bool = False,
1229 exclude_styles: bool = False,
1230 **kwargs,
1231 ) -> str | None:
1232 """
1233 Write Styler to a file, buffer or string in HTML-CSS format.
1234
1235 .. versionadded:: 1.3.0
1236
1237 Parameters
1238 ----------
1239 %(buf)s
1240 table_uuid : str, optional
1241 Id attribute assigned to the <table> HTML element in the format:
1242
1243 ``<table id="T_<table_uuid>" ..>``
1244
1245 If not given uses Styler's initially assigned value.
1246 table_attributes : str, optional
1247 Attributes to assign within the `<table>` HTML element in the format:
1248
1249 ``<table .. <table_attributes> >``
1250
1251 If not given defaults to Styler's preexisting value.
1252 sparse_index : bool, optional
1253 Whether to sparsify the display of a hierarchical index. Setting to False
1254 will display each explicit level element in a hierarchical key for each row.
1255 Defaults to ``pandas.options.styler.sparse.index`` value.
1256
1257 .. versionadded:: 1.4.0
1258 sparse_columns : bool, optional
1259 Whether to sparsify the display of a hierarchical index. Setting to False
1260 will display each explicit level element in a hierarchical key for each
1261 column. Defaults to ``pandas.options.styler.sparse.columns`` value.
1262
1263 .. versionadded:: 1.4.0
1264 bold_headers : bool, optional
1265 Adds "font-weight: bold;" as a CSS property to table style header cells.
1266
1267 .. versionadded:: 1.4.0
1268 caption : str, optional
1269 Set, or overwrite, the caption on Styler before rendering.
1270
1271 .. versionadded:: 1.4.0
1272 max_rows : int, optional
1273 The maximum number of rows that will be rendered. Defaults to
1274 ``pandas.options.styler.render.max_rows/max_columns``.
1275
1276 .. versionadded:: 1.4.0
1277 max_columns : int, optional
1278 The maximum number of columns that will be rendered. Defaults to
1279 ``pandas.options.styler.render.max_columns``, which is None.
1280
1281 Rows and columns may be reduced if the number of total elements is
1282 large. This value is set to ``pandas.options.styler.render.max_elements``,
1283 which is 262144 (18 bit browser rendering).
1284
1285 .. versionadded:: 1.4.0
1286 %(encoding)s
1287 doctype_html : bool, default False
1288 Whether to output a fully structured HTML file including all
1289 HTML elements, or just the core ``<style>`` and ``<table>`` elements.
1290 exclude_styles : bool, default False
1291 Whether to include the ``<style>`` element and all associated element
1292 ``class`` and ``id`` identifiers, or solely the ``<table>`` element without
1293 styling identifiers.
1294 **kwargs
1295 Any additional keyword arguments are passed through to the jinja2
1296 ``self.template.render`` process. This is useful when you need to provide
1297 additional variables for a custom template.
1298
1299 Returns
1300 -------
1301 str or None
1302 If `buf` is None, returns the result as a string. Otherwise returns `None`.
1303
1304 See Also
1305 --------
1306 DataFrame.to_html: Write a DataFrame to a file, buffer or string in HTML format.
1307
1308 Examples
1309 --------
1310 >>> df = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
1311 >>> print(df.style.to_html()) # doctest: +SKIP
1312 <style type="text/css">
1313 </style>
1314 <table id="T_1e78e">
1315 <thead>
1316 <tr>
1317 <th class="blank level0" > </th>
1318 <th id="T_1e78e_level0_col0" class="col_heading level0 col0" >A</th>
1319 <th id="T_1e78e_level0_col1" class="col_heading level0 col1" >B</th>
1320 </tr>
1321 ...
1322 """
1323 obj = self._copy(deepcopy=True) # manipulate table_styles on obj, not self
1324
1325 if table_uuid:
1326 obj.set_uuid(table_uuid)
1327
1328 if table_attributes:
1329 obj.set_table_attributes(table_attributes)
1330
1331 if sparse_index is None:
1332 sparse_index = get_option("styler.sparse.index")
1333 if sparse_columns is None:
1334 sparse_columns = get_option("styler.sparse.columns")
1335
1336 if bold_headers:
1337 obj.set_table_styles(
1338 [{"selector": "th", "props": "font-weight: bold;"}], overwrite=False
1339 )
1340
1341 if caption is not None:
1342 obj.set_caption(caption)
1343
1344 # Build HTML string..
1345 html = obj._render_html(
1346 sparse_index=sparse_index,
1347 sparse_columns=sparse_columns,
1348 max_rows=max_rows,
1349 max_cols=max_columns,
1350 exclude_styles=exclude_styles,
1351 encoding=encoding or get_option("styler.render.encoding"),
1352 doctype_html=doctype_html,
1353 **kwargs,
1354 )
1355
1356 return save_to_buffer(
1357 html, buf=buf, encoding=(encoding if buf is not None else None)
1358 )
1359
1360 @overload
1361 def to_string(
1362 self,
1363 buf: FilePath | WriteBuffer[str],
1364 *,
1365 encoding: str | None = ...,
1366 sparse_index: bool | None = ...,
1367 sparse_columns: bool | None = ...,
1368 max_rows: int | None = ...,
1369 max_columns: int | None = ...,
1370 delimiter: str = ...,
1371 ) -> None:
1372 ...
1373
1374 @overload
1375 def to_string(
1376 self,
1377 buf: None = ...,
1378 *,
1379 encoding: str | None = ...,
1380 sparse_index: bool | None = ...,
1381 sparse_columns: bool | None = ...,
1382 max_rows: int | None = ...,
1383 max_columns: int | None = ...,
1384 delimiter: str = ...,
1385 ) -> str:
1386 ...
1387
1388 @Substitution(buf=buffering_args, encoding=encoding_args)
1389 def to_string(
1390 self,
1391 buf: FilePath | WriteBuffer[str] | None = None,
1392 *,
1393 encoding: str | None = None,
1394 sparse_index: bool | None = None,
1395 sparse_columns: bool | None = None,
1396 max_rows: int | None = None,
1397 max_columns: int | None = None,
1398 delimiter: str = " ",
1399 ) -> str | None:
1400 """
1401 Write Styler to a file, buffer or string in text format.
1402
1403 .. versionadded:: 1.5.0
1404
1405 Parameters
1406 ----------
1407 %(buf)s
1408 %(encoding)s
1409 sparse_index : bool, optional
1410 Whether to sparsify the display of a hierarchical index. Setting to False
1411 will display each explicit level element in a hierarchical key for each row.
1412 Defaults to ``pandas.options.styler.sparse.index`` value.
1413 sparse_columns : bool, optional
1414 Whether to sparsify the display of a hierarchical index. Setting to False
1415 will display each explicit level element in a hierarchical key for each
1416 column. Defaults to ``pandas.options.styler.sparse.columns`` value.
1417 max_rows : int, optional
1418 The maximum number of rows that will be rendered. Defaults to
1419 ``pandas.options.styler.render.max_rows``, which is None.
1420 max_columns : int, optional
1421 The maximum number of columns that will be rendered. Defaults to
1422 ``pandas.options.styler.render.max_columns``, which is None.
1423
1424 Rows and columns may be reduced if the number of total elements is
1425 large. This value is set to ``pandas.options.styler.render.max_elements``,
1426 which is 262144 (18 bit browser rendering).
1427 delimiter : str, default single space
1428 The separator between data elements.
1429
1430 Returns
1431 -------
1432 str or None
1433 If `buf` is None, returns the result as a string. Otherwise returns `None`.
1434
1435 Examples
1436 --------
1437 >>> df = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
1438 >>> df.style.to_string()
1439 ' A B\\n0 1 3\\n1 2 4\\n'
1440 """
1441 obj = self._copy(deepcopy=True)
1442
1443 if sparse_index is None:
1444 sparse_index = get_option("styler.sparse.index")
1445 if sparse_columns is None:
1446 sparse_columns = get_option("styler.sparse.columns")
1447
1448 text = obj._render_string(
1449 sparse_columns=sparse_columns,
1450 sparse_index=sparse_index,
1451 max_rows=max_rows,
1452 max_cols=max_columns,
1453 delimiter=delimiter,
1454 )
1455 return save_to_buffer(
1456 text, buf=buf, encoding=(encoding if buf is not None else None)
1457 )
1458
1459 def set_td_classes(self, classes: DataFrame) -> Styler:
1460 """
1461 Set the ``class`` attribute of ``<td>`` HTML elements.
1462
1463 Parameters
1464 ----------
1465 classes : DataFrame
1466 DataFrame containing strings that will be translated to CSS classes,
1467 mapped by identical column and index key values that must exist on the
1468 underlying Styler data. None, NaN values, and empty strings will
1469 be ignored and not affect the rendered HTML.
1470
1471 Returns
1472 -------
1473 Styler
1474
1475 See Also
1476 --------
1477 Styler.set_table_styles: Set the table styles included within the ``<style>``
1478 HTML element.
1479 Styler.set_table_attributes: Set the table attributes added to the ``<table>``
1480 HTML element.
1481
1482 Notes
1483 -----
1484 Can be used in combination with ``Styler.set_table_styles`` to define an
1485 internal CSS solution without reference to external CSS files.
1486
1487 Examples
1488 --------
1489 >>> df = pd.DataFrame(data=[[1, 2, 3], [4, 5, 6]], columns=["A", "B", "C"])
1490 >>> classes = pd.DataFrame([
1491 ... ["min-val red", "", "blue"],
1492 ... ["red", None, "blue max-val"]
1493 ... ], index=df.index, columns=df.columns)
1494 >>> df.style.set_td_classes(classes) # doctest: +SKIP
1495
1496 Using `MultiIndex` columns and a `classes` `DataFrame` as a subset of the
1497 underlying,
1498
1499 >>> df = pd.DataFrame([[1,2],[3,4]], index=["a", "b"],
1500 ... columns=[["level0", "level0"], ["level1a", "level1b"]])
1501 >>> classes = pd.DataFrame(["min-val"], index=["a"],
1502 ... columns=[["level0"],["level1a"]])
1503 >>> df.style.set_td_classes(classes) # doctest: +SKIP
1504
1505 Form of the output with new additional css classes,
1506
1507 >>> from pandas.io.formats.style import Styler
1508 >>> df = pd.DataFrame([[1]])
1509 >>> css = pd.DataFrame([["other-class"]])
1510 >>> s = Styler(df, uuid="_", cell_ids=False).set_td_classes(css)
1511 >>> s.hide(axis=0).to_html() # doctest: +SKIP
1512 '<style type="text/css"></style>'
1513 '<table id="T__">'
1514 ' <thead>'
1515 ' <tr><th class="col_heading level0 col0" >0</th></tr>'
1516 ' </thead>'
1517 ' <tbody>'
1518 ' <tr><td class="data row0 col0 other-class" >1</td></tr>'
1519 ' </tbody>'
1520 '</table>'
1521 """
1522 if not classes.index.is_unique or not classes.columns.is_unique:
1523 raise KeyError(
1524 "Classes render only if `classes` has unique index and columns."
1525 )
1526 classes = classes.reindex_like(self.data)
1527
1528 for r, row_tup in enumerate(classes.itertuples()):
1529 for c, value in enumerate(row_tup[1:]):
1530 if not (pd.isna(value) or value == ""):
1531 self.cell_context[(r, c)] = str(value)
1532
1533 return self
1534
1535 def _update_ctx(self, attrs: DataFrame) -> None:
1536 """
1537 Update the state of the ``Styler`` for data cells.
1538
1539 Collects a mapping of {index_label: [('<property>', '<value>'), ..]}.
1540
1541 Parameters
1542 ----------
1543 attrs : DataFrame
1544 should contain strings of '<property>: <value>;<prop2>: <val2>'
1545 Whitespace shouldn't matter and the final trailing ';' shouldn't
1546 matter.
1547 """
1548 if not self.index.is_unique or not self.columns.is_unique:
1549 raise KeyError(
1550 "`Styler.apply` and `.map` are not compatible "
1551 "with non-unique index or columns."
1552 )
1553
1554 for cn in attrs.columns:
1555 j = self.columns.get_loc(cn)
1556 ser = attrs[cn]
1557 for rn, c in ser.items():
1558 if not c or pd.isna(c):
1559 continue
1560 css_list = maybe_convert_css_to_tuples(c)
1561 i = self.index.get_loc(rn)
1562 self.ctx[(i, j)].extend(css_list)
1563
1564 def _update_ctx_header(self, attrs: DataFrame, axis: AxisInt) -> None:
1565 """
1566 Update the state of the ``Styler`` for header cells.
1567
1568 Collects a mapping of {index_label: [('<property>', '<value>'), ..]}.
1569
1570 Parameters
1571 ----------
1572 attrs : Series
1573 Should contain strings of '<property>: <value>;<prop2>: <val2>', and an
1574 integer index.
1575 Whitespace shouldn't matter and the final trailing ';' shouldn't
1576 matter.
1577 axis : int
1578 Identifies whether the ctx object being updated is the index or columns
1579 """
1580 for j in attrs.columns:
1581 ser = attrs[j]
1582 for i, c in ser.items():
1583 if not c:
1584 continue
1585 css_list = maybe_convert_css_to_tuples(c)
1586 if axis == 0:
1587 self.ctx_index[(i, j)].extend(css_list)
1588 else:
1589 self.ctx_columns[(j, i)].extend(css_list)
1590
1591 def _copy(self, deepcopy: bool = False) -> Styler:
1592 """
1593 Copies a Styler, allowing for deepcopy or shallow copy
1594
1595 Copying a Styler aims to recreate a new Styler object which contains the same
1596 data and styles as the original.
1597
1598 Data dependent attributes [copied and NOT exported]:
1599 - formatting (._display_funcs)
1600 - hidden index values or column values (.hidden_rows, .hidden_columns)
1601 - tooltips
1602 - cell_context (cell css classes)
1603 - ctx (cell css styles)
1604 - caption
1605 - concatenated stylers
1606
1607 Non-data dependent attributes [copied and exported]:
1608 - css
1609 - hidden index state and hidden columns state (.hide_index_, .hide_columns_)
1610 - table_attributes
1611 - table_styles
1612 - applied styles (_todo)
1613
1614 """
1615 # GH 40675, 52728
1616 styler = type(self)(
1617 self.data, # populates attributes 'data', 'columns', 'index' as shallow
1618 )
1619 shallow = [ # simple string or boolean immutables
1620 "hide_index_",
1621 "hide_columns_",
1622 "hide_column_names",
1623 "hide_index_names",
1624 "table_attributes",
1625 "cell_ids",
1626 "caption",
1627 "uuid",
1628 "uuid_len",
1629 "template_latex", # also copy templates if these have been customised
1630 "template_html_style",
1631 "template_html_table",
1632 "template_html",
1633 ]
1634 deep = [ # nested lists or dicts
1635 "css",
1636 "concatenated",
1637 "_display_funcs",
1638 "_display_funcs_index",
1639 "_display_funcs_columns",
1640 "hidden_rows",
1641 "hidden_columns",
1642 "ctx",
1643 "ctx_index",
1644 "ctx_columns",
1645 "cell_context",
1646 "_todo",
1647 "table_styles",
1648 "tooltips",
1649 ]
1650
1651 for attr in shallow:
1652 setattr(styler, attr, getattr(self, attr))
1653
1654 for attr in deep:
1655 val = getattr(self, attr)
1656 setattr(styler, attr, copy.deepcopy(val) if deepcopy else val)
1657
1658 return styler
1659
1660 def __copy__(self) -> Styler:
1661 return self._copy(deepcopy=False)
1662
1663 def __deepcopy__(self, memo) -> Styler:
1664 return self._copy(deepcopy=True)
1665
1666 def clear(self) -> None:
1667 """
1668 Reset the ``Styler``, removing any previously applied styles.
1669
1670 Returns None.
1671
1672 Examples
1673 --------
1674 >>> df = pd.DataFrame({'A': [1, 2], 'B': [3, np.nan]})
1675
1676 After any added style:
1677
1678 >>> df.style.highlight_null(color='yellow') # doctest: +SKIP
1679
1680 Remove it with:
1681
1682 >>> df.style.clear() # doctest: +SKIP
1683
1684 Please see:
1685 `Table Visualization <../../user_guide/style.ipynb>`_ for more examples.
1686 """
1687 # create default GH 40675
1688 clean_copy = Styler(self.data, uuid=self.uuid)
1689 clean_attrs = [a for a in clean_copy.__dict__ if not callable(a)]
1690 self_attrs = [a for a in self.__dict__ if not callable(a)] # maybe more attrs
1691 for attr in clean_attrs:
1692 setattr(self, attr, getattr(clean_copy, attr))
1693 for attr in set(self_attrs).difference(clean_attrs):
1694 delattr(self, attr)
1695
1696 def _apply(
1697 self,
1698 func: Callable,
1699 axis: Axis | None = 0,
1700 subset: Subset | None = None,
1701 **kwargs,
1702 ) -> Styler:
1703 subset = slice(None) if subset is None else subset
1704 subset = non_reducing_slice(subset)
1705 data = self.data.loc[subset]
1706 if data.empty:
1707 result = DataFrame()
1708 elif axis is None:
1709 result = func(data, **kwargs)
1710 if not isinstance(result, DataFrame):
1711 if not isinstance(result, np.ndarray):
1712 raise TypeError(
1713 f"Function {repr(func)} must return a DataFrame or ndarray "
1714 f"when passed to `Styler.apply` with axis=None"
1715 )
1716 if data.shape != result.shape:
1717 raise ValueError(
1718 f"Function {repr(func)} returned ndarray with wrong shape.\n"
1719 f"Result has shape: {result.shape}\n"
1720 f"Expected shape: {data.shape}"
1721 )
1722 result = DataFrame(result, index=data.index, columns=data.columns)
1723 else:
1724 axis = self.data._get_axis_number(axis)
1725 if axis == 0:
1726 result = data.apply(func, axis=0, **kwargs)
1727 else:
1728 result = data.T.apply(func, axis=0, **kwargs).T # see GH 42005
1729
1730 if isinstance(result, Series):
1731 raise ValueError(
1732 f"Function {repr(func)} resulted in the apply method collapsing to a "
1733 f"Series.\nUsually, this is the result of the function returning a "
1734 f"single value, instead of list-like."
1735 )
1736 msg = (
1737 f"Function {repr(func)} created invalid {{0}} labels.\nUsually, this is "
1738 f"the result of the function returning a "
1739 f"{'Series' if axis is not None else 'DataFrame'} which contains invalid "
1740 f"labels, or returning an incorrectly shaped, list-like object which "
1741 f"cannot be mapped to labels, possibly due to applying the function along "
1742 f"the wrong axis.\n"
1743 f"Result {{0}} has shape: {{1}}\n"
1744 f"Expected {{0}} shape: {{2}}"
1745 )
1746 if not all(result.index.isin(data.index)):
1747 raise ValueError(msg.format("index", result.index.shape, data.index.shape))
1748 if not all(result.columns.isin(data.columns)):
1749 raise ValueError(
1750 msg.format("columns", result.columns.shape, data.columns.shape)
1751 )
1752 self._update_ctx(result)
1753 return self
1754
1755 @Substitution(subset=subset_args)
1756 def apply(
1757 self,
1758 func: Callable,
1759 axis: Axis | None = 0,
1760 subset: Subset | None = None,
1761 **kwargs,
1762 ) -> Styler:
1763 """
1764 Apply a CSS-styling function column-wise, row-wise, or table-wise.
1765
1766 Updates the HTML representation with the result.
1767
1768 Parameters
1769 ----------
1770 func : function
1771 ``func`` should take a Series if ``axis`` in [0,1] and return a list-like
1772 object of same length, or a Series, not necessarily of same length, with
1773 valid index labels considering ``subset``.
1774 ``func`` should take a DataFrame if ``axis`` is ``None`` and return either
1775 an ndarray with the same shape or a DataFrame, not necessarily of the same
1776 shape, with valid index and columns labels considering ``subset``.
1777
1778 .. versionchanged:: 1.3.0
1779
1780 .. versionchanged:: 1.4.0
1781
1782 axis : {0 or 'index', 1 or 'columns', None}, default 0
1783 Apply to each column (``axis=0`` or ``'index'``), to each row
1784 (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
1785 with ``axis=None``.
1786 %(subset)s
1787 **kwargs : dict
1788 Pass along to ``func``.
1789
1790 Returns
1791 -------
1792 Styler
1793
1794 See Also
1795 --------
1796 Styler.map_index: Apply a CSS-styling function to headers elementwise.
1797 Styler.apply_index: Apply a CSS-styling function to headers level-wise.
1798 Styler.map: Apply a CSS-styling function elementwise.
1799
1800 Notes
1801 -----
1802 The elements of the output of ``func`` should be CSS styles as strings, in the
1803 format 'attribute: value; attribute2: value2; ...' or,
1804 if nothing is to be applied to that element, an empty string or ``None``.
1805
1806 This is similar to ``DataFrame.apply``, except that ``axis=None``
1807 applies the function to the entire DataFrame at once,
1808 rather than column-wise or row-wise.
1809
1810 Examples
1811 --------
1812 >>> def highlight_max(x, color):
1813 ... return np.where(x == np.nanmax(x.to_numpy()), f"color: {color};", None)
1814 >>> df = pd.DataFrame(np.random.randn(5, 2), columns=["A", "B"])
1815 >>> df.style.apply(highlight_max, color='red') # doctest: +SKIP
1816 >>> df.style.apply(highlight_max, color='blue', axis=1) # doctest: +SKIP
1817 >>> df.style.apply(highlight_max, color='green', axis=None) # doctest: +SKIP
1818
1819 Using ``subset`` to restrict application to a single column or multiple columns
1820
1821 >>> df.style.apply(highlight_max, color='red', subset="A")
1822 ... # doctest: +SKIP
1823 >>> df.style.apply(highlight_max, color='red', subset=["A", "B"])
1824 ... # doctest: +SKIP
1825
1826 Using a 2d input to ``subset`` to select rows in addition to columns
1827
1828 >>> df.style.apply(highlight_max, color='red', subset=([0, 1, 2], slice(None)))
1829 ... # doctest: +SKIP
1830 >>> df.style.apply(highlight_max, color='red', subset=(slice(0, 5, 2), "A"))
1831 ... # doctest: +SKIP
1832
1833 Using a function which returns a Series / DataFrame of unequal length but
1834 containing valid index labels
1835
1836 >>> df = pd.DataFrame([[1, 2], [3, 4], [4, 6]], index=["A1", "A2", "Total"])
1837 >>> total_style = pd.Series("font-weight: bold;", index=["Total"])
1838 >>> df.style.apply(lambda s: total_style) # doctest: +SKIP
1839
1840 See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for
1841 more details.
1842 """
1843 self._todo.append(
1844 (lambda instance: getattr(instance, "_apply"), (func, axis, subset), kwargs)
1845 )
1846 return self
1847
1848 def _apply_index(
1849 self,
1850 func: Callable,
1851 axis: Axis = 0,
1852 level: Level | list[Level] | None = None,
1853 method: str = "apply",
1854 **kwargs,
1855 ) -> Styler:
1856 axis = self.data._get_axis_number(axis)
1857 obj = self.index if axis == 0 else self.columns
1858
1859 levels_ = refactor_levels(level, obj)
1860 data = DataFrame(obj.to_list()).loc[:, levels_]
1861
1862 if method == "apply":
1863 result = data.apply(func, axis=0, **kwargs)
1864 elif method == "map":
1865 result = data.map(func, **kwargs)
1866
1867 self._update_ctx_header(result, axis)
1868 return self
1869
1870 @doc(
1871 this="apply",
1872 wise="level-wise",
1873 alt="map",
1874 altwise="elementwise",
1875 func="take a Series and return a string array of the same length",
1876 input_note="the index as a Series, if an Index, or a level of a MultiIndex",
1877 output_note="an identically sized array of CSS styles as strings",
1878 var="s",
1879 ret='np.where(s == "B", "background-color: yellow;", "")',
1880 ret2='["background-color: yellow;" if "x" in v else "" for v in s]',
1881 )
1882 def apply_index(
1883 self,
1884 func: Callable,
1885 axis: AxisInt | str = 0,
1886 level: Level | list[Level] | None = None,
1887 **kwargs,
1888 ) -> Styler:
1889 """
1890 Apply a CSS-styling function to the index or column headers, {wise}.
1891
1892 Updates the HTML representation with the result.
1893
1894 .. versionadded:: 1.4.0
1895
1896 .. versionadded:: 2.1.0
1897 Styler.applymap_index was deprecated and renamed to Styler.map_index.
1898
1899 Parameters
1900 ----------
1901 func : function
1902 ``func`` should {func}.
1903 axis : {{0, 1, "index", "columns"}}
1904 The headers over which to apply the function.
1905 level : int, str, list, optional
1906 If index is MultiIndex the level(s) over which to apply the function.
1907 **kwargs : dict
1908 Pass along to ``func``.
1909
1910 Returns
1911 -------
1912 Styler
1913
1914 See Also
1915 --------
1916 Styler.{alt}_index: Apply a CSS-styling function to headers {altwise}.
1917 Styler.apply: Apply a CSS-styling function column-wise, row-wise, or table-wise.
1918 Styler.map: Apply a CSS-styling function elementwise.
1919
1920 Notes
1921 -----
1922 Each input to ``func`` will be {input_note}. The output of ``func`` should be
1923 {output_note}, in the format 'attribute: value; attribute2: value2; ...'
1924 or, if nothing is to be applied to that element, an empty string or ``None``.
1925
1926 Examples
1927 --------
1928 Basic usage to conditionally highlight values in the index.
1929
1930 >>> df = pd.DataFrame([[1,2], [3,4]], index=["A", "B"])
1931 >>> def color_b(s):
1932 ... return {ret}
1933 >>> df.style.{this}_index(color_b) # doctest: +SKIP
1934
1935 .. figure:: ../../_static/style/appmaphead1.png
1936
1937 Selectively applying to specific levels of MultiIndex columns.
1938
1939 >>> midx = pd.MultiIndex.from_product([['ix', 'jy'], [0, 1], ['x3', 'z4']])
1940 >>> df = pd.DataFrame([np.arange(8)], columns=midx)
1941 >>> def highlight_x({var}):
1942 ... return {ret2}
1943 >>> df.style.{this}_index(highlight_x, axis="columns", level=[0, 2])
1944 ... # doctest: +SKIP
1945
1946 .. figure:: ../../_static/style/appmaphead2.png
1947 """
1948 self._todo.append(
1949 (
1950 lambda instance: getattr(instance, "_apply_index"),
1951 (func, axis, level, "apply"),
1952 kwargs,
1953 )
1954 )
1955 return self
1956
1957 @doc(
1958 apply_index,
1959 this="map",
1960 wise="elementwise",
1961 alt="apply",
1962 altwise="level-wise",
1963 func="take a scalar and return a string",
1964 input_note="an index value, if an Index, or a level value of a MultiIndex",
1965 output_note="CSS styles as a string",
1966 var="v",
1967 ret='"background-color: yellow;" if v == "B" else None',
1968 ret2='"background-color: yellow;" if "x" in v else None',
1969 )
1970 def map_index(
1971 self,
1972 func: Callable,
1973 axis: AxisInt | str = 0,
1974 level: Level | list[Level] | None = None,
1975 **kwargs,
1976 ) -> Styler:
1977 self._todo.append(
1978 (
1979 lambda instance: getattr(instance, "_apply_index"),
1980 (func, axis, level, "map"),
1981 kwargs,
1982 )
1983 )
1984 return self
1985
1986 def applymap_index(
1987 self,
1988 func: Callable,
1989 axis: AxisInt | str = 0,
1990 level: Level | list[Level] | None = None,
1991 **kwargs,
1992 ) -> Styler:
1993 """
1994 Apply a CSS-styling function to the index or column headers, elementwise.
1995
1996 .. deprecated:: 2.1.0
1997
1998 Styler.applymap_index has been deprecated. Use Styler.map_index instead.
1999
2000 Parameters
2001 ----------
2002 func : function
2003 ``func`` should take a scalar and return a string.
2004 axis : {{0, 1, "index", "columns"}}
2005 The headers over which to apply the function.
2006 level : int, str, list, optional
2007 If index is MultiIndex the level(s) over which to apply the function.
2008 **kwargs : dict
2009 Pass along to ``func``.
2010
2011 Returns
2012 -------
2013 Styler
2014 """
2015 warnings.warn(
2016 "Styler.applymap_index has been deprecated. Use Styler.map_index instead.",
2017 FutureWarning,
2018 stacklevel=find_stack_level(),
2019 )
2020 return self.map_index(func, axis, level, **kwargs)
2021
2022 def _map(self, func: Callable, subset: Subset | None = None, **kwargs) -> Styler:
2023 func = partial(func, **kwargs) # map doesn't take kwargs?
2024 if subset is None:
2025 subset = IndexSlice[:]
2026 subset = non_reducing_slice(subset)
2027 result = self.data.loc[subset].map(func)
2028 self._update_ctx(result)
2029 return self
2030
2031 @Substitution(subset=subset_args)
2032 def map(self, func: Callable, subset: Subset | None = None, **kwargs) -> Styler:
2033 """
2034 Apply a CSS-styling function elementwise.
2035
2036 Updates the HTML representation with the result.
2037
2038 Parameters
2039 ----------
2040 func : function
2041 ``func`` should take a scalar and return a string.
2042 %(subset)s
2043 **kwargs : dict
2044 Pass along to ``func``.
2045
2046 Returns
2047 -------
2048 Styler
2049
2050 See Also
2051 --------
2052 Styler.map_index: Apply a CSS-styling function to headers elementwise.
2053 Styler.apply_index: Apply a CSS-styling function to headers level-wise.
2054 Styler.apply: Apply a CSS-styling function column-wise, row-wise, or table-wise.
2055
2056 Notes
2057 -----
2058 The elements of the output of ``func`` should be CSS styles as strings, in the
2059 format 'attribute: value; attribute2: value2; ...' or,
2060 if nothing is to be applied to that element, an empty string or ``None``.
2061
2062 Examples
2063 --------
2064 >>> def color_negative(v, color):
2065 ... return f"color: {color};" if v < 0 else None
2066 >>> df = pd.DataFrame(np.random.randn(5, 2), columns=["A", "B"])
2067 >>> df.style.map(color_negative, color='red') # doctest: +SKIP
2068
2069 Using ``subset`` to restrict application to a single column or multiple columns
2070
2071 >>> df.style.map(color_negative, color='red', subset="A")
2072 ... # doctest: +SKIP
2073 >>> df.style.map(color_negative, color='red', subset=["A", "B"])
2074 ... # doctest: +SKIP
2075
2076 Using a 2d input to ``subset`` to select rows in addition to columns
2077
2078 >>> df.style.map(color_negative, color='red',
2079 ... subset=([0,1,2], slice(None))) # doctest: +SKIP
2080 >>> df.style.map(color_negative, color='red', subset=(slice(0,5,2), "A"))
2081 ... # doctest: +SKIP
2082
2083 See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for
2084 more details.
2085 """
2086 self._todo.append(
2087 (lambda instance: getattr(instance, "_map"), (func, subset), kwargs)
2088 )
2089 return self
2090
2091 @Substitution(subset=subset_args)
2092 def applymap(
2093 self, func: Callable, subset: Subset | None = None, **kwargs
2094 ) -> Styler:
2095 """
2096 Apply a CSS-styling function elementwise.
2097
2098 .. deprecated:: 2.1.0
2099
2100 Styler.applymap has been deprecated. Use Styler.map instead.
2101
2102 Parameters
2103 ----------
2104 func : function
2105 ``func`` should take a scalar and return a string.
2106 %(subset)s
2107 **kwargs : dict
2108 Pass along to ``func``.
2109
2110 Returns
2111 -------
2112 Styler
2113 """
2114 warnings.warn(
2115 "Styler.applymap has been deprecated. Use Styler.map instead.",
2116 FutureWarning,
2117 stacklevel=find_stack_level(),
2118 )
2119 return self.map(func, subset, **kwargs)
2120
2121 def set_table_attributes(self, attributes: str) -> Styler:
2122 """
2123 Set the table attributes added to the ``<table>`` HTML element.
2124
2125 These are items in addition to automatic (by default) ``id`` attribute.
2126
2127 Parameters
2128 ----------
2129 attributes : str
2130
2131 Returns
2132 -------
2133 Styler
2134
2135 See Also
2136 --------
2137 Styler.set_table_styles: Set the table styles included within the ``<style>``
2138 HTML element.
2139 Styler.set_td_classes: Set the DataFrame of strings added to the ``class``
2140 attribute of ``<td>`` HTML elements.
2141
2142 Examples
2143 --------
2144 >>> df = pd.DataFrame(np.random.randn(10, 4))
2145 >>> df.style.set_table_attributes('class="pure-table"') # doctest: +SKIP
2146 # ... <table class="pure-table"> ...
2147 """
2148 self.table_attributes = attributes
2149 return self
2150
2151 def export(self) -> dict[str, Any]:
2152 """
2153 Export the styles applied to the current Styler.
2154
2155 Can be applied to a second Styler with ``Styler.use``.
2156
2157 Returns
2158 -------
2159 dict
2160
2161 See Also
2162 --------
2163 Styler.use: Set the styles on the current Styler.
2164 Styler.copy: Create a copy of the current Styler.
2165
2166 Notes
2167 -----
2168 This method is designed to copy non-data dependent attributes of
2169 one Styler to another. It differs from ``Styler.copy`` where data and
2170 data dependent attributes are also copied.
2171
2172 The following items are exported since they are not generally data dependent:
2173
2174 - Styling functions added by the ``apply`` and ``map``
2175 - Whether axes and names are hidden from the display, if unambiguous.
2176 - Table attributes
2177 - Table styles
2178
2179 The following attributes are considered data dependent and therefore not
2180 exported:
2181
2182 - Caption
2183 - UUID
2184 - Tooltips
2185 - Any hidden rows or columns identified by Index labels
2186 - Any formatting applied using ``Styler.format``
2187 - Any CSS classes added using ``Styler.set_td_classes``
2188
2189 Examples
2190 --------
2191
2192 >>> styler = pd.DataFrame([[1, 2], [3, 4]]).style
2193 >>> styler2 = pd.DataFrame([[9, 9, 9]]).style
2194 >>> styler.hide(axis=0).highlight_max(axis=1) # doctest: +SKIP
2195 >>> export = styler.export()
2196 >>> styler2.use(export) # doctest: +SKIP
2197 """
2198 return {
2199 "apply": copy.copy(self._todo),
2200 "table_attributes": self.table_attributes,
2201 "table_styles": copy.copy(self.table_styles),
2202 "hide_index": all(self.hide_index_),
2203 "hide_columns": all(self.hide_columns_),
2204 "hide_index_names": self.hide_index_names,
2205 "hide_column_names": self.hide_column_names,
2206 "css": copy.copy(self.css),
2207 }
2208
2209 def use(self, styles: dict[str, Any]) -> Styler:
2210 """
2211 Set the styles on the current Styler.
2212
2213 Possibly uses styles from ``Styler.export``.
2214
2215 Parameters
2216 ----------
2217 styles : dict(str, Any)
2218 List of attributes to add to Styler. Dict keys should contain only:
2219 - "apply": list of styler functions, typically added with ``apply`` or
2220 ``map``.
2221 - "table_attributes": HTML attributes, typically added with
2222 ``set_table_attributes``.
2223 - "table_styles": CSS selectors and properties, typically added with
2224 ``set_table_styles``.
2225 - "hide_index": whether the index is hidden, typically added with
2226 ``hide_index``, or a boolean list for hidden levels.
2227 - "hide_columns": whether column headers are hidden, typically added with
2228 ``hide_columns``, or a boolean list for hidden levels.
2229 - "hide_index_names": whether index names are hidden.
2230 - "hide_column_names": whether column header names are hidden.
2231 - "css": the css class names used.
2232
2233 Returns
2234 -------
2235 Styler
2236
2237 See Also
2238 --------
2239 Styler.export : Export the non data dependent attributes to the current Styler.
2240
2241 Examples
2242 --------
2243
2244 >>> styler = pd.DataFrame([[1, 2], [3, 4]]).style
2245 >>> styler2 = pd.DataFrame([[9, 9, 9]]).style
2246 >>> styler.hide(axis=0).highlight_max(axis=1) # doctest: +SKIP
2247 >>> export = styler.export()
2248 >>> styler2.use(export) # doctest: +SKIP
2249 """
2250 self._todo.extend(styles.get("apply", []))
2251 table_attributes: str = self.table_attributes or ""
2252 obj_table_atts: str = (
2253 ""
2254 if styles.get("table_attributes") is None
2255 else str(styles.get("table_attributes"))
2256 )
2257 self.set_table_attributes((table_attributes + " " + obj_table_atts).strip())
2258 if styles.get("table_styles"):
2259 self.set_table_styles(styles.get("table_styles"), overwrite=False)
2260
2261 for obj in ["index", "columns"]:
2262 hide_obj = styles.get("hide_" + obj)
2263 if hide_obj is not None:
2264 if isinstance(hide_obj, bool):
2265 n = getattr(self, obj).nlevels
2266 setattr(self, "hide_" + obj + "_", [hide_obj] * n)
2267 else:
2268 setattr(self, "hide_" + obj + "_", hide_obj)
2269
2270 self.hide_index_names = styles.get("hide_index_names", False)
2271 self.hide_column_names = styles.get("hide_column_names", False)
2272 if styles.get("css"):
2273 self.css = styles.get("css") # type: ignore[assignment]
2274 return self
2275
2276 def set_uuid(self, uuid: str) -> Styler:
2277 """
2278 Set the uuid applied to ``id`` attributes of HTML elements.
2279
2280 Parameters
2281 ----------
2282 uuid : str
2283
2284 Returns
2285 -------
2286 Styler
2287
2288 Notes
2289 -----
2290 Almost all HTML elements within the table, and including the ``<table>`` element
2291 are assigned ``id`` attributes. The format is ``T_uuid_<extra>`` where
2292 ``<extra>`` is typically a more specific identifier, such as ``row1_col2``.
2293
2294 Examples
2295 --------
2296 >>> df = pd.DataFrame([[1, 2], [3, 4]], index=['A', 'B'], columns=['c1', 'c2'])
2297
2298 You can get the `id` attributes with the following:
2299
2300 >>> print((df).style.to_html()) # doctest: +SKIP
2301
2302 To add a title to column `c1`, its `id` is T_20a7d_level0_col0:
2303
2304 >>> df.style.set_uuid("T_20a7d_level0_col0")
2305 ... .set_caption("Test") # doctest: +SKIP
2306
2307 Please see:
2308 `Table visualization <../../user_guide/style.ipynb>`_ for more examples.
2309 """
2310 self.uuid = uuid
2311 return self
2312
2313 def set_caption(self, caption: str | tuple | list) -> Styler:
2314 """
2315 Set the text added to a ``<caption>`` HTML element.
2316
2317 Parameters
2318 ----------
2319 caption : str, tuple, list
2320 For HTML output either the string input is used or the first element of the
2321 tuple. For LaTeX the string input provides a caption and the additional
2322 tuple input allows for full captions and short captions, in that order.
2323
2324 Returns
2325 -------
2326 Styler
2327
2328 Examples
2329 --------
2330 >>> df = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
2331 >>> df.style.set_caption("test") # doctest: +SKIP
2332
2333 Please see:
2334 `Table Visualization <../../user_guide/style.ipynb>`_ for more examples.
2335 """
2336 msg = "`caption` must be either a string or 2-tuple of strings."
2337 if isinstance(caption, (list, tuple)):
2338 if (
2339 len(caption) != 2
2340 or not isinstance(caption[0], str)
2341 or not isinstance(caption[1], str)
2342 ):
2343 raise ValueError(msg)
2344 elif not isinstance(caption, str):
2345 raise ValueError(msg)
2346 self.caption = caption
2347 return self
2348
2349 def set_sticky(
2350 self,
2351 axis: Axis = 0,
2352 pixel_size: int | None = None,
2353 levels: Level | list[Level] | None = None,
2354 ) -> Styler:
2355 """
2356 Add CSS to permanently display the index or column headers in a scrolling frame.
2357
2358 Parameters
2359 ----------
2360 axis : {0 or 'index', 1 or 'columns'}, default 0
2361 Whether to make the index or column headers sticky.
2362 pixel_size : int, optional
2363 Required to configure the width of index cells or the height of column
2364 header cells when sticking a MultiIndex (or with a named Index).
2365 Defaults to 75 and 25 respectively.
2366 levels : int, str, list, optional
2367 If ``axis`` is a MultiIndex the specific levels to stick. If ``None`` will
2368 stick all levels.
2369
2370 Returns
2371 -------
2372 Styler
2373
2374 Notes
2375 -----
2376 This method uses the CSS 'position: sticky;' property to display. It is
2377 designed to work with visible axes, therefore both:
2378
2379 - `styler.set_sticky(axis="index").hide(axis="index")`
2380 - `styler.set_sticky(axis="columns").hide(axis="columns")`
2381
2382 may produce strange behaviour due to CSS controls with missing elements.
2383
2384 Examples
2385 --------
2386 >>> df = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
2387 >>> df.style.set_sticky(axis="index") # doctest: +SKIP
2388
2389 Please see:
2390 `Table Visualization <../../user_guide/style.ipynb>`_ for more examples.
2391 """
2392 axis = self.data._get_axis_number(axis)
2393 obj = self.data.index if axis == 0 else self.data.columns
2394 pixel_size = (75 if axis == 0 else 25) if not pixel_size else pixel_size
2395
2396 props = "position:sticky; background-color:inherit;"
2397 if not isinstance(obj, pd.MultiIndex):
2398 # handling MultiIndexes requires different CSS
2399
2400 if axis == 1:
2401 # stick the first <tr> of <head> and, if index names, the second <tr>
2402 # if self._hide_columns then no <thead><tr> here will exist: no conflict
2403 styles: CSSStyles = [
2404 {
2405 "selector": "thead tr:nth-child(1) th",
2406 "props": props + "top:0px; z-index:2;",
2407 }
2408 ]
2409 if self.index.names[0] is not None:
2410 styles[0]["props"] = (
2411 props + f"top:0px; z-index:2; height:{pixel_size}px;"
2412 )
2413 styles.append(
2414 {
2415 "selector": "thead tr:nth-child(2) th",
2416 "props": props
2417 + f"top:{pixel_size}px; z-index:2; height:{pixel_size}px; ",
2418 }
2419 )
2420 else:
2421 # stick the first <th> of each <tr> in both <thead> and <tbody>
2422 # if self._hide_index then no <th> will exist in <tbody>: no conflict
2423 # but <th> will exist in <thead>: conflict with initial element
2424 styles = [
2425 {
2426 "selector": "thead tr th:nth-child(1)",
2427 "props": props + "left:0px; z-index:3 !important;",
2428 },
2429 {
2430 "selector": "tbody tr th:nth-child(1)",
2431 "props": props + "left:0px; z-index:1;",
2432 },
2433 ]
2434
2435 else:
2436 # handle the MultiIndex case
2437 range_idx = list(range(obj.nlevels))
2438 levels_: list[int] = refactor_levels(levels, obj) if levels else range_idx
2439 levels_ = sorted(levels_)
2440
2441 if axis == 1:
2442 styles = []
2443 for i, level in enumerate(levels_):
2444 styles.append(
2445 {
2446 "selector": f"thead tr:nth-child({level+1}) th",
2447 "props": props
2448 + (
2449 f"top:{i * pixel_size}px; height:{pixel_size}px; "
2450 "z-index:2;"
2451 ),
2452 }
2453 )
2454 if not all(name is None for name in self.index.names):
2455 styles.append(
2456 {
2457 "selector": f"thead tr:nth-child({obj.nlevels+1}) th",
2458 "props": props
2459 + (
2460 f"top:{(len(levels_)) * pixel_size}px; "
2461 f"height:{pixel_size}px; z-index:2;"
2462 ),
2463 }
2464 )
2465
2466 else:
2467 styles = []
2468 for i, level in enumerate(levels_):
2469 props_ = props + (
2470 f"left:{i * pixel_size}px; "
2471 f"min-width:{pixel_size}px; "
2472 f"max-width:{pixel_size}px; "
2473 )
2474 styles.extend(
2475 [
2476 {
2477 "selector": f"thead tr th:nth-child({level+1})",
2478 "props": props_ + "z-index:3 !important;",
2479 },
2480 {
2481 "selector": f"tbody tr th.level{level}",
2482 "props": props_ + "z-index:1;",
2483 },
2484 ]
2485 )
2486
2487 return self.set_table_styles(styles, overwrite=False)
2488
2489 def set_table_styles(
2490 self,
2491 table_styles: dict[Any, CSSStyles] | CSSStyles | None = None,
2492 axis: AxisInt = 0,
2493 overwrite: bool = True,
2494 css_class_names: dict[str, str] | None = None,
2495 ) -> Styler:
2496 """
2497 Set the table styles included within the ``<style>`` HTML element.
2498
2499 This function can be used to style the entire table, columns, rows or
2500 specific HTML selectors.
2501
2502 Parameters
2503 ----------
2504 table_styles : list or dict
2505 If supplying a list, each individual table_style should be a
2506 dictionary with ``selector`` and ``props`` keys. ``selector``
2507 should be a CSS selector that the style will be applied to
2508 (automatically prefixed by the table's UUID) and ``props``
2509 should be a list of tuples with ``(attribute, value)``.
2510 If supplying a dict, the dict keys should correspond to
2511 column names or index values, depending upon the specified
2512 `axis` argument. These will be mapped to row or col CSS
2513 selectors. MultiIndex values as dict keys should be
2514 in their respective tuple form. The dict values should be
2515 a list as specified in the form with CSS selectors and
2516 props that will be applied to the specified row or column.
2517 axis : {0 or 'index', 1 or 'columns', None}, default 0
2518 Apply to each column (``axis=0`` or ``'index'``), to each row
2519 (``axis=1`` or ``'columns'``). Only used if `table_styles` is
2520 dict.
2521 overwrite : bool, default True
2522 Styles are replaced if `True`, or extended if `False`. CSS
2523 rules are preserved so most recent styles set will dominate
2524 if selectors intersect.
2525 css_class_names : dict, optional
2526 A dict of strings used to replace the default CSS classes described below.
2527
2528 .. versionadded:: 1.4.0
2529
2530 Returns
2531 -------
2532 Styler
2533
2534 See Also
2535 --------
2536 Styler.set_td_classes: Set the DataFrame of strings added to the ``class``
2537 attribute of ``<td>`` HTML elements.
2538 Styler.set_table_attributes: Set the table attributes added to the ``<table>``
2539 HTML element.
2540
2541 Notes
2542 -----
2543 The default CSS classes dict, whose values can be replaced is as follows:
2544
2545 .. code-block:: python
2546
2547 css_class_names = {"row_heading": "row_heading",
2548 "col_heading": "col_heading",
2549 "index_name": "index_name",
2550 "col": "col",
2551 "row": "row",
2552 "col_trim": "col_trim",
2553 "row_trim": "row_trim",
2554 "level": "level",
2555 "data": "data",
2556 "blank": "blank",
2557 "foot": "foot"}
2558
2559 Examples
2560 --------
2561 >>> df = pd.DataFrame(np.random.randn(10, 4),
2562 ... columns=['A', 'B', 'C', 'D'])
2563 >>> df.style.set_table_styles(
2564 ... [{'selector': 'tr:hover',
2565 ... 'props': [('background-color', 'yellow')]}]
2566 ... ) # doctest: +SKIP
2567
2568 Or with CSS strings
2569
2570 >>> df.style.set_table_styles(
2571 ... [{'selector': 'tr:hover',
2572 ... 'props': 'background-color: yellow; font-size: 1em;'}]
2573 ... ) # doctest: +SKIP
2574
2575 Adding column styling by name
2576
2577 >>> df.style.set_table_styles({
2578 ... 'A': [{'selector': '',
2579 ... 'props': [('color', 'red')]}],
2580 ... 'B': [{'selector': 'td',
2581 ... 'props': 'color: blue;'}]
2582 ... }, overwrite=False) # doctest: +SKIP
2583
2584 Adding row styling
2585
2586 >>> df.style.set_table_styles({
2587 ... 0: [{'selector': 'td:hover',
2588 ... 'props': [('font-size', '25px')]}]
2589 ... }, axis=1, overwrite=False) # doctest: +SKIP
2590
2591 See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for
2592 more details.
2593 """
2594 if css_class_names is not None:
2595 self.css = {**self.css, **css_class_names}
2596
2597 if table_styles is None:
2598 return self
2599 elif isinstance(table_styles, dict):
2600 axis = self.data._get_axis_number(axis)
2601 obj = self.data.index if axis == 1 else self.data.columns
2602 idf = f".{self.css['row']}" if axis == 1 else f".{self.css['col']}"
2603
2604 table_styles = [
2605 {
2606 "selector": str(s["selector"]) + idf + str(idx),
2607 "props": maybe_convert_css_to_tuples(s["props"]),
2608 }
2609 for key, styles in table_styles.items()
2610 for idx in obj.get_indexer_for([key])
2611 for s in format_table_styles(styles)
2612 ]
2613 else:
2614 table_styles = [
2615 {
2616 "selector": s["selector"],
2617 "props": maybe_convert_css_to_tuples(s["props"]),
2618 }
2619 for s in table_styles
2620 ]
2621
2622 if not overwrite and self.table_styles is not None:
2623 self.table_styles.extend(table_styles)
2624 else:
2625 self.table_styles = table_styles
2626 return self
2627
2628 def hide(
2629 self,
2630 subset: Subset | None = None,
2631 axis: Axis = 0,
2632 level: Level | list[Level] | None = None,
2633 names: bool = False,
2634 ) -> Styler:
2635 """
2636 Hide the entire index / column headers, or specific rows / columns from display.
2637
2638 .. versionadded:: 1.4.0
2639
2640 Parameters
2641 ----------
2642 subset : label, array-like, IndexSlice, optional
2643 A valid 1d input or single key along the axis within
2644 `DataFrame.loc[<subset>, :]` or `DataFrame.loc[:, <subset>]` depending
2645 upon ``axis``, to limit ``data`` to select hidden rows / columns.
2646 axis : {"index", 0, "columns", 1}
2647 Apply to the index or columns.
2648 level : int, str, list
2649 The level(s) to hide in a MultiIndex if hiding the entire index / column
2650 headers. Cannot be used simultaneously with ``subset``.
2651 names : bool
2652 Whether to hide the level name(s) of the index / columns headers in the case
2653 it (or at least one the levels) remains visible.
2654
2655 Returns
2656 -------
2657 Styler
2658
2659 Notes
2660 -----
2661 .. warning::
2662 This method only works with the output methods ``to_html``, ``to_string``
2663 and ``to_latex``.
2664
2665 Other output methods, including ``to_excel``, ignore this hiding method
2666 and will display all data.
2667
2668 This method has multiple functionality depending upon the combination
2669 of the ``subset``, ``level`` and ``names`` arguments (see examples). The
2670 ``axis`` argument is used only to control whether the method is applied to row
2671 or column headers:
2672
2673 .. list-table:: Argument combinations
2674 :widths: 10 20 10 60
2675 :header-rows: 1
2676
2677 * - ``subset``
2678 - ``level``
2679 - ``names``
2680 - Effect
2681 * - None
2682 - None
2683 - False
2684 - The axis-Index is hidden entirely.
2685 * - None
2686 - None
2687 - True
2688 - Only the axis-Index names are hidden.
2689 * - None
2690 - Int, Str, List
2691 - False
2692 - Specified axis-MultiIndex levels are hidden entirely.
2693 * - None
2694 - Int, Str, List
2695 - True
2696 - Specified axis-MultiIndex levels are hidden entirely and the names of
2697 remaining axis-MultiIndex levels.
2698 * - Subset
2699 - None
2700 - False
2701 - The specified data rows/columns are hidden, but the axis-Index itself,
2702 and names, remain unchanged.
2703 * - Subset
2704 - None
2705 - True
2706 - The specified data rows/columns and axis-Index names are hidden, but
2707 the axis-Index itself remains unchanged.
2708 * - Subset
2709 - Int, Str, List
2710 - Boolean
2711 - ValueError: cannot supply ``subset`` and ``level`` simultaneously.
2712
2713 Note this method only hides the identified elements so can be chained to hide
2714 multiple elements in sequence.
2715
2716 Examples
2717 --------
2718 Simple application hiding specific rows:
2719
2720 >>> df = pd.DataFrame([[1,2], [3,4], [5,6]], index=["a", "b", "c"])
2721 >>> df.style.hide(["a", "b"]) # doctest: +SKIP
2722 0 1
2723 c 5 6
2724
2725 Hide the index and retain the data values:
2726
2727 >>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]])
2728 >>> df = pd.DataFrame(np.random.randn(6,6), index=midx, columns=midx)
2729 >>> df.style.format("{:.1f}").hide() # doctest: +SKIP
2730 x y
2731 a b c a b c
2732 0.1 0.0 0.4 1.3 0.6 -1.4
2733 0.7 1.0 1.3 1.5 -0.0 -0.2
2734 1.4 -0.8 1.6 -0.2 -0.4 -0.3
2735 0.4 1.0 -0.2 -0.8 -1.2 1.1
2736 -0.6 1.2 1.8 1.9 0.3 0.3
2737 0.8 0.5 -0.3 1.2 2.2 -0.8
2738
2739 Hide specific rows in a MultiIndex but retain the index:
2740
2741 >>> df.style.format("{:.1f}").hide(subset=(slice(None), ["a", "c"]))
2742 ... # doctest: +SKIP
2743 x y
2744 a b c a b c
2745 x b 0.7 1.0 1.3 1.5 -0.0 -0.2
2746 y b -0.6 1.2 1.8 1.9 0.3 0.3
2747
2748 Hide specific rows and the index through chaining:
2749
2750 >>> df.style.format("{:.1f}").hide(subset=(slice(None), ["a", "c"])).hide()
2751 ... # doctest: +SKIP
2752 x y
2753 a b c a b c
2754 0.7 1.0 1.3 1.5 -0.0 -0.2
2755 -0.6 1.2 1.8 1.9 0.3 0.3
2756
2757 Hide a specific level:
2758
2759 >>> df.style.format("{:,.1f}").hide(level=1) # doctest: +SKIP
2760 x y
2761 a b c a b c
2762 x 0.1 0.0 0.4 1.3 0.6 -1.4
2763 0.7 1.0 1.3 1.5 -0.0 -0.2
2764 1.4 -0.8 1.6 -0.2 -0.4 -0.3
2765 y 0.4 1.0 -0.2 -0.8 -1.2 1.1
2766 -0.6 1.2 1.8 1.9 0.3 0.3
2767 0.8 0.5 -0.3 1.2 2.2 -0.8
2768
2769 Hiding just the index level names:
2770
2771 >>> df.index.names = ["lev0", "lev1"]
2772 >>> df.style.format("{:,.1f}").hide(names=True) # doctest: +SKIP
2773 x y
2774 a b c a b c
2775 x a 0.1 0.0 0.4 1.3 0.6 -1.4
2776 b 0.7 1.0 1.3 1.5 -0.0 -0.2
2777 c 1.4 -0.8 1.6 -0.2 -0.4 -0.3
2778 y a 0.4 1.0 -0.2 -0.8 -1.2 1.1
2779 b -0.6 1.2 1.8 1.9 0.3 0.3
2780 c 0.8 0.5 -0.3 1.2 2.2 -0.8
2781
2782 Examples all produce equivalently transposed effects with ``axis="columns"``.
2783 """
2784 axis = self.data._get_axis_number(axis)
2785 if axis == 0:
2786 obj, objs, alt = "index", "index", "rows"
2787 else:
2788 obj, objs, alt = "column", "columns", "columns"
2789
2790 if level is not None and subset is not None:
2791 raise ValueError("`subset` and `level` cannot be passed simultaneously")
2792
2793 if subset is None:
2794 if level is None and names:
2795 # this combination implies user shows the index and hides just names
2796 setattr(self, f"hide_{obj}_names", True)
2797 return self
2798
2799 levels_ = refactor_levels(level, getattr(self, objs))
2800 setattr(
2801 self,
2802 f"hide_{objs}_",
2803 [lev in levels_ for lev in range(getattr(self, objs).nlevels)],
2804 )
2805 else:
2806 if axis == 0:
2807 subset_ = IndexSlice[subset, :] # new var so mypy reads not Optional
2808 else:
2809 subset_ = IndexSlice[:, subset] # new var so mypy reads not Optional
2810 subset = non_reducing_slice(subset_)
2811 hide = self.data.loc[subset]
2812 h_els = getattr(self, objs).get_indexer_for(getattr(hide, objs))
2813 setattr(self, f"hidden_{alt}", h_els)
2814
2815 if names:
2816 setattr(self, f"hide_{obj}_names", True)
2817 return self
2818
2819 # -----------------------------------------------------------------------
2820 # A collection of "builtin" styles
2821 # -----------------------------------------------------------------------
2822
2823 def _get_numeric_subset_default(self):
2824 # Returns a boolean mask indicating where `self.data` has numerical columns.
2825 # Choosing a mask as opposed to the column names also works for
2826 # boolean column labels (GH47838).
2827 return self.data.columns.isin(self.data.select_dtypes(include=np.number))
2828
2829 @doc(
2830 name="background",
2831 alt="text",
2832 image_prefix="bg",
2833 text_threshold="""text_color_threshold : float or int\n
2834 Luminance threshold for determining text color in [0, 1]. Facilitates text\n
2835 visibility across varying background colors. All text is dark if 0, and\n
2836 light if 1, defaults to 0.408.""",
2837 )
2838 @Substitution(subset=subset_args)
2839 def background_gradient(
2840 self,
2841 cmap: str | Colormap = "PuBu",
2842 low: float = 0,
2843 high: float = 0,
2844 axis: Axis | None = 0,
2845 subset: Subset | None = None,
2846 text_color_threshold: float = 0.408,
2847 vmin: float | None = None,
2848 vmax: float | None = None,
2849 gmap: Sequence | None = None,
2850 ) -> Styler:
2851 """
2852 Color the {name} in a gradient style.
2853
2854 The {name} color is determined according
2855 to the data in each column, row or frame, or by a given
2856 gradient map. Requires matplotlib.
2857
2858 Parameters
2859 ----------
2860 cmap : str or colormap
2861 Matplotlib colormap.
2862 low : float
2863 Compress the color range at the low end. This is a multiple of the data
2864 range to extend below the minimum; good values usually in [0, 1],
2865 defaults to 0.
2866 high : float
2867 Compress the color range at the high end. This is a multiple of the data
2868 range to extend above the maximum; good values usually in [0, 1],
2869 defaults to 0.
2870 axis : {{0, 1, "index", "columns", None}}, default 0
2871 Apply to each column (``axis=0`` or ``'index'``), to each row
2872 (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
2873 with ``axis=None``.
2874 %(subset)s
2875 {text_threshold}
2876 vmin : float, optional
2877 Minimum data value that corresponds to colormap minimum value.
2878 If not specified the minimum value of the data (or gmap) will be used.
2879 vmax : float, optional
2880 Maximum data value that corresponds to colormap maximum value.
2881 If not specified the maximum value of the data (or gmap) will be used.
2882 gmap : array-like, optional
2883 Gradient map for determining the {name} colors. If not supplied
2884 will use the underlying data from rows, columns or frame. If given as an
2885 ndarray or list-like must be an identical shape to the underlying data
2886 considering ``axis`` and ``subset``. If given as DataFrame or Series must
2887 have same index and column labels considering ``axis`` and ``subset``.
2888 If supplied, ``vmin`` and ``vmax`` should be given relative to this
2889 gradient map.
2890
2891 .. versionadded:: 1.3.0
2892
2893 Returns
2894 -------
2895 Styler
2896
2897 See Also
2898 --------
2899 Styler.{alt}_gradient: Color the {alt} in a gradient style.
2900
2901 Notes
2902 -----
2903 When using ``low`` and ``high`` the range
2904 of the gradient, given by the data if ``gmap`` is not given or by ``gmap``,
2905 is extended at the low end effectively by
2906 `map.min - low * map.range` and at the high end by
2907 `map.max + high * map.range` before the colors are normalized and determined.
2908
2909 If combining with ``vmin`` and ``vmax`` the `map.min`, `map.max` and
2910 `map.range` are replaced by values according to the values derived from
2911 ``vmin`` and ``vmax``.
2912
2913 This method will preselect numeric columns and ignore non-numeric columns
2914 unless a ``gmap`` is supplied in which case no preselection occurs.
2915
2916 Examples
2917 --------
2918 >>> df = pd.DataFrame(columns=["City", "Temp (c)", "Rain (mm)", "Wind (m/s)"],
2919 ... data=[["Stockholm", 21.6, 5.0, 3.2],
2920 ... ["Oslo", 22.4, 13.3, 3.1],
2921 ... ["Copenhagen", 24.5, 0.0, 6.7]])
2922
2923 Shading the values column-wise, with ``axis=0``, preselecting numeric columns
2924
2925 >>> df.style.{name}_gradient(axis=0) # doctest: +SKIP
2926
2927 .. figure:: ../../_static/style/{image_prefix}_ax0.png
2928
2929 Shading all values collectively using ``axis=None``
2930
2931 >>> df.style.{name}_gradient(axis=None) # doctest: +SKIP
2932
2933 .. figure:: ../../_static/style/{image_prefix}_axNone.png
2934
2935 Compress the color map from the both ``low`` and ``high`` ends
2936
2937 >>> df.style.{name}_gradient(axis=None, low=0.75, high=1.0) # doctest: +SKIP
2938
2939 .. figure:: ../../_static/style/{image_prefix}_axNone_lowhigh.png
2940
2941 Manually setting ``vmin`` and ``vmax`` gradient thresholds
2942
2943 >>> df.style.{name}_gradient(axis=None, vmin=6.7, vmax=21.6) # doctest: +SKIP
2944
2945 .. figure:: ../../_static/style/{image_prefix}_axNone_vminvmax.png
2946
2947 Setting a ``gmap`` and applying to all columns with another ``cmap``
2948
2949 >>> df.style.{name}_gradient(axis=0, gmap=df['Temp (c)'], cmap='YlOrRd')
2950 ... # doctest: +SKIP
2951
2952 .. figure:: ../../_static/style/{image_prefix}_gmap.png
2953
2954 Setting the gradient map for a dataframe (i.e. ``axis=None``), we need to
2955 explicitly state ``subset`` to match the ``gmap`` shape
2956
2957 >>> gmap = np.array([[1,2,3], [2,3,4], [3,4,5]])
2958 >>> df.style.{name}_gradient(axis=None, gmap=gmap,
2959 ... cmap='YlOrRd', subset=['Temp (c)', 'Rain (mm)', 'Wind (m/s)']
2960 ... ) # doctest: +SKIP
2961
2962 .. figure:: ../../_static/style/{image_prefix}_axNone_gmap.png
2963 """
2964 if subset is None and gmap is None:
2965 subset = self._get_numeric_subset_default()
2966
2967 self.apply(
2968 _background_gradient,
2969 cmap=cmap,
2970 subset=subset,
2971 axis=axis,
2972 low=low,
2973 high=high,
2974 text_color_threshold=text_color_threshold,
2975 vmin=vmin,
2976 vmax=vmax,
2977 gmap=gmap,
2978 )
2979 return self
2980
2981 @doc(
2982 background_gradient,
2983 name="text",
2984 alt="background",
2985 image_prefix="tg",
2986 text_threshold="",
2987 )
2988 def text_gradient(
2989 self,
2990 cmap: str | Colormap = "PuBu",
2991 low: float = 0,
2992 high: float = 0,
2993 axis: Axis | None = 0,
2994 subset: Subset | None = None,
2995 vmin: float | None = None,
2996 vmax: float | None = None,
2997 gmap: Sequence | None = None,
2998 ) -> Styler:
2999 if subset is None and gmap is None:
3000 subset = self._get_numeric_subset_default()
3001
3002 return self.apply(
3003 _background_gradient,
3004 cmap=cmap,
3005 subset=subset,
3006 axis=axis,
3007 low=low,
3008 high=high,
3009 vmin=vmin,
3010 vmax=vmax,
3011 gmap=gmap,
3012 text_only=True,
3013 )
3014
3015 @Substitution(subset=subset_args)
3016 def set_properties(self, subset: Subset | None = None, **kwargs) -> Styler:
3017 """
3018 Set defined CSS-properties to each ``<td>`` HTML element for the given subset.
3019
3020 Parameters
3021 ----------
3022 %(subset)s
3023 **kwargs : dict
3024 A dictionary of property, value pairs to be set for each cell.
3025
3026 Returns
3027 -------
3028 Styler
3029
3030 Notes
3031 -----
3032 This is a convenience methods which wraps the :meth:`Styler.map` calling a
3033 function returning the CSS-properties independently of the data.
3034
3035 Examples
3036 --------
3037 >>> df = pd.DataFrame(np.random.randn(10, 4))
3038 >>> df.style.set_properties(color="white", align="right") # doctest: +SKIP
3039 >>> df.style.set_properties(**{'background-color': 'yellow'}) # doctest: +SKIP
3040
3041 See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for
3042 more details.
3043 """
3044 values = "".join([f"{p}: {v};" for p, v in kwargs.items()])
3045 return self.map(lambda x: values, subset=subset)
3046
3047 @Substitution(subset=subset_args)
3048 def bar( # pylint: disable=disallowed-name
3049 self,
3050 subset: Subset | None = None,
3051 axis: Axis | None = 0,
3052 *,
3053 color: str | list | tuple | None = None,
3054 cmap: Any | None = None,
3055 width: float = 100,
3056 height: float = 100,
3057 align: str | float | Callable = "mid",
3058 vmin: float | None = None,
3059 vmax: float | None = None,
3060 props: str = "width: 10em;",
3061 ) -> Styler:
3062 """
3063 Draw bar chart in the cell backgrounds.
3064
3065 .. versionchanged:: 1.4.0
3066
3067 Parameters
3068 ----------
3069 %(subset)s
3070 axis : {0 or 'index', 1 or 'columns', None}, default 0
3071 Apply to each column (``axis=0`` or ``'index'``), to each row
3072 (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
3073 with ``axis=None``.
3074 color : str or 2-tuple/list
3075 If a str is passed, the color is the same for both
3076 negative and positive numbers. If 2-tuple/list is used, the
3077 first element is the color_negative and the second is the
3078 color_positive (eg: ['#d65f5f', '#5fba7d']).
3079 cmap : str, matplotlib.cm.ColorMap
3080 A string name of a matplotlib Colormap, or a Colormap object. Cannot be
3081 used together with ``color``.
3082
3083 .. versionadded:: 1.4.0
3084 width : float, default 100
3085 The percentage of the cell, measured from the left, in which to draw the
3086 bars, in [0, 100].
3087 height : float, default 100
3088 The percentage height of the bar in the cell, centrally aligned, in [0,100].
3089
3090 .. versionadded:: 1.4.0
3091 align : str, int, float, callable, default 'mid'
3092 How to align the bars within the cells relative to a width adjusted center.
3093 If string must be one of:
3094
3095 - 'left' : bars are drawn rightwards from the minimum data value.
3096 - 'right' : bars are drawn leftwards from the maximum data value.
3097 - 'zero' : a value of zero is located at the center of the cell.
3098 - 'mid' : a value of (max-min)/2 is located at the center of the cell,
3099 or if all values are negative (positive) the zero is
3100 aligned at the right (left) of the cell.
3101 - 'mean' : the mean value of the data is located at the center of the cell.
3102
3103 If a float or integer is given this will indicate the center of the cell.
3104
3105 If a callable should take a 1d or 2d array and return a scalar.
3106
3107 .. versionchanged:: 1.4.0
3108
3109 vmin : float, optional
3110 Minimum bar value, defining the left hand limit
3111 of the bar drawing range, lower values are clipped to `vmin`.
3112 When None (default): the minimum value of the data will be used.
3113 vmax : float, optional
3114 Maximum bar value, defining the right hand limit
3115 of the bar drawing range, higher values are clipped to `vmax`.
3116 When None (default): the maximum value of the data will be used.
3117 props : str, optional
3118 The base CSS of the cell that is extended to add the bar chart. Defaults to
3119 `"width: 10em;"`.
3120
3121 .. versionadded:: 1.4.0
3122
3123 Returns
3124 -------
3125 Styler
3126
3127 Notes
3128 -----
3129 This section of the user guide:
3130 `Table Visualization <../../user_guide/style.ipynb>`_ gives
3131 a number of examples for different settings and color coordination.
3132
3133 Examples
3134 --------
3135 >>> df = pd.DataFrame({'A': [1, 2, 3, 4], 'B': [3, 4, 5, 6]})
3136 >>> df.style.bar(subset=['A'], color='gray') # doctest: +SKIP
3137 """
3138 if color is None and cmap is None:
3139 color = "#d65f5f"
3140 elif color is not None and cmap is not None:
3141 raise ValueError("`color` and `cmap` cannot both be given")
3142 elif color is not None:
3143 if (isinstance(color, (list, tuple)) and len(color) > 2) or not isinstance(
3144 color, (str, list, tuple)
3145 ):
3146 raise ValueError(
3147 "`color` must be string or list or tuple of 2 strings,"
3148 "(eg: color=['#d65f5f', '#5fba7d'])"
3149 )
3150
3151 if not 0 <= width <= 100:
3152 raise ValueError(f"`width` must be a value in [0, 100], got {width}")
3153 if not 0 <= height <= 100:
3154 raise ValueError(f"`height` must be a value in [0, 100], got {height}")
3155
3156 if subset is None:
3157 subset = self._get_numeric_subset_default()
3158
3159 self.apply(
3160 _bar,
3161 subset=subset,
3162 axis=axis,
3163 align=align,
3164 colors=color,
3165 cmap=cmap,
3166 width=width / 100,
3167 height=height / 100,
3168 vmin=vmin,
3169 vmax=vmax,
3170 base_css=props,
3171 )
3172
3173 return self
3174
3175 @Substitution(
3176 subset=subset_args,
3177 props=properties_args,
3178 color=coloring_args.format(default="red"),
3179 )
3180 def highlight_null(
3181 self,
3182 color: str = "red",
3183 subset: Subset | None = None,
3184 props: str | None = None,
3185 ) -> Styler:
3186 """
3187 Highlight missing values with a style.
3188
3189 Parameters
3190 ----------
3191 %(color)s
3192
3193 .. versionadded:: 1.5.0
3194
3195 %(subset)s
3196
3197 %(props)s
3198
3199 .. versionadded:: 1.3.0
3200
3201 Returns
3202 -------
3203 Styler
3204
3205 See Also
3206 --------
3207 Styler.highlight_max: Highlight the maximum with a style.
3208 Styler.highlight_min: Highlight the minimum with a style.
3209 Styler.highlight_between: Highlight a defined range with a style.
3210 Styler.highlight_quantile: Highlight values defined by a quantile with a style.
3211
3212 Examples
3213 --------
3214 >>> df = pd.DataFrame({'A': [1, 2], 'B': [3, np.nan]})
3215 >>> df.style.highlight_null(color='yellow') # doctest: +SKIP
3216
3217 Please see:
3218 `Table Visualization <../../user_guide/style.ipynb>`_ for more examples.
3219 """
3220
3221 def f(data: DataFrame, props: str) -> np.ndarray:
3222 return np.where(pd.isna(data).to_numpy(), props, "")
3223
3224 if props is None:
3225 props = f"background-color: {color};"
3226 return self.apply(f, axis=None, subset=subset, props=props)
3227
3228 @Substitution(
3229 subset=subset_args,
3230 color=coloring_args.format(default="yellow"),
3231 props=properties_args,
3232 )
3233 def highlight_max(
3234 self,
3235 subset: Subset | None = None,
3236 color: str = "yellow",
3237 axis: Axis | None = 0,
3238 props: str | None = None,
3239 ) -> Styler:
3240 """
3241 Highlight the maximum with a style.
3242
3243 Parameters
3244 ----------
3245 %(subset)s
3246 %(color)s
3247 axis : {0 or 'index', 1 or 'columns', None}, default 0
3248 Apply to each column (``axis=0`` or ``'index'``), to each row
3249 (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
3250 with ``axis=None``.
3251 %(props)s
3252
3253 .. versionadded:: 1.3.0
3254
3255 Returns
3256 -------
3257 Styler
3258
3259 See Also
3260 --------
3261 Styler.highlight_null: Highlight missing values with a style.
3262 Styler.highlight_min: Highlight the minimum with a style.
3263 Styler.highlight_between: Highlight a defined range with a style.
3264 Styler.highlight_quantile: Highlight values defined by a quantile with a style.
3265
3266 Examples
3267 --------
3268 >>> df = pd.DataFrame({'A': [2, 1], 'B': [3, 4]})
3269 >>> df.style.highlight_max(color='yellow') # doctest: +SKIP
3270
3271 Please see:
3272 `Table Visualization <../../user_guide/style.ipynb>`_ for more examples.
3273 """
3274
3275 if props is None:
3276 props = f"background-color: {color};"
3277 return self.apply(
3278 partial(_highlight_value, op="max"),
3279 axis=axis,
3280 subset=subset,
3281 props=props,
3282 )
3283
3284 @Substitution(
3285 subset=subset_args,
3286 color=coloring_args.format(default="yellow"),
3287 props=properties_args,
3288 )
3289 def highlight_min(
3290 self,
3291 subset: Subset | None = None,
3292 color: str = "yellow",
3293 axis: Axis | None = 0,
3294 props: str | None = None,
3295 ) -> Styler:
3296 """
3297 Highlight the minimum with a style.
3298
3299 Parameters
3300 ----------
3301 %(subset)s
3302 %(color)s
3303 axis : {0 or 'index', 1 or 'columns', None}, default 0
3304 Apply to each column (``axis=0`` or ``'index'``), to each row
3305 (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
3306 with ``axis=None``.
3307 %(props)s
3308
3309 .. versionadded:: 1.3.0
3310
3311 Returns
3312 -------
3313 Styler
3314
3315 See Also
3316 --------
3317 Styler.highlight_null: Highlight missing values with a style.
3318 Styler.highlight_max: Highlight the maximum with a style.
3319 Styler.highlight_between: Highlight a defined range with a style.
3320 Styler.highlight_quantile: Highlight values defined by a quantile with a style.
3321
3322 Examples
3323 --------
3324 >>> df = pd.DataFrame({'A': [2, 1], 'B': [3, 4]})
3325 >>> df.style.highlight_min(color='yellow') # doctest: +SKIP
3326
3327 Please see:
3328 `Table Visualization <../../user_guide/style.ipynb>`_ for more examples.
3329 """
3330
3331 if props is None:
3332 props = f"background-color: {color};"
3333 return self.apply(
3334 partial(_highlight_value, op="min"),
3335 axis=axis,
3336 subset=subset,
3337 props=props,
3338 )
3339
3340 @Substitution(
3341 subset=subset_args,
3342 color=coloring_args.format(default="yellow"),
3343 props=properties_args,
3344 )
3345 def highlight_between(
3346 self,
3347 subset: Subset | None = None,
3348 color: str = "yellow",
3349 axis: Axis | None = 0,
3350 left: Scalar | Sequence | None = None,
3351 right: Scalar | Sequence | None = None,
3352 inclusive: IntervalClosedType = "both",
3353 props: str | None = None,
3354 ) -> Styler:
3355 """
3356 Highlight a defined range with a style.
3357
3358 .. versionadded:: 1.3.0
3359
3360 Parameters
3361 ----------
3362 %(subset)s
3363 %(color)s
3364 axis : {0 or 'index', 1 or 'columns', None}, default 0
3365 If ``left`` or ``right`` given as sequence, axis along which to apply those
3366 boundaries. See examples.
3367 left : scalar or datetime-like, or sequence or array-like, default None
3368 Left bound for defining the range.
3369 right : scalar or datetime-like, or sequence or array-like, default None
3370 Right bound for defining the range.
3371 inclusive : {'both', 'neither', 'left', 'right'}
3372 Identify whether bounds are closed or open.
3373 %(props)s
3374
3375 Returns
3376 -------
3377 Styler
3378
3379 See Also
3380 --------
3381 Styler.highlight_null: Highlight missing values with a style.
3382 Styler.highlight_max: Highlight the maximum with a style.
3383 Styler.highlight_min: Highlight the minimum with a style.
3384 Styler.highlight_quantile: Highlight values defined by a quantile with a style.
3385
3386 Notes
3387 -----
3388 If ``left`` is ``None`` only the right bound is applied.
3389 If ``right`` is ``None`` only the left bound is applied. If both are ``None``
3390 all values are highlighted.
3391
3392 ``axis`` is only needed if ``left`` or ``right`` are provided as a sequence or
3393 an array-like object for aligning the shapes. If ``left`` and ``right`` are
3394 both scalars then all ``axis`` inputs will give the same result.
3395
3396 This function only works with compatible ``dtypes``. For example a datetime-like
3397 region can only use equivalent datetime-like ``left`` and ``right`` arguments.
3398 Use ``subset`` to control regions which have multiple ``dtypes``.
3399
3400 Examples
3401 --------
3402 Basic usage
3403
3404 >>> df = pd.DataFrame({
3405 ... 'One': [1.2, 1.6, 1.5],
3406 ... 'Two': [2.9, 2.1, 2.5],
3407 ... 'Three': [3.1, 3.2, 3.8],
3408 ... })
3409 >>> df.style.highlight_between(left=2.1, right=2.9) # doctest: +SKIP
3410
3411 .. figure:: ../../_static/style/hbetw_basic.png
3412
3413 Using a range input sequence along an ``axis``, in this case setting a ``left``
3414 and ``right`` for each column individually
3415
3416 >>> df.style.highlight_between(left=[1.4, 2.4, 3.4], right=[1.6, 2.6, 3.6],
3417 ... axis=1, color="#fffd75") # doctest: +SKIP
3418
3419 .. figure:: ../../_static/style/hbetw_seq.png
3420
3421 Using ``axis=None`` and providing the ``left`` argument as an array that
3422 matches the input DataFrame, with a constant ``right``
3423
3424 >>> df.style.highlight_between(left=[[2,2,3],[2,2,3],[3,3,3]], right=3.5,
3425 ... axis=None, color="#fffd75") # doctest: +SKIP
3426
3427 .. figure:: ../../_static/style/hbetw_axNone.png
3428
3429 Using ``props`` instead of default background coloring
3430
3431 >>> df.style.highlight_between(left=1.5, right=3.5,
3432 ... props='font-weight:bold;color:#e83e8c') # doctest: +SKIP
3433
3434 .. figure:: ../../_static/style/hbetw_props.png
3435 """
3436 if props is None:
3437 props = f"background-color: {color};"
3438 return self.apply(
3439 _highlight_between,
3440 axis=axis,
3441 subset=subset,
3442 props=props,
3443 left=left,
3444 right=right,
3445 inclusive=inclusive,
3446 )
3447
3448 @Substitution(
3449 subset=subset_args,
3450 color=coloring_args.format(default="yellow"),
3451 props=properties_args,
3452 )
3453 def highlight_quantile(
3454 self,
3455 subset: Subset | None = None,
3456 color: str = "yellow",
3457 axis: Axis | None = 0,
3458 q_left: float = 0.0,
3459 q_right: float = 1.0,
3460 interpolation: QuantileInterpolation = "linear",
3461 inclusive: IntervalClosedType = "both",
3462 props: str | None = None,
3463 ) -> Styler:
3464 """
3465 Highlight values defined by a quantile with a style.
3466
3467 .. versionadded:: 1.3.0
3468
3469 Parameters
3470 ----------
3471 %(subset)s
3472 %(color)s
3473 axis : {0 or 'index', 1 or 'columns', None}, default 0
3474 Axis along which to determine and highlight quantiles. If ``None`` quantiles
3475 are measured over the entire DataFrame. See examples.
3476 q_left : float, default 0
3477 Left bound, in [0, q_right), for the target quantile range.
3478 q_right : float, default 1
3479 Right bound, in (q_left, 1], for the target quantile range.
3480 interpolation : {'linear', 'lower', 'higher', 'midpoint', 'nearest'}
3481 Argument passed to ``Series.quantile`` or ``DataFrame.quantile`` for
3482 quantile estimation.
3483 inclusive : {'both', 'neither', 'left', 'right'}
3484 Identify whether quantile bounds are closed or open.
3485 %(props)s
3486
3487 Returns
3488 -------
3489 Styler
3490
3491 See Also
3492 --------
3493 Styler.highlight_null: Highlight missing values with a style.
3494 Styler.highlight_max: Highlight the maximum with a style.
3495 Styler.highlight_min: Highlight the minimum with a style.
3496 Styler.highlight_between: Highlight a defined range with a style.
3497
3498 Notes
3499 -----
3500 This function does not work with ``str`` dtypes.
3501
3502 Examples
3503 --------
3504 Using ``axis=None`` and apply a quantile to all collective data
3505
3506 >>> df = pd.DataFrame(np.arange(10).reshape(2,5) + 1)
3507 >>> df.style.highlight_quantile(axis=None, q_left=0.8, color="#fffd75")
3508 ... # doctest: +SKIP
3509
3510 .. figure:: ../../_static/style/hq_axNone.png
3511
3512 Or highlight quantiles row-wise or column-wise, in this case by row-wise
3513
3514 >>> df.style.highlight_quantile(axis=1, q_left=0.8, color="#fffd75")
3515 ... # doctest: +SKIP
3516
3517 .. figure:: ../../_static/style/hq_ax1.png
3518
3519 Use ``props`` instead of default background coloring
3520
3521 >>> df.style.highlight_quantile(axis=None, q_left=0.2, q_right=0.8,
3522 ... props='font-weight:bold;color:#e83e8c') # doctest: +SKIP
3523
3524 .. figure:: ../../_static/style/hq_props.png
3525 """
3526 subset_ = slice(None) if subset is None else subset
3527 subset_ = non_reducing_slice(subset_)
3528 data = self.data.loc[subset_]
3529
3530 # after quantile is found along axis, e.g. along rows,
3531 # applying the calculated quantile to alternate axis, e.g. to each column
3532 quantiles = [q_left, q_right]
3533 if axis is None:
3534 q = Series(data.to_numpy().ravel()).quantile(
3535 q=quantiles, interpolation=interpolation
3536 )
3537 axis_apply: int | None = None
3538 else:
3539 axis = self.data._get_axis_number(axis)
3540 q = data.quantile(
3541 axis=axis, numeric_only=False, q=quantiles, interpolation=interpolation
3542 )
3543 axis_apply = 1 - axis
3544
3545 if props is None:
3546 props = f"background-color: {color};"
3547 return self.apply(
3548 _highlight_between,
3549 axis=axis_apply,
3550 subset=subset,
3551 props=props,
3552 left=q.iloc[0],
3553 right=q.iloc[1],
3554 inclusive=inclusive,
3555 )
3556
3557 @classmethod
3558 def from_custom_template(
3559 cls,
3560 searchpath: Sequence[str],
3561 html_table: str | None = None,
3562 html_style: str | None = None,
3563 ) -> type[Styler]:
3564 """
3565 Factory function for creating a subclass of ``Styler``.
3566
3567 Uses custom templates and Jinja environment.
3568
3569 .. versionchanged:: 1.3.0
3570
3571 Parameters
3572 ----------
3573 searchpath : str or list
3574 Path or paths of directories containing the templates.
3575 html_table : str
3576 Name of your custom template to replace the html_table template.
3577
3578 .. versionadded:: 1.3.0
3579
3580 html_style : str
3581 Name of your custom template to replace the html_style template.
3582
3583 .. versionadded:: 1.3.0
3584
3585 Returns
3586 -------
3587 MyStyler : subclass of Styler
3588 Has the correct ``env``,``template_html``, ``template_html_table`` and
3589 ``template_html_style`` class attributes set.
3590
3591 Examples
3592 --------
3593 >>> from pandas.io.formats.style import Styler
3594 >>> EasyStyler = Styler.from_custom_template("path/to/template",
3595 ... "template.tpl",
3596 ... ) # doctest: +SKIP
3597 >>> df = pd.DataFrame({"A": [1, 2]})
3598 >>> EasyStyler(df) # doctest: +SKIP
3599
3600 Please see:
3601 `Table Visualization <../../user_guide/style.ipynb>`_ for more examples.
3602 """
3603 loader = jinja2.ChoiceLoader([jinja2.FileSystemLoader(searchpath), cls.loader])
3604
3605 # mypy doesn't like dynamically-defined classes
3606 # error: Variable "cls" is not valid as a type
3607 # error: Invalid base class "cls"
3608 class MyStyler(cls): # type: ignore[valid-type,misc]
3609 env = jinja2.Environment(loader=loader)
3610 if html_table:
3611 template_html_table = env.get_template(html_table)
3612 if html_style:
3613 template_html_style = env.get_template(html_style)
3614
3615 return MyStyler
3616
3617 def pipe(self, func: Callable, *args, **kwargs):
3618 """
3619 Apply ``func(self, *args, **kwargs)``, and return the result.
3620
3621 Parameters
3622 ----------
3623 func : function
3624 Function to apply to the Styler. Alternatively, a
3625 ``(callable, keyword)`` tuple where ``keyword`` is a string
3626 indicating the keyword of ``callable`` that expects the Styler.
3627 *args : optional
3628 Arguments passed to `func`.
3629 **kwargs : optional
3630 A dictionary of keyword arguments passed into ``func``.
3631
3632 Returns
3633 -------
3634 object :
3635 The value returned by ``func``.
3636
3637 See Also
3638 --------
3639 DataFrame.pipe : Analogous method for DataFrame.
3640 Styler.apply : Apply a CSS-styling function column-wise, row-wise, or
3641 table-wise.
3642
3643 Notes
3644 -----
3645 Like :meth:`DataFrame.pipe`, this method can simplify the
3646 application of several user-defined functions to a styler. Instead
3647 of writing:
3648
3649 .. code-block:: python
3650
3651 f(g(df.style.format(precision=3), arg1=a), arg2=b, arg3=c)
3652
3653 users can write:
3654
3655 .. code-block:: python
3656
3657 (df.style.format(precision=3)
3658 .pipe(g, arg1=a)
3659 .pipe(f, arg2=b, arg3=c))
3660
3661 In particular, this allows users to define functions that take a
3662 styler object, along with other parameters, and return the styler after
3663 making styling changes (such as calling :meth:`Styler.apply` or
3664 :meth:`Styler.set_properties`).
3665
3666 Examples
3667 --------
3668
3669 **Common Use**
3670
3671 A common usage pattern is to pre-define styling operations which
3672 can be easily applied to a generic styler in a single ``pipe`` call.
3673
3674 >>> def some_highlights(styler, min_color="red", max_color="blue"):
3675 ... styler.highlight_min(color=min_color, axis=None)
3676 ... styler.highlight_max(color=max_color, axis=None)
3677 ... styler.highlight_null()
3678 ... return styler
3679 >>> df = pd.DataFrame([[1, 2, 3, pd.NA], [pd.NA, 4, 5, 6]], dtype="Int64")
3680 >>> df.style.pipe(some_highlights, min_color="green") # doctest: +SKIP
3681
3682 .. figure:: ../../_static/style/df_pipe_hl.png
3683
3684 Since the method returns a ``Styler`` object it can be chained with other
3685 methods as if applying the underlying highlighters directly.
3686
3687 >>> (df.style.format("{:.1f}")
3688 ... .pipe(some_highlights, min_color="green")
3689 ... .highlight_between(left=2, right=5)) # doctest: +SKIP
3690
3691 .. figure:: ../../_static/style/df_pipe_hl2.png
3692
3693 **Advanced Use**
3694
3695 Sometimes it may be necessary to pre-define styling functions, but in the case
3696 where those functions rely on the styler, data or context. Since
3697 ``Styler.use`` and ``Styler.export`` are designed to be non-data dependent,
3698 they cannot be used for this purpose. Additionally the ``Styler.apply``
3699 and ``Styler.format`` type methods are not context aware, so a solution
3700 is to use ``pipe`` to dynamically wrap this functionality.
3701
3702 Suppose we want to code a generic styling function that highlights the final
3703 level of a MultiIndex. The number of levels in the Index is dynamic so we
3704 need the ``Styler`` context to define the level.
3705
3706 >>> def highlight_last_level(styler):
3707 ... return styler.apply_index(
3708 ... lambda v: "background-color: pink; color: yellow", axis="columns",
3709 ... level=styler.columns.nlevels-1
3710 ... ) # doctest: +SKIP
3711 >>> df.columns = pd.MultiIndex.from_product([["A", "B"], ["X", "Y"]])
3712 >>> df.style.pipe(highlight_last_level) # doctest: +SKIP
3713
3714 .. figure:: ../../_static/style/df_pipe_applymap.png
3715
3716 Additionally suppose we want to highlight a column header if there is any
3717 missing data in that column.
3718 In this case we need the data object itself to determine the effect on the
3719 column headers.
3720
3721 >>> def highlight_header_missing(styler, level):
3722 ... def dynamic_highlight(s):
3723 ... return np.where(
3724 ... styler.data.isna().any(), "background-color: red;", ""
3725 ... )
3726 ... return styler.apply_index(dynamic_highlight, axis=1, level=level)
3727 >>> df.style.pipe(highlight_header_missing, level=1) # doctest: +SKIP
3728
3729 .. figure:: ../../_static/style/df_pipe_applydata.png
3730 """
3731 return com.pipe(self, func, *args, **kwargs)
3732
3733
3734def _validate_apply_axis_arg(
3735 arg: NDFrame | Sequence | np.ndarray,
3736 arg_name: str,
3737 dtype: Any | None,
3738 data: NDFrame,
3739) -> np.ndarray:
3740 """
3741 For the apply-type methods, ``axis=None`` creates ``data`` as DataFrame, and for
3742 ``axis=[1,0]`` it creates a Series. Where ``arg`` is expected as an element
3743 of some operator with ``data`` we must make sure that the two are compatible shapes,
3744 or raise.
3745
3746 Parameters
3747 ----------
3748 arg : sequence, Series or DataFrame
3749 the user input arg
3750 arg_name : string
3751 name of the arg for use in error messages
3752 dtype : numpy dtype, optional
3753 forced numpy dtype if given
3754 data : Series or DataFrame
3755 underling subset of Styler data on which operations are performed
3756
3757 Returns
3758 -------
3759 ndarray
3760 """
3761 dtype = {"dtype": dtype} if dtype else {}
3762 # raise if input is wrong for axis:
3763 if isinstance(arg, Series) and isinstance(data, DataFrame):
3764 raise ValueError(
3765 f"'{arg_name}' is a Series but underlying data for operations "
3766 f"is a DataFrame since 'axis=None'"
3767 )
3768 if isinstance(arg, DataFrame) and isinstance(data, Series):
3769 raise ValueError(
3770 f"'{arg_name}' is a DataFrame but underlying data for "
3771 f"operations is a Series with 'axis in [0,1]'"
3772 )
3773 if isinstance(arg, (Series, DataFrame)): # align indx / cols to data
3774 arg = arg.reindex_like(data, method=None).to_numpy(**dtype)
3775 else:
3776 arg = np.asarray(arg, **dtype)
3777 assert isinstance(arg, np.ndarray) # mypy requirement
3778 if arg.shape != data.shape: # check valid input
3779 raise ValueError(
3780 f"supplied '{arg_name}' is not correct shape for data over "
3781 f"selected 'axis': got {arg.shape}, "
3782 f"expected {data.shape}"
3783 )
3784 return arg
3785
3786
3787def _background_gradient(
3788 data,
3789 cmap: str | Colormap = "PuBu",
3790 low: float = 0,
3791 high: float = 0,
3792 text_color_threshold: float = 0.408,
3793 vmin: float | None = None,
3794 vmax: float | None = None,
3795 gmap: Sequence | np.ndarray | DataFrame | Series | None = None,
3796 text_only: bool = False,
3797):
3798 """
3799 Color background in a range according to the data or a gradient map
3800 """
3801 if gmap is None: # the data is used the gmap
3802 gmap = data.to_numpy(dtype=float, na_value=np.nan)
3803 else: # else validate gmap against the underlying data
3804 gmap = _validate_apply_axis_arg(gmap, "gmap", float, data)
3805
3806 with _mpl(Styler.background_gradient) as (_, _matplotlib):
3807 smin = np.nanmin(gmap) if vmin is None else vmin
3808 smax = np.nanmax(gmap) if vmax is None else vmax
3809 rng = smax - smin
3810 # extend lower / upper bounds, compresses color range
3811 norm = _matplotlib.colors.Normalize(smin - (rng * low), smax + (rng * high))
3812
3813 if cmap is None:
3814 rgbas = _matplotlib.colormaps[_matplotlib.rcParams["image.cmap"]](
3815 norm(gmap)
3816 )
3817 else:
3818 rgbas = _matplotlib.colormaps.get_cmap(cmap)(norm(gmap))
3819
3820 def relative_luminance(rgba) -> float:
3821 """
3822 Calculate relative luminance of a color.
3823
3824 The calculation adheres to the W3C standards
3825 (https://www.w3.org/WAI/GL/wiki/Relative_luminance)
3826
3827 Parameters
3828 ----------
3829 color : rgb or rgba tuple
3830
3831 Returns
3832 -------
3833 float
3834 The relative luminance as a value from 0 to 1
3835 """
3836 r, g, b = (
3837 x / 12.92 if x <= 0.04045 else ((x + 0.055) / 1.055) ** 2.4
3838 for x in rgba[:3]
3839 )
3840 return 0.2126 * r + 0.7152 * g + 0.0722 * b
3841
3842 def css(rgba, text_only) -> str:
3843 if not text_only:
3844 dark = relative_luminance(rgba) < text_color_threshold
3845 text_color = "#f1f1f1" if dark else "#000000"
3846 return (
3847 f"background-color: {_matplotlib.colors.rgb2hex(rgba)};"
3848 f"color: {text_color};"
3849 )
3850 else:
3851 return f"color: {_matplotlib.colors.rgb2hex(rgba)};"
3852
3853 if data.ndim == 1:
3854 return [css(rgba, text_only) for rgba in rgbas]
3855 else:
3856 return DataFrame(
3857 [[css(rgba, text_only) for rgba in row] for row in rgbas],
3858 index=data.index,
3859 columns=data.columns,
3860 )
3861
3862
3863def _highlight_between(
3864 data: NDFrame,
3865 props: str,
3866 left: Scalar | Sequence | np.ndarray | NDFrame | None = None,
3867 right: Scalar | Sequence | np.ndarray | NDFrame | None = None,
3868 inclusive: bool | str = True,
3869) -> np.ndarray:
3870 """
3871 Return an array of css props based on condition of data values within given range.
3872 """
3873 if np.iterable(left) and not isinstance(left, str):
3874 left = _validate_apply_axis_arg(left, "left", None, data)
3875
3876 if np.iterable(right) and not isinstance(right, str):
3877 right = _validate_apply_axis_arg(right, "right", None, data)
3878
3879 # get ops with correct boundary attribution
3880 if inclusive == "both":
3881 ops = (operator.ge, operator.le)
3882 elif inclusive == "neither":
3883 ops = (operator.gt, operator.lt)
3884 elif inclusive == "left":
3885 ops = (operator.ge, operator.lt)
3886 elif inclusive == "right":
3887 ops = (operator.gt, operator.le)
3888 else:
3889 raise ValueError(
3890 f"'inclusive' values can be 'both', 'left', 'right', or 'neither' "
3891 f"got {inclusive}"
3892 )
3893
3894 g_left = (
3895 # error: Argument 2 to "ge" has incompatible type "Union[str, float,
3896 # Period, Timedelta, Interval[Any], datetime64, timedelta64, datetime,
3897 # Sequence[Any], ndarray[Any, Any], NDFrame]"; expected "Union
3898 # [SupportsDunderLE, SupportsDunderGE, SupportsDunderGT, SupportsDunderLT]"
3899 ops[0](data, left) # type: ignore[arg-type]
3900 if left is not None
3901 else np.full(data.shape, True, dtype=bool)
3902 )
3903 if isinstance(g_left, (DataFrame, Series)):
3904 g_left = g_left.where(pd.notna(g_left), False)
3905 l_right = (
3906 # error: Argument 2 to "le" has incompatible type "Union[str, float,
3907 # Period, Timedelta, Interval[Any], datetime64, timedelta64, datetime,
3908 # Sequence[Any], ndarray[Any, Any], NDFrame]"; expected "Union
3909 # [SupportsDunderLE, SupportsDunderGE, SupportsDunderGT, SupportsDunderLT]"
3910 ops[1](data, right) # type: ignore[arg-type]
3911 if right is not None
3912 else np.full(data.shape, True, dtype=bool)
3913 )
3914 if isinstance(l_right, (DataFrame, Series)):
3915 l_right = l_right.where(pd.notna(l_right), False)
3916 return np.where(g_left & l_right, props, "")
3917
3918
3919def _highlight_value(data: DataFrame | Series, op: str, props: str) -> np.ndarray:
3920 """
3921 Return an array of css strings based on the condition of values matching an op.
3922 """
3923 value = getattr(data, op)(skipna=True)
3924 if isinstance(data, DataFrame): # min/max must be done twice to return scalar
3925 value = getattr(value, op)(skipna=True)
3926 cond = data == value
3927 cond = cond.where(pd.notna(cond), False)
3928 return np.where(cond, props, "")
3929
3930
3931def _bar(
3932 data: NDFrame,
3933 align: str | float | Callable,
3934 colors: str | list | tuple,
3935 cmap: Any,
3936 width: float,
3937 height: float,
3938 vmin: float | None,
3939 vmax: float | None,
3940 base_css: str,
3941):
3942 """
3943 Draw bar chart in data cells using HTML CSS linear gradient.
3944
3945 Parameters
3946 ----------
3947 data : Series or DataFrame
3948 Underling subset of Styler data on which operations are performed.
3949 align : str in {"left", "right", "mid", "zero", "mean"}, int, float, callable
3950 Method for how bars are structured or scalar value of centre point.
3951 colors : list-like of str
3952 Two listed colors as string in valid CSS.
3953 width : float in [0,1]
3954 The percentage of the cell, measured from left, where drawn bars will reside.
3955 height : float in [0,1]
3956 The percentage of the cell's height where drawn bars will reside, centrally
3957 aligned.
3958 vmin : float, optional
3959 Overwrite the minimum value of the window.
3960 vmax : float, optional
3961 Overwrite the maximum value of the window.
3962 base_css : str
3963 Additional CSS that is included in the cell before bars are drawn.
3964 """
3965
3966 def css_bar(start: float, end: float, color: str) -> str:
3967 """
3968 Generate CSS code to draw a bar from start to end in a table cell.
3969
3970 Uses linear-gradient.
3971
3972 Parameters
3973 ----------
3974 start : float
3975 Relative positional start of bar coloring in [0,1]
3976 end : float
3977 Relative positional end of the bar coloring in [0,1]
3978 color : str
3979 CSS valid color to apply.
3980
3981 Returns
3982 -------
3983 str : The CSS applicable to the cell.
3984
3985 Notes
3986 -----
3987 Uses ``base_css`` from outer scope.
3988 """
3989 cell_css = base_css
3990 if end > start:
3991 cell_css += "background: linear-gradient(90deg,"
3992 if start > 0:
3993 cell_css += f" transparent {start*100:.1f}%, {color} {start*100:.1f}%,"
3994 cell_css += f" {color} {end*100:.1f}%, transparent {end*100:.1f}%)"
3995 return cell_css
3996
3997 def css_calc(x, left: float, right: float, align: str, color: str | list | tuple):
3998 """
3999 Return the correct CSS for bar placement based on calculated values.
4000
4001 Parameters
4002 ----------
4003 x : float
4004 Value which determines the bar placement.
4005 left : float
4006 Value marking the left side of calculation, usually minimum of data.
4007 right : float
4008 Value marking the right side of the calculation, usually maximum of data
4009 (left < right).
4010 align : {"left", "right", "zero", "mid"}
4011 How the bars will be positioned.
4012 "left", "right", "zero" can be used with any values for ``left``, ``right``.
4013 "mid" can only be used where ``left <= 0`` and ``right >= 0``.
4014 "zero" is used to specify a center when all values ``x``, ``left``,
4015 ``right`` are translated, e.g. by say a mean or median.
4016
4017 Returns
4018 -------
4019 str : Resultant CSS with linear gradient.
4020
4021 Notes
4022 -----
4023 Uses ``colors``, ``width`` and ``height`` from outer scope.
4024 """
4025 if pd.isna(x):
4026 return base_css
4027
4028 if isinstance(color, (list, tuple)):
4029 color = color[0] if x < 0 else color[1]
4030 assert isinstance(color, str) # mypy redefinition
4031
4032 x = left if x < left else x
4033 x = right if x > right else x # trim data if outside of the window
4034
4035 start: float = 0
4036 end: float = 1
4037
4038 if align == "left":
4039 # all proportions are measured from the left side between left and right
4040 end = (x - left) / (right - left)
4041
4042 elif align == "right":
4043 # all proportions are measured from the right side between left and right
4044 start = (x - left) / (right - left)
4045
4046 else:
4047 z_frac: float = 0.5 # location of zero based on the left-right range
4048 if align == "zero":
4049 # all proportions are measured from the center at zero
4050 limit: float = max(abs(left), abs(right))
4051 left, right = -limit, limit
4052 elif align == "mid":
4053 # bars drawn from zero either leftwards or rightwards with center at mid
4054 mid: float = (left + right) / 2
4055 z_frac = (
4056 -mid / (right - left) + 0.5 if mid < 0 else -left / (right - left)
4057 )
4058
4059 if x < 0:
4060 start, end = (x - left) / (right - left), z_frac
4061 else:
4062 start, end = z_frac, (x - left) / (right - left)
4063
4064 ret = css_bar(start * width, end * width, color)
4065 if height < 1 and "background: linear-gradient(" in ret:
4066 return (
4067 ret + f" no-repeat center; background-size: 100% {height * 100:.1f}%;"
4068 )
4069 else:
4070 return ret
4071
4072 values = data.to_numpy()
4073 # A tricky way to address the issue where np.nanmin/np.nanmax fail to handle pd.NA.
4074 left = np.nanmin(data.min(skipna=True)) if vmin is None else vmin
4075 right = np.nanmax(data.max(skipna=True)) if vmax is None else vmax
4076 z: float = 0 # adjustment to translate data
4077
4078 if align == "mid":
4079 if left >= 0: # "mid" is documented to act as "left" if all values positive
4080 align, left = "left", 0 if vmin is None else vmin
4081 elif right <= 0: # "mid" is documented to act as "right" if all values negative
4082 align, right = "right", 0 if vmax is None else vmax
4083 elif align == "mean":
4084 z, align = np.nanmean(values), "zero"
4085 elif callable(align):
4086 z, align = align(values), "zero"
4087 elif isinstance(align, (float, int)):
4088 z, align = float(align), "zero"
4089 elif align not in ("left", "right", "zero"):
4090 raise ValueError(
4091 "`align` should be in {'left', 'right', 'mid', 'mean', 'zero'} or be a "
4092 "value defining the center line or a callable that returns a float"
4093 )
4094
4095 rgbas = None
4096 if cmap is not None:
4097 # use the matplotlib colormap input
4098 with _mpl(Styler.bar) as (_, _matplotlib):
4099 cmap = (
4100 _matplotlib.colormaps[cmap]
4101 if isinstance(cmap, str)
4102 else cmap # assumed to be a Colormap instance as documented
4103 )
4104 norm = _matplotlib.colors.Normalize(left, right)
4105 rgbas = cmap(norm(values))
4106 if data.ndim == 1:
4107 rgbas = [_matplotlib.colors.rgb2hex(rgba) for rgba in rgbas]
4108 else:
4109 rgbas = [
4110 [_matplotlib.colors.rgb2hex(rgba) for rgba in row] for row in rgbas
4111 ]
4112
4113 assert isinstance(align, str) # mypy: should now be in [left, right, mid, zero]
4114 if data.ndim == 1:
4115 return [
4116 css_calc(
4117 x - z, left - z, right - z, align, colors if rgbas is None else rgbas[i]
4118 )
4119 for i, x in enumerate(values)
4120 ]
4121 else:
4122 return np.array(
4123 [
4124 [
4125 css_calc(
4126 x - z,
4127 left - z,
4128 right - z,
4129 align,
4130 colors if rgbas is None else rgbas[i][j],
4131 )
4132 for j, x in enumerate(row)
4133 ]
4134 for i, row in enumerate(values)
4135 ]
4136 )