""" Module for applying conditional formatting to DataFrames and Series. """ from __future__ import annotations import copy from functools import partial import operator from typing import ( TYPE_CHECKING, Concatenate, Self, overload, ) import numpy as np from pandas._config import get_option from pandas.compat._optional import import_optional_dependency import pandas as pd from pandas import ( IndexSlice, RangeIndex, ) import pandas.core.common as com from pandas.core.frame import ( DataFrame, Series, ) from pandas.io.formats.format import save_to_buffer jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.") from pandas.io.formats.style_render import ( CSSProperties, CSSStyles, ExtFormatter, StylerRenderer, Subset, Tooltips, format_table_styles, maybe_convert_css_to_tuples, non_reducing_slice, refactor_levels, ) if TYPE_CHECKING: from collections.abc import ( Callable, Hashable, Sequence, ) from matplotlib.colors import Colormap from pandas._typing import ( Any, Axis, AxisInt, ExcelWriterMergeCells, FilePath, IndexLabel, IntervalClosedType, Level, P, QuantileInterpolation, Scalar, StorageOptions, T, WriteBuffer, WriteExcelBuffer, ) from pandas import ExcelWriter from pandas.core.generic import NDFrame class Styler(StylerRenderer): r""" Helps style a DataFrame or Series according to the data with HTML and CSS. This class provides methods for styling and formatting a Pandas DataFrame or Series. The styled output can be rendered as HTML or LaTeX, and it supports CSS-based styling, allowing users to control colors, font styles, and other visual aspects of tabular data. It is particularly useful for presenting DataFrame objects in a Jupyter Notebook environment or when exporting styled tables for reports and Parameters ---------- data : Series or DataFrame Data to be styled - either a Series or DataFrame. precision : int, optional Precision to round floats to. If not given defaults to ``pandas.options.styler.format.precision``. table_styles : list-like, default None List of {selector: (attr, value)} dicts; see Notes. uuid : str, default None A unique identifier to avoid CSS collisions; generated automatically. caption : str, tuple, default None String caption to attach to the table. Tuple only used for LaTeX dual captions. table_attributes : str, default None Items that show up in the opening ```` tag in addition to automatic (by default) id. cell_ids : bool, default True If True, each cell will have an ``id`` attribute in their HTML tag. The ``id`` takes the form ``T__row_col`` where ```` is the unique identifier, ```` is the row number and ```` is the column number. na_rep : str, optional Representation for missing values. If ``na_rep`` is None, no special formatting is applied, and falls back to ``pandas.options.styler.format.na_rep``. uuid_len : int, default 5 If ``uuid`` is not specified, the length of the ``uuid`` to randomly generate expressed in hex characters, in range [0, 32]. decimal : str, optional Character used as decimal separator for floats, complex and integers. If not given uses ``pandas.options.styler.format.decimal``. thousands : str, optional, default None Character used as thousands separator for floats, complex and integers. If not given uses ``pandas.options.styler.format.thousands``. escape : str, optional Use 'html' to replace the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in cell display string with HTML-safe sequences. Use 'latex' to replace the characters ``&``, ``%``, ``$``, ``#``, ``_``, ``{``, ``}``, ``~``, ``^``, and ``\`` in the cell display string with LaTeX-safe sequences. Use 'latex-math' to replace the characters the same way as in 'latex' mode, except for math substrings, which either are surrounded by two characters ``$`` or start with the character ``\(`` and end with ``\)``. If not given uses ``pandas.options.styler.format.escape``. formatter : str, callable, dict, optional Object to define how values are displayed. See ``Styler.format``. If not given uses ``pandas.options.styler.format.formatter``. Attributes ---------- index : data.index Index columns : data.columns Index env : Jinja2 jinja2.Environment template_html : Jinja2 Template template_html_table : Jinja2 Template template_html_style : Jinja2 Template template_latex : Jinja2 Template loader : Jinja2 Loader See Also -------- DataFrame.style : Return a Styler object containing methods for building a styled HTML representation for the DataFrame. Notes ----- .. warning:: ``Styler`` is primarily intended for use on safe input that you control. When using ``Styler`` on untrusted, user-provided input to serve HTML, you should set ``escape="html"`` to prevent security vulnerabilities. See the Jinja2 documentation on escaping HTML for more. Most styling will be done by passing style functions into ``Styler.apply`` or ``Styler.map``. Style functions should return values with strings containing CSS ``'attr: value'`` that will be applied to the indicated cells. If using in the Jupyter notebook, Styler has defined a ``_repr_html_`` to automatically render itself. Otherwise call Styler.to_html to get the generated HTML. CSS classes are attached to the generated HTML * Index and Column names include ``index_name`` and ``level`` where `k` is its level in a MultiIndex * Index label cells include * ``row_heading`` * ``row`` where `n` is the numeric position of the row * ``level`` where `k` is the level in a MultiIndex * Column label cells include * ``col_heading`` * ``col`` where `n` is the numeric position of the column * ``level`` where `k` is the level in a MultiIndex * Blank cells include ``blank`` * Data cells include ``data`` * Trimmed cells include ``col_trim`` or ``row_trim``. Any, or all, or these classes can be renamed by using the ``css_class_names`` argument in ``Styler.set_table_styles``, giving a value such as *{"row": "MY_ROW_CLASS", "col_trim": "", "row_trim": ""}*. Examples -------- >>> df = pd.DataFrame( ... [[1.0, 2.0, 3.0], [4, 5, 6]], index=["a", "b"], columns=["A", "B", "C"] ... ) >>> pd.io.formats.style.Styler( ... df, precision=2, caption="My table" ... ) # doctest: +SKIP Please see: `Table Visualization <../../user_guide/style.ipynb>`_ for more examples. """ def __init__( self, data: DataFrame | Series, precision: int | None = None, table_styles: CSSStyles | None = None, uuid: str | None = None, caption: str | tuple | list | None = None, table_attributes: str | None = None, cell_ids: bool = True, na_rep: str | None = None, uuid_len: int = 5, decimal: str | None = None, thousands: str | None = None, escape: str | None = None, formatter: ExtFormatter | None = None, ) -> None: super().__init__( data=data, uuid=uuid, uuid_len=uuid_len, table_styles=table_styles, table_attributes=table_attributes, caption=caption, cell_ids=cell_ids, precision=precision, ) # validate ordered args thousands = thousands or get_option("styler.format.thousands") decimal = decimal or get_option("styler.format.decimal") na_rep = na_rep or get_option("styler.format.na_rep") escape = escape or get_option("styler.format.escape") formatter = formatter or get_option("styler.format.formatter") # precision is handled by superclass as default for performance self.format( formatter=formatter, precision=precision, na_rep=na_rep, escape=escape, decimal=decimal, thousands=thousands, ) def concat(self, other: Styler) -> Styler: """ Append another Styler to combine the output into a single table. Parameters ---------- other : Styler The other Styler object which has already been styled and formatted. The data for this Styler must have the same columns as the original, and the number of index levels must also be the same to render correctly. Returns ------- Styler Instance of class with specified Styler appended. See Also -------- Styler.clear : Reset the ``Styler``, removing any previously applied styles. Styler.export : Export the styles applied to the current Styler. Notes ----- The purpose of this method is to extend existing styled dataframes with other metrics that may be useful but may not conform to the original's structure. For example adding a sub total row, or displaying metrics such as means, variance or counts. Styles that are applied using the ``apply``, ``map``, ``apply_index`` and ``map_index``, and formatting applied with ``format`` and ``format_index`` will be preserved. .. warning:: Only the output methods ``to_html``, ``to_string`` and ``to_latex`` currently work with concatenated Stylers. Other output methods, including ``to_excel``, **do not** work with concatenated Stylers. The following should be noted: - ``table_styles``, ``table_attributes``, ``caption`` and ``uuid`` are all inherited from the original Styler and not ``other``. - hidden columns and hidden index levels will be inherited from the original Styler - ``css`` will be inherited from the original Styler, and the value of keys ``data``, ``row_heading`` and ``row`` will be prepended with ``foot0_``. If more concats are chained, their styles will be prepended with ``foot1_``, ''foot_2'', etc., and if a concatenated style have another concatenated style, the second style will be prepended with ``foot{parent}_foot{child}_``. A common use case is to concatenate user defined functions with ``DataFrame.agg`` or with described statistics via ``DataFrame.describe``. See examples. Examples -------- A common use case is adding totals rows, or otherwise, via methods calculated in ``DataFrame.agg``. >>> df = pd.DataFrame( ... [[4, 6], [1, 9], [3, 4], [5, 5], [9, 6]], ... columns=["Mike", "Jim"], ... index=["Mon", "Tue", "Wed", "Thurs", "Fri"], ... ) >>> styler = df.style.concat(df.agg(["sum"]).style) # doctest: +SKIP .. figure:: ../../_static/style/footer_simple.png Since the concatenated object is a Styler the existing functionality can be used to conditionally format it as well as the original. >>> descriptors = df.agg(["sum", "mean", lambda s: s.dtype]) >>> descriptors.index = ["Total", "Average", "dtype"] >>> other = ( ... descriptors.style.highlight_max( ... axis=1, subset=(["Total", "Average"], slice(None)) ... ) ... .format(subset=("Average", slice(None)), precision=2, decimal=",") ... .map(lambda v: "font-weight: bold;") ... ) >>> styler = df.style.highlight_max(color="salmon").set_table_styles( ... [{"selector": ".foot_row0", "props": "border-top: 1px solid black;"}] ... ) >>> styler.concat(other) # doctest: +SKIP .. figure:: ../../_static/style/footer_extended.png When ``other`` has fewer index levels than the original Styler it is possible to extend the index in ``other``, with placeholder levels. >>> df = pd.DataFrame( ... [[1], [2]], index=pd.MultiIndex.from_product([[0], [1, 2]]) ... ) >>> descriptors = df.agg(["sum"]) >>> descriptors.index = pd.MultiIndex.from_product([[""], descriptors.index]) >>> df.style.concat(descriptors.style) # doctest: +SKIP """ if not isinstance(other, Styler): raise TypeError("`other` must be of type `Styler`") if not self.data.columns.equals(other.data.columns): raise ValueError("`other.data` must have same columns as `Styler.data`") if not self.data.index.nlevels == other.data.index.nlevels: raise ValueError( "number of index levels must be same in `other` " "as in `Styler`. See documentation for suggestions." ) self.concatenated.append(other) return self def _repr_html_(self) -> str | None: """ Hooks into Jupyter notebook rich display system, which calls _repr_html_ by default if an object is returned at the end of a cell. """ if get_option("styler.render.repr") == "html": return self.to_html() return None def _repr_latex_(self) -> str | None: if get_option("styler.render.repr") == "latex": return self.to_latex() return None def set_tooltips( self, ttips: DataFrame, props: CSSProperties | None = None, css_class: str | None = None, as_title_attribute: bool = False, ) -> Styler: """ Set the DataFrame of strings on ``Styler`` generating ``:hover`` tooltips. These string based tooltips are only applicable to ``
`` HTML elements, and cannot be used for column or index headers. Parameters ---------- ttips : DataFrame DataFrame containing strings that will be translated to tooltips, mapped by identical column and index values that must exist on the underlying Styler data. None, NaN values, and empty strings will be ignored and not affect the rendered HTML. props : list-like or str, optional List of (attr, value) tuples or a valid CSS string. If ``None`` adopts the internal default values described in notes. css_class : str, optional Name of the tooltip class used in CSS, should conform to HTML standards. Only useful if integrating tooltips with external CSS. If ``None`` uses the internal default value 'pd-t'. as_title_attribute : bool, default False Add the tooltip text as title attribute to resultant element. If True then props and css_class arguments are ignored. Returns ------- Styler Instance of class with DataFrame set for strings on ``Styler`` generating ``:hover`` tooltips. See Also -------- Styler.set_table_attributes : Set the table attributes added to the ```` HTML element. Styler.set_table_styles : Set the table styles included within the ``
... """ obj = self._copy(deepcopy=True) # manipulate table_styles on obj, not self if table_uuid: obj.set_uuid(table_uuid) if table_attributes: obj.set_table_attributes(table_attributes) if sparse_index is None: sparse_index = get_option("styler.sparse.index") if sparse_columns is None: sparse_columns = get_option("styler.sparse.columns") if bold_headers: obj.set_table_styles( [{"selector": "th", "props": "font-weight: bold;"}], overwrite=False ) if caption is not None: obj.set_caption(caption) # Build HTML string.. html = obj._render_html( sparse_index=sparse_index, sparse_columns=sparse_columns, max_rows=max_rows, max_cols=max_columns, exclude_styles=exclude_styles, encoding=encoding or get_option("styler.render.encoding"), doctype_html=doctype_html, **kwargs, ) return save_to_buffer( html, buf=buf, encoding=(encoding if buf is not None else None) ) @overload def to_string( self, buf: FilePath | WriteBuffer[str], *, encoding: str | None = ..., sparse_index: bool | None = ..., sparse_columns: bool | None = ..., max_rows: int | None = ..., max_columns: int | None = ..., delimiter: str = ..., ) -> None: ... @overload def to_string( self, buf: None = ..., *, encoding: str | None = ..., sparse_index: bool | None = ..., sparse_columns: bool | None = ..., max_rows: int | None = ..., max_columns: int | None = ..., delimiter: str = ..., ) -> str: ... def to_string( self, buf: FilePath | WriteBuffer[str] | None = None, *, encoding: str | None = None, sparse_index: bool | None = None, sparse_columns: bool | None = None, max_rows: int | None = None, max_columns: int | None = None, delimiter: str = " ", ) -> str | None: """ Write Styler to a file, buffer or string in text format. Parameters ---------- buf : str, path object, file-like object, optional String, path object (implementing ``os.PathLike[str]``), or file-like object implementing a string ``write()`` function. If ``None``, the result is returned as a string. encoding : str, optional Character encoding setting for file output (and meta tags if available). Defaults to ``pandas.options.styler.render.encoding`` value of "utf-8". sparse_index : bool, optional Whether to sparsify the display of a hierarchical index. Setting to False will display each explicit level element in a hierarchical key for each row. Defaults to ``pandas.options.styler.sparse.index`` value. sparse_columns : bool, optional Whether to sparsify the display of a hierarchical index. Setting to False will display each explicit level element in a hierarchical key for each column. Defaults to ``pandas.options.styler.sparse.columns`` value. max_rows : int, optional The maximum number of rows that will be rendered. Defaults to ``pandas.options.styler.render.max_rows``, which is None. max_columns : int, optional The maximum number of columns that will be rendered. Defaults to ``pandas.options.styler.render.max_columns``, which is None. Rows and columns may be reduced if the number of total elements is large. This value is set to ``pandas.options.styler.render.max_elements``, which is 262144 (18 bit browser rendering). delimiter : str, default single space The separator between data elements. Returns ------- str or None If `buf` is None, returns the result as a string. Otherwise returns `None`. See Also -------- DataFrame.to_string : Render a DataFrame to a console-friendly tabular output. Examples -------- >>> df = pd.DataFrame({"A": [1, 2], "B": [3, 4]}) >>> df.style.to_string() ' A B\\n0 1 3\\n1 2 4\\n' """ obj = self._copy(deepcopy=True) if sparse_index is None: sparse_index = get_option("styler.sparse.index") if sparse_columns is None: sparse_columns = get_option("styler.sparse.columns") text = obj._render_string( sparse_columns=sparse_columns, sparse_index=sparse_index, max_rows=max_rows, max_cols=max_columns, delimiter=delimiter, ) return save_to_buffer( text, buf=buf, encoding=(encoding if buf is not None else None) ) def set_td_classes(self, classes: DataFrame) -> Styler: """ Set the ``class`` attribute of ``
  A B
`` HTML elements. Parameters ---------- classes : DataFrame DataFrame containing strings that will be translated to CSS classes, mapped by identical column and index key values that must exist on the underlying Styler data. None, NaN values, and empty strings will be ignored and not affect the rendered HTML. Returns ------- Styler Instance of class with ``class`` attribute set for ```` HTML elements. See Also -------- Styler.set_table_styles: Set the table styles included within the ``' '' ' ' ' ' ' ' ' ' ' ' ' ' '
0
1
' """ if not classes.index.is_unique or not classes.columns.is_unique: raise KeyError( "Classes render only if `classes` has unique index and columns." ) classes = classes.reindex_like(self.data) for r, row_tup in enumerate(classes.itertuples()): for c, value in enumerate(row_tup[1:]): if not (pd.isna(value) or value == ""): self.cell_context[(r, c)] = str(value) return self def _update_ctx(self, attrs: DataFrame) -> None: """ Update the state of the ``Styler`` for data cells. Collects a mapping of {index_label: [('', ''), ..]}. Parameters ---------- attrs : DataFrame should contain strings of ': ;: ' Whitespace shouldn't matter and the final trailing ';' shouldn't matter. """ if not self.index.is_unique or not self.columns.is_unique: raise KeyError( "`Styler.apply` and `.map` are not compatible " "with non-unique index or columns." ) for cn in attrs.columns: j = self.columns.get_loc(cn) ser = attrs[cn] for rn, c in ser.items(): if not c or pd.isna(c): continue css_list = maybe_convert_css_to_tuples(c) i = self.index.get_loc(rn) self.ctx[(i, j)].extend(css_list) def _update_ctx_header(self, attrs: DataFrame, axis: AxisInt) -> None: """ Update the state of the ``Styler`` for header cells. Collects a mapping of {index_label: [('', ''), ..]}. Parameters ---------- attrs : Series Should contain strings of ': ;: ', and an integer index. Whitespace shouldn't matter and the final trailing ';' shouldn't matter. axis : int Identifies whether the ctx object being updated is the index or columns """ for j in attrs.columns: ser = attrs[j] for i, c in ser.items(): if not c or pd.isna(c): continue css_list = maybe_convert_css_to_tuples(c) if axis == 0: self.ctx_index[(i, j)].extend(css_list) else: self.ctx_columns[(j, i)].extend(css_list) def _copy(self, deepcopy: bool = False) -> Styler: """ Copies a Styler, allowing for deepcopy or shallow copy Copying a Styler aims to recreate a new Styler object which contains the same data and styles as the original. Data dependent attributes [copied and NOT exported]: - formatting (._display_funcs) - hidden index values or column values (.hidden_rows, .hidden_columns) - tooltips - cell_context (cell css classes) - ctx (cell css styles) - caption - concatenated stylers Non-data dependent attributes [copied and exported]: - css - hidden index state and hidden columns state (.hide_index_, .hide_columns_) - table_attributes - table_styles - applied styles (_todo) """ # GH 40675, 52728 styler = type(self)( self.data, # populates attributes 'data', 'columns', 'index' as shallow ) shallow = [ # simple string or boolean immutables "hide_index_", "hide_columns_", "hide_column_names", "hide_index_names", "table_attributes", "cell_ids", "caption", "uuid", "uuid_len", "template_latex", # also copy templates if these have been customised "template_html_style", "template_html_table", "template_html", ] deep = [ # nested lists or dicts "css", "concatenated", "_display_funcs", "_display_funcs_index", "_display_funcs_columns", "_display_funcs_index_names", "_display_funcs_column_names", "hidden_rows", "hidden_columns", "ctx", "ctx_index", "ctx_columns", "cell_context", "_todo", "table_styles", "tooltips", ] for attr in shallow: setattr(styler, attr, getattr(self, attr)) for attr in deep: val = getattr(self, attr) setattr(styler, attr, copy.deepcopy(val) if deepcopy else val) return styler def __copy__(self) -> Styler: return self._copy(deepcopy=False) def __deepcopy__(self, memo) -> Styler: return self._copy(deepcopy=True) def clear(self) -> None: """ Reset the ``Styler``, removing any previously applied styles. Returns None. See Also -------- Styler.apply : Apply a CSS-styling function column-wise, row-wise, or table-wise. Styler.export : Export the styles applied to the current Styler. Styler.map : Apply a CSS-styling function elementwise. Styler.use : Set the styles on the current Styler. Examples -------- >>> df = pd.DataFrame({"A": [1, 2], "B": [3, np.nan]}) After any added style: >>> df.style.highlight_null(color="yellow") # doctest: +SKIP Remove it with: >>> df.style.clear() # doctest: +SKIP Please see: `Table Visualization <../../user_guide/style.ipynb>`_ for more examples. """ # create default GH 40675 clean_copy = Styler(self.data, uuid=self.uuid) clean_attrs = [a for a in clean_copy.__dict__ if not callable(a)] self_attrs = [a for a in self.__dict__ if not callable(a)] # maybe more attrs for attr in clean_attrs: setattr(self, attr, getattr(clean_copy, attr)) for attr in set(self_attrs).difference(clean_attrs): delattr(self, attr) def _apply( self, func: Callable, axis: Axis | None = 0, subset: Subset | None = None, **kwargs, ) -> Styler: subset = slice(None) if subset is None else subset subset = non_reducing_slice(subset) data = self.data.loc[subset] if data.empty: result = DataFrame() elif axis is None: result = func(data, **kwargs) if not isinstance(result, DataFrame): if not isinstance(result, np.ndarray): raise TypeError( f"Function {func!r} must return a DataFrame or ndarray " f"when passed to `Styler.apply` with axis=None" ) if data.shape != result.shape: raise ValueError( f"Function {func!r} returned ndarray with wrong shape.\n" f"Result has shape: {result.shape}\n" f"Expected shape: {data.shape}" ) result = DataFrame(result, index=data.index, columns=data.columns) else: axis = self.data._get_axis_number(axis) if axis == 0: result = data.apply(func, axis=0, **kwargs) else: result = data.T.apply(func, axis=0, **kwargs).T # see GH 42005 if isinstance(result, Series): raise ValueError( f"Function {func!r} resulted in the apply method collapsing to a " f"Series.\nUsually, this is the result of the function returning a " f"single value, instead of list-like." ) msg = ( f"Function {func!r} created invalid {{0}} labels.\nUsually, this is " f"the result of the function returning a " f"{'Series' if axis is not None else 'DataFrame'} which contains invalid " f"labels, or returning an incorrectly shaped, list-like object which " f"cannot be mapped to labels, possibly due to applying the function along " f"the wrong axis.\n" f"Result {{0}} has shape: {{1}}\n" f"Expected {{0}} shape: {{2}}" ) if not all(result.index.isin(data.index)): raise ValueError(msg.format("index", result.index.shape, data.index.shape)) if not all(result.columns.isin(data.columns)): raise ValueError( msg.format("columns", result.columns.shape, data.columns.shape) ) self._update_ctx(result) return self def apply( self, func: Callable, axis: Axis | None = 0, subset: Subset | None = None, **kwargs, ) -> Styler: """ Apply a CSS-styling function column-wise, row-wise, or table-wise. Updates the HTML representation with the result. Parameters ---------- func : function ``func`` should take a Series if ``axis`` in [0,1] and return a list-like object of same length, or a Series, not necessarily of same length, with valid index labels considering ``subset``. ``func`` should take a DataFrame if ``axis`` is ``None`` and return either an ndarray with the same shape or a DataFrame, not necessarily of the same shape, with valid index and columns labels considering ``subset``. axis : {0 or 'index', 1 or 'columns', None}, default 0 Apply to each column (``axis=0`` or ``'index'``), to each row (``axis=1`` or ``'columns'``), or to the entire DataFrame at once with ``axis=None``. subset : label, array-like, IndexSlice, optional A valid 2d input to `DataFrame.loc[]`, or, in the case of a 1d input or single key, to `DataFrame.loc[:, ]` where the columns are prioritised, to limit ``data`` to *before* applying the function. **kwargs : dict Pass along to ``func``. Returns ------- Styler Instance of class with CSS applied to its HTML representation. See Also -------- Styler.map_index: Apply a CSS-styling function to headers elementwise. Styler.apply_index: Apply a CSS-styling function to headers level-wise. Styler.map: Apply a CSS-styling function elementwise. Notes ----- The elements of the output of ``func`` should be CSS styles as strings, in the format 'attribute: value; attribute2: value2; ...' or, if nothing is to be applied to that element, an empty string or ``None``. This is similar to ``DataFrame.apply``, except that ``axis=None`` applies the function to the entire DataFrame at once, rather than column-wise or row-wise. Examples -------- >>> def highlight_max(x, color): ... return np.where(x == np.nanmax(x.to_numpy()), f"color: {color};", None) >>> df = pd.DataFrame(np.random.randn(5, 2), columns=["A", "B"]) >>> df.style.apply(highlight_max, color="red") # doctest: +SKIP >>> df.style.apply(highlight_max, color="blue", axis=1) # doctest: +SKIP >>> df.style.apply(highlight_max, color="green", axis=None) # doctest: +SKIP Using ``subset`` to restrict application to a single column or multiple columns >>> df.style.apply(highlight_max, color="red", subset="A") ... # doctest: +SKIP >>> df.style.apply(highlight_max, color="red", subset=["A", "B"]) ... # doctest: +SKIP Using a 2d input to ``subset`` to select rows in addition to columns >>> df.style.apply(highlight_max, color="red", subset=([0, 1, 2], slice(None))) ... # doctest: +SKIP >>> df.style.apply(highlight_max, color="red", subset=(slice(0, 5, 2), "A")) ... # doctest: +SKIP Using a function which returns a Series / DataFrame of unequal length but containing valid index labels >>> df = pd.DataFrame([[1, 2], [3, 4], [4, 6]], index=["A1", "A2", "Total"]) >>> total_style = pd.Series("font-weight: bold;", index=["Total"]) >>> df.style.apply(lambda s: total_style) # doctest: +SKIP See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for more details. """ self._todo.append( (lambda instance: instance._apply, (func, axis, subset), kwargs) ) return self def _apply_index( self, func: Callable, axis: Axis = 0, level: Level | list[Level] | None = None, method: str = "apply", **kwargs, ) -> Styler: axis = self.data._get_axis_number(axis) obj = self.index if axis == 0 else self.columns levels_ = refactor_levels(level, obj) data = DataFrame(obj.to_list()).loc[:, levels_] if method == "apply": result = data.apply(func, axis=0, **kwargs) elif method == "map": result = data.map(func, **kwargs) self._update_ctx_header(result, axis) return self def apply_index( self, func: Callable, axis: AxisInt | str = 0, level: Level | list[Level] | None = None, **kwargs, ) -> Styler: """ Apply a CSS-styling function to the index or column headers, level-wise. Updates the HTML representation with the result. .. versionadded:: 2.1.0 Styler.applymap_index was deprecated and renamed to Styler.map_index. Parameters ---------- func : function ``func`` should take a Series and return a string array of the same length. axis : {{0, 1, "index", "columns"}} The headers over which to apply the function. level : int, str, list, optional If index is MultiIndex the level(s) over which to apply the function. **kwargs : dict Pass along to ``func``. Returns ------- Styler Instance of class with CSS applied to its HTML representation. See Also -------- Styler.map_index: Apply a CSS-styling function to headers elementwise. Styler.apply: Apply a CSS-styling function column-wise, row-wise, or table-wise. Styler.map: Apply a CSS-styling function elementwise. Notes ----- Each input to ``func`` will be the index as a Series, if an Index, or a level of a MultiIndex. The output of ``func`` should be an identically sized array of CSS styles as strings, in the format 'attribute: value; attribute2: value2; ...' or, if nothing is to be applied to that element, an empty string or ``None``. Examples -------- Basic usage to conditionally highlight values in the index. >>> df = pd.DataFrame([[1, 2], [3, 4]], index=["A", "B"]) >>> def color_b(label): ... return np.where(label == "B", "background-color: yellow;", "") >>> df.style.apply_index(color_b) # doctest: +SKIP .. figure:: ../../_static/style/appmaphead1.png Selectively applying to specific levels of MultiIndex columns. >>> midx = pd.MultiIndex.from_product([["ix", "jy"], [0, 1], ["x3", "z4"]]) >>> df = pd.DataFrame([np.arange(8)], columns=midx) >>> def highlight_x(label): ... return ["background-color: yellow;" if "x" in v else "" for v in label] >>> df.style.apply_index( ... highlight_x, axis="columns", level=[0, 2] ... ) # doctest: +SKIP .. figure:: ../../_static/style/appmaphead2.png """ self._todo.append( ( lambda instance: instance._apply_index, (func, axis, level, "apply"), kwargs, ) ) return self def map_index( self, func: Callable, axis: AxisInt | str = 0, level: Level | list[Level] | None = None, **kwargs, ) -> Styler: """ Apply a CSS-styling function to the index or column headers, elementwise. Updates the HTML representation with the result. .. versionadded:: 2.1.0 Styler.applymap_index was deprecated and renamed to Styler.map_index. Parameters ---------- func : function ``func`` should take a scalar and return a string. axis : {{0, 1, "index", "columns"}} The headers over which to apply the function. level : int, str, list, optional If index is MultiIndex the level(s) over which to apply the function. **kwargs : dict Pass along to ``func``. Returns ------- Styler Instance of class with CSS applied to its HTML representation. See Also -------- Styler.apply_index: Apply a CSS-styling function to headers level-wise. Styler.apply: Apply a CSS-styling function column-wise, row-wise, or table-wise. Styler.map: Apply a CSS-styling function elementwise. Notes ----- Each input to ``func`` will be an index value, if an Index, or a level value of a MultiIndex. The output of ``func`` should be CSS styles as a string, in the format 'attribute: value; attribute2: value2; ...' or, if nothing is to be applied to that element, an empty string or ``None``. Examples -------- Basic usage to conditionally highlight values in the index. >>> df = pd.DataFrame([[1, 2], [3, 4]], index=["A", "B"]) >>> def color_b(label): ... return "background-color: yellow;" if label == "B" else None >>> df.style.map_index(color_b) # doctest: +SKIP .. figure:: ../../_static/style/appmaphead1.png Selectively applying to specific levels of MultiIndex columns. >>> midx = pd.MultiIndex.from_product([["ix", "jy"], [0, 1], ["x3", "z4"]]) >>> df = pd.DataFrame([np.arange(8)], columns=midx) >>> def highlight_x(label): ... return "background-color: yellow;" if "x" in label else None >>> df.style.map_index( ... highlight_x, axis="columns", level=[0, 2] ... ) # doctest: +SKIP .. figure:: ../../_static/style/appmaphead2.png """ self._todo.append( ( lambda instance: instance._apply_index, (func, axis, level, "map"), kwargs, ) ) return self def _map(self, func: Callable, subset: Subset | None = None, **kwargs) -> Styler: func = partial(func, **kwargs) # map doesn't take kwargs? if subset is None: subset = IndexSlice[:] subset = non_reducing_slice(subset) result = self.data.loc[subset].map(func) self._update_ctx(result) return self def map(self, func: Callable, subset: Subset | None = None, **kwargs) -> Styler: """ Apply a CSS-styling function elementwise. Updates the HTML representation with the result. Parameters ---------- func : function ``func`` should take a scalar and return a string. subset : label, array-like, IndexSlice, optional A valid 2d input to `DataFrame.loc[]`, or, in the case of a 1d input or single key, to `DataFrame.loc[:, ]` where the columns are prioritised, to limit ``data`` to *before* applying the function. **kwargs : dict Pass along to ``func``. Returns ------- Styler Instance of class with CSS-styling function applied elementwise. See Also -------- Styler.map_index: Apply a CSS-styling function to headers elementwise. Styler.apply_index: Apply a CSS-styling function to headers level-wise. Styler.apply: Apply a CSS-styling function column-wise, row-wise, or table-wise. Notes ----- The elements of the output of ``func`` should be CSS styles as strings, in the format 'attribute: value; attribute2: value2; ...' or, if nothing is to be applied to that element, an empty string or ``None``. Examples -------- >>> def color_negative(v, color): ... return f"color: {color};" if v < 0 else None >>> df = pd.DataFrame(np.random.randn(5, 2), columns=["A", "B"]) >>> df.style.map(color_negative, color="red") # doctest: +SKIP Using ``subset`` to restrict application to a single column or multiple columns >>> df.style.map(color_negative, color="red", subset="A") ... # doctest: +SKIP >>> df.style.map(color_negative, color="red", subset=["A", "B"]) ... # doctest: +SKIP Using a 2d input to ``subset`` to select rows in addition to columns >>> df.style.map( ... color_negative, color="red", subset=([0, 1, 2], slice(None)) ... ) # doctest: +SKIP >>> df.style.map(color_negative, color="red", subset=(slice(0, 5, 2), "A")) ... # doctest: +SKIP See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for more details. """ self._todo.append((lambda instance: instance._map, (func, subset), kwargs)) return self def set_table_attributes(self, attributes: str) -> Styler: """ Set the table attributes added to the ```` HTML element. These are items in addition to automatic (by default) ``id`` attribute. Parameters ---------- attributes : str Table attributes to be added to the ``
`` HTML element. Returns ------- Styler Instance of class with specified table attributes set. See Also -------- Styler.set_table_styles: Set the table styles included within the ``