text: """
  Module for applying conditional formatting to DataFrames and Series.
  """
  from __future__ import annotations
  
  from collections import defaultdict
  from contextlib import contextmanager
  import copy
  from functools import partial
  from itertools import product
  from typing import (
      Any,
      Callable,
      DefaultDict,
      Dict,
      Hashable,
      List,
      Optional,
      Sequence,
      Tuple,
      Union,
  )
  from uuid import uuid4
  
  import numpy as np
  
  from pandas._config import get_option
  
  from pandas._libs import lib
  from pandas._typing import (
      Axis,
      FrameOrSeries,
      FrameOrSeriesUnion,
      IndexLabel,
  )
  from pandas.compat._optional import import_optional_dependency
  from pandas.util._decorators import doc
  
  from pandas.core.dtypes.common import is_float
  
  import pandas as pd
  from pandas.api.types import (
      is_dict_like,
      is_list_like,
  )
  from pandas.core import generic
  import pandas.core.common as com
  from pandas.core.frame import DataFrame
  from pandas.core.generic import NDFrame
  from pandas.core.indexing import (
      maybe_numeric_slice,
      non_reducing_slice,
  )
  
  jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.")
  CSSSequence = Sequence[Tuple[str, Union[str, int, float]]]
  CSSProperties = Union[str, CSSSequence]
  CSSStyles = List[Dict[str, CSSProperties]]
  
  try:
      from matplotlib import colors
      import matplotlib.pyplot as plt
  
      has_mpl = True
  except ImportError:
      has_mpl = False
      no_mpl_message = "{0} requires matplotlib."
  
  
  @contextmanager
  def _mpl(func: Callable):
      if has_mpl:
          yield plt, colors
      else:
          raise ImportError(no_mpl_message.format(func.__name__))
  
  
  class Styler:
      """
      Helps style a DataFrame or Series according to the data with HTML and CSS.
  
      Parameters
      ----------
      data : Series or DataFrame
          Data to be styled - either a Series or DataFrame.
      precision : int
          Precision to round floats to, defaults to pd.options.display.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, default None
          Caption to attach to the table.
      table_attributes : str, default None
          Items that show up in the opening ``<table>`` 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_<uuid>_row<num_row>_col<num_col>``
          where ``<uuid>`` is the unique identifier, ``<num_row>`` is the row
          number and ``<num_col>`` is the column number.
      na_rep : str, optional
          Representation for missing values.
          If ``na_rep`` is None, no special formatting is applied.
  
          .. versionadded:: 1.0.0
  
      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].
  
          .. versionadded:: 1.2.0
  
      Attributes
      ----------
      env : Jinja2 jinja2.Environment
      template : 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
      -----
      Most styling will be done by passing style functions into
      ``Styler.apply`` or ``Styler.applymap``. 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.render to get
      the generated HTML.
  
      CSS classes are attached to the generated HTML
  
      * Index and Column names include ``index_name`` and ``level<k>``
        where `k` is its level in a MultiIndex
      * Index label cells include
  
        * ``row_heading``
        * ``row<n>`` where `n` is the numeric position of the row
        * ``level<k>`` where `k` is the level in a MultiIndex
  
      * Column label cells include
        * ``col_heading``
        * ``col<n>`` where `n` is the numeric position of the column
        * ``level<k>`` where `k` is the level in a MultiIndex
  
      * Blank cells include ``blank``
      * Data cells include ``data``
      """
  
      loader = jinja2.PackageLoader("pandas", "io/formats/templates")
      env = jinja2.Environment(loader=loader, trim_blocks=True)
      template = env.get_template("html.tpl")
  
      def __init__(
          self,
          data: FrameOrSeriesUnion,
          precision: Optional[int] = None,
          table_styles: Optional[CSSStyles] = None,
          uuid: Optional[str] = None,
          caption: Optional[str] = None,
          table_attributes: Optional[str] = None,
          cell_ids: bool = True,
          na_rep: Optional[str] = None,
          uuid_len: int = 5,
      ):
          # validate ordered args
          if isinstance(data, pd.Series):
              data = data.to_frame()
          if not isinstance(data, DataFrame):
              raise TypeError("``data`` must be a Series or DataFrame")
          if not data.index.is_unique or not data.columns.is_unique:
              raise ValueError("style is not supported for non-unique indices.")
          self.data: DataFrame = data
          self.index: pd.Index = data.index
          self.columns: pd.Index = data.columns
          if precision is None:
              precision = get_option("display.precision")
          self.precision = precision
          self.table_styles = table_styles
          if not isinstance(uuid_len, int) or not uuid_len >= 0:
              raise TypeError("``uuid_len`` must be an integer in range [0, 32].")
          self.uuid_len = min(32, uuid_len)
          self.uuid = (uuid or uuid4().hex[: self.uuid_len]) + "_"
          self.caption = caption
          self.table_attributes = table_attributes
          self.cell_ids = cell_ids
          self.na_rep = na_rep
  
          # assign additional default vars
          self.hidden_index: bool = False
          self.hidden_columns: Sequence[int] = []
          self.ctx: DefaultDict[Tuple[int, int], List[str]] = defaultdict(list)
          self.cell_context: Dict[str, Any] = {}
          self._todo: List[Tuple[Callable, Tuple, Dict]] = []
          self.tooltips: Optional[_Tooltips] = None
          self._display_funcs: DefaultDict[  # maps (row, col) -> formatting function
              Tuple[int, int], Callable[[Any], str]
          ] = defaultdict(lambda: self._default_display_func)
  
      def _repr_html_(self) -> str:
          """
          Hooks into Jupyter notebook rich display system.
          """
          return self.render()
  
      def _init_tooltips(self):
          """
          Checks parameters compatible with tooltips and creates instance if necessary
          """
          if not self.cell_ids:
              # tooltips not optimised for individual cell check. requires reasonable
              # redesign and more extensive code for a feature that might be rarely used.
              raise NotImplementedError(
                  "Tooltips can only render with 'cell_ids' is True."
              )
          if self.tooltips is None:
              self.tooltips = _Tooltips()
  
      def _default_display_func(self, x):
          if self.na_rep is not None and pd.isna(x):
              return self.na_rep
          elif is_float(x):
              display_format = f"{x:.{self.precision}f}"
              return display_format
          else:
              return x
  
      def set_tooltips(self, ttips: DataFrame) -> Styler:
          """
          Add string based tooltips that will appear in the `Styler` HTML result. These
          tooltips are applicable only to`<td>` elements.
  
          .. versionadded:: 1.3.0
  
          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.
  
          Returns
          -------
          self : Styler
  
          Notes
          -----
          Tooltips are created by adding `<span class="pd-t"></span>` to each data cell
          and then manipulating the table level CSS to attach pseudo hover and pseudo
          after selectors to produce the required the results. For styling control
          see `:meth:Styler.set_tooltips_class`.
          Tooltips are not designed to be efficient, and can add large amounts of
          additional HTML for larger tables, since they also require that `cell_ids`
          is forced to `True`.
  
          Examples
          --------
          >>> df = pd.DataFrame(data=[[0, 1], [2, 3]])
          >>> ttips = pd.DataFrame(
          ...    data=[["Min", ""], [np.nan, "Max"]], columns=df.columns, index=df.index
          ... )
          >>> s = df.style.set_tooltips(ttips).render()
          """
          self._init_tooltips()
          assert self.tooltips is not None  # mypy requiremen
          self.tooltips.tt_data = ttips
          return self
  
      def set_tooltips_class(
          self,
          name: Optional[str] = None,
          properties: Optional[CSSProperties] = None,
      ) -> Styler:
          """
          Manually configure the name and/or properties of the class for
          creating tooltips on hover.
  
          .. versionadded:: 1.3.0
  
          Parameters
          ----------
          name : str, default None
              Name of the tooltip class used in CSS, should conform to HTML standards.
          properties : list-like or str, default None
              List of (attr, value) tuples or a valid CSS string; see example.
  
          Returns
          -------
          self : Styler
  
          Notes
          -----
          If arguments are `None` will not make any changes to the underlying ``Tooltips``
          existing values.
  
          The default properties for the tooltip CSS class are:
  
          - visibility: hidden
          - position: absolute
          - z-index: 1
          - background-color: black
          - color: white
          - transform: translate(-20px, -20px)
  
          The property ('visibility', 'hidden') is a key prerequisite to the hover
          functionality, and should always be included in any manual properties
          specification.
  
          Examples
          --------
          >>> df = pd.DataFrame(np.random.randn(10, 4))
          >>> df.style.set_tooltips_class(name='tt-add', properties=[
          ...     ('visibility', 'hidden'),
          ...     ('position', 'absolute'),
          ...     ('z-index', 1)])
          >>> df.style.set_tooltips_class(name='tt-add',
          ...     properties='visibility:hidden; position:absolute; z-index:1;')
          """
          self._init_tooltips()
          assert self.tooltips is not None  # mypy requirement
          if properties:
              self.tooltips.class_properties = properties
          if name:
              self.tooltips.class_name = name
          return self
  
      @doc(
          NDFrame.to_excel,
          klass="Styler",
          storage_options=generic._shared_docs["storage_options"],
      )
      def to_excel(
          self,
          excel_writer,
          sheet_name: str = "Sheet1",
          na_rep: str = "",
          float_format: Optional[str] = None,
          columns: Optional[Sequence[Hashable]] = None,
          header: Union[Sequence[Hashable], bool] = True,
          index: bool = True,
          index_label: Optional[IndexLabel] = None,
          startrow: int = 0,
          startcol: int = 0,
          engine: Optional[str] = None,
          merge_cells: bool = True,
          encoding: Optional[str] = None,
          inf_rep: str = "inf",
          verbose: bool = True,
          freeze_panes: Optional[Tuple[int, int]] = None,
      ) -> None:
  
          from pandas.io.formats.excel import ExcelFormatter
  
          formatter = ExcelFormatter(
              self,
              na_rep=na_rep,
              cols=columns,
              header=header,
              float_format=float_format,
              index=index,
              index_label=index_label,
              merge_cells=merge_cells,
              inf_rep=inf_rep,
          )
          formatter.write(
              excel_writer,
              sheet_name=sheet_name,
              startrow=startrow,
              startcol=startcol,
              freeze_panes=freeze_panes,
              engine=engine,
          )
  
      def _translate(self):
          """
          Convert the DataFrame in `self.data` and the attrs from `_build_styles`
          into a dictionary of {head, body, uuid, cellstyle}.
          """
          table_styles = self.table_styles or []
          caption = self.caption
          ctx = self.ctx
          precision = self.precision
          hidden_index = self.hidden_index
          hidden_columns = self.hidden_columns
          uuid = self.uuid
          ROW_HEADING_CLASS = "row_heading"
          COL_HEADING_CLASS = "col_heading"
          INDEX_NAME_CLASS = "index_name"
  
          DATA_CLASS = "data"
          BLANK_CLASS = "blank"
          BLANK_VALUE = ""
  
          # for sparsifying a MultiIndex
          idx_lengths = _get_level_lengths(self.index)
          col_lengths = _get_level_lengths(self.columns, hidden_columns)
  
          cell_context = self.cell_context
  
          n_rlvls = self.data.index.nlevels
          n_clvls = self.data.columns.nlevels
          rlabels = self.data.index.tolist()
          clabels = self.data.columns.tolist()
  
          if n_rlvls == 1:
              rlabels = [[x] for x in rlabels]
          if n_clvls == 1:
              clabels = [[x] for x in clabels]
          clabels = list(zip(*clabels))
  
          cellstyle_map = defaultdict(list)
          head = []
  
          for r in range(n_clvls):
              # Blank for Index columns...
              row_es = [
                  {
                      "type": "th",
                      "value": BLANK_VALUE,
                      "display_value": BLANK_VALUE,
                      "is_visible": not hidden_index,
                      "class": " ".join([BLANK_CLASS]),
                  }
              ] * (n_rlvls - 1)
  
              # ... except maybe the last for columns.names
              name = self.data.columns.names[r]
              cs = [
                  BLANK_CLASS if name is None else INDEX_NAME_CLASS,
                  f"level{r}",
              ]
              name = BLANK_VALUE if name is None else name
              row_es.append(
                  {
                      "type": "th",
                      "value": name,
                      "display_value": name,
                      "class": " ".join(cs),
                      "is_visible": not hidden_index,
                  }
              )
  
              if clabels:
                  for c, value in enumerate(clabels[r]):
                      cs = [
                          COL_HEADING_CLASS,
                          f"level{r}",
                          f"col{c}",
                      ]
                      cs.extend(
                          cell_context.get("col_headings", {}).get(r, {}).get(c, [])
                      )
                      es = {
                          "type": "th",
                          "value": value,
                          "display_value": value,
                          "class": " ".join(cs),
                          "is_visible": _is_visible(c, r, col_lengths),
                      }
                      colspan = col_lengths.get((r, c), 0)
                      if colspan > 1:
                          es["attributes"] = [f'colspan="{colspan}"']
                      row_es.append(es)
                  head.append(row_es)
  
          if (
              self.data.index.names
              and com.any_not_none(*self.data.index.names)
              and not hidden_index
          ):
              index_header_row = []
  
              for c, name in enumerate(self.data.index.names):
                  cs = [INDEX_NAME_CLASS, f"level{c}"]
                  name = "" if name is None else name
                  index_header_row.append(
                      {"type": "th", "value": name, "class": " ".join(cs)}
                  )
  
              index_header_row.extend(
                  [{"type": "th", "value": BLANK_VALUE, "class": " ".join([BLANK_CLASS])}]
                  * (len(clabels[0]) - len(hidden_columns))
              )
  
              head.append(index_header_row)
  
          body = []
          for r, idx in enumerate(self.data.index):
              row_es = []
              for c, value in enumerate(rlabels[r]):
                  rid = [
                      ROW_HEADING_CLASS,
                      f"level{c}",
                      f"row{r}",
                  ]
                  es = {
                      "type": "th",
                      "is_visible": (_is_visible(r, c, idx_lengths) and not hidden_index),
                      "value": value,
                      "display_value": value,
                      "id": "_".join(rid[1:]),
                      "class": " ".join(rid),
                  }
                  rowspan = idx_lengths.get((c, r), 0)
                  if rowspan > 1:
                      es["attributes"] = [f'rowspan="{rowspan}"']
                  row_es.append(es)
  
              for c, col in enumerate(self.data.columns):
                  cs = [DATA_CLASS, f"row{r}", f"col{c}"]
                  formatter = self._display_funcs[(r, c)]
                  value = self.data.iloc[r, c]
                  row_dict = {
                      "type": "td",
                      "value": value,
                      "display_value": formatter(value),
                      "is_visible": (c not in hidden_columns),
                  }
  
                  # only add an id if the cell has a style
                  props = []
                  if self.cell_ids or (r, c) in ctx:
                      row_dict["id"] = "_".join(cs[1:])
                      for x in ctx[r, c]:
                          # have to handle empty styles like ['']
                          if x.count(":"):
                              props.append(tuple(x.split(":")))
                          else:
                              props.append(("", ""))
  
                  # add custom classes from cell context
                  cs.extend(cell_context.get("data", {}).get(r, {}).get(c, []))
                  row_dict["class"] = " ".join(cs)
  
                  row_es.append(row_dict)
                  cellstyle_map[tuple(props)].append(f"row{r}_col{c}")
              body.append(row_es)
  
          cellstyle = [
              {"props": list(props), "selectors": selectors}
              for props, selectors in cellstyle_map.items()
          ]
  
          table_attr = self.table_attributes
          use_mathjax = get_option("display.html.use_mathjax")
          if not use_mathjax:
              table_attr = table_attr or ""
              if 'class="' in table_attr:
                  table_attr = table_attr.replace('class="', 'class="tex2jax_ignore ')
              else:
                  table_attr += ' class="tex2jax_ignore"'
  
          d = {
              "head": head,
              "cellstyle": cellstyle,
              "body": body,
              "uuid": uuid,
              "precision": precision,
              "table_styles": table_styles,
              "caption": caption,
              "table_attributes": table_attr,
          }
          if self.tooltips:
              d = self.tooltips._translate(self.data, self.uuid, d)
  
          return d
  
      def format(self, formatter, subset=None, na_rep: Optional[str] = None) -> Styler:
          """
          Format the text display value of cells.
  
          Parameters
          ----------
          formatter : str, callable, dict or None
              If ``formatter`` is None, the default formatter is used.
          subset : IndexSlice
              An argument to ``DataFrame.loc`` that restricts which elements
              ``formatter`` is applied to.
          na_rep : str, optional
              Representation for missing values.
              If ``na_rep`` is None, no special formatting is applied.
  
              .. versionadded:: 1.0.0
  
          Returns
          -------
          self : Styler
  
          Notes
          -----
          ``formatter`` is either an ``a`` or a dict ``{column name: a}`` where
          ``a`` is one of
  
          - str: this will be wrapped in: ``a.format(x)``
          - callable: called with the value of an individual cell
  
          The default display value for numeric values is the "general" (``g``)
          format with ``pd.options.display.precision`` precision.
  
          Examples
          --------
          >>> df = pd.DataFrame(np.random.randn(4, 2), columns=['a', 'b'])
          >>> df.style.format("{:.2%}")
          >>> df['c'] = ['a', 'b', 'c', 'd']
          >>> df.style.format({'c': str.upper})
          """
          if formatter is None:
              assert self._display_funcs.default_factory is not None
              formatter = self._display_funcs.default_factory()
  
          if subset is None:
              row_locs = range(len(self.data))
              col_locs = range(len(self.data.columns))
          else:
              subset = non_reducing_slice(subset)
              if len(subset) == 1:
                  subset = subset, self.data.columns
  
              sub_df = self.data.loc[subset]
              row_locs = self.data.index.get_indexer_for(sub_df.index)
              col_locs = self.data.columns.get_indexer_for(sub_df.columns)
  
          if is_dict_like(formatter):
              for col, col_formatter in formatter.items():
                  # formatter must be callable, so '{}' are converted to lambdas
                  col_formatter = _maybe_wrap_formatter(col_formatter, na_rep)
                  col_num = self.data.columns.get_indexer_for([col])[0]
  
                  for row_num in row_locs:
                      self._display_funcs[(row_num, col_num)] = col_formatter
          else:
              # single scalar to format all cells with
              formatter = _maybe_wrap_formatter(formatter, na_rep)
              locs = product(*(row_locs, col_locs))
              for i, j in locs:
                  self._display_funcs[(i, j)] = formatter
          return self
  
      def set_td_classes(self, classes: DataFrame) -> Styler:
          """
          Add string based CSS class names to data cells that will appear within the
          `Styler` HTML result. These classes are added within specified `<td>` elements.
  
          Parameters
          ----------
          classes : DataFrame
              DataFrame containing strings that will be translated to CSS classes,
              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.
  
          Returns
          -------
          self : Styler
  
          Examples
          --------
          >>> df = pd.DataFrame(data=[[1, 2, 3], [4, 5, 6]], columns=["A", "B", "C"])
          >>> classes = pd.DataFrame([
          ...     ["min-val red", "", "blue"],
          ...     ["red", None, "blue max-val"]
          ... ], index=df.index, columns=df.columns)
          >>> df.style.set_td_classes(classes)
  
          Using `MultiIndex` columns and a `classes` `DataFrame` as a subset of the
          underlying,
  
          >>> df = pd.DataFrame([[1,2],[3,4]], index=["a", "b"],
          ...     columns=[["level0", "level0"], ["level1a", "level1b"]])
          >>> classes = pd.DataFrame(["min-val"], index=["a"],
          ...     columns=[["level0"],["level1a"]])
          >>> df.style.set_td_classes(classes)
  
          Form of the output with new additional css classes,
  
          >>> df = pd.DataFrame([[1]])
          >>> css = pd.DataFrame(["other-class"])
          >>> s = Styler(df, uuid="_", cell_ids=False).set_td_classes(css)
          >>> s.hide_index().render()
          '<style  type="text/css" ></style>'
          '<table id="T__" >'
          '  <thead>'
          '    <tr><th class="col_heading level0 col0" >0</th></tr>'
          '  </thead>'
          '  <tbody>'
          '    <tr><td  class="data row0 col0 other-class" >1</td></tr>'
          '  </tbody>'
          '</table>'
          """
          classes = classes.reindex_like(self.data)
  
          mask = (classes.isna()) | (classes.eq(""))
          self.cell_context["data"] = {
              r: {
                  c: [str(classes.iloc[r, c])]
                  for c, cn in enumerate(classes.columns)
                  if not mask.iloc[r, c]
              }
              for r, rn in enumerate(classes.index)
          }
  
          return self
  
      def render(self, **kwargs) -> str:
          """
          Render the built up styles to HTML.
  
          Parameters
          ----------
          **kwargs
              Any additional keyword arguments are passed
              through to ``self.template.render``.
              This is useful when you need to provide
              additional variables for a custom template.
  
          Returns
          -------
          rendered : str
              The rendered HTML.
  
          Notes
          -----
          ``Styler`` objects have defined the ``_repr_html_`` method
          which automatically calls ``self.render()`` when it's the
          last item in a Notebook cell. When calling ``Styler.render()``
          directly, wrap the result in ``IPython.display.HTML`` to view
          the rendered HTML in the notebook.
  
          Pandas uses the following keys in render. Arguments passed
          in ``**kwargs`` take precedence, so think carefully if you want
          to override them:
  
          * head
          * cellstyle
          * body
          * uuid
          * precision
          * table_styles
          * caption
          * table_attributes
          """
          self._compute()
          # TODO: namespace all the pandas keys
          d = self._translate()
          # filter out empty styles, every cell will have a class
          # but the list of props may just be [['', '']].
          # so we have the nested anys below
          trimmed = [x for x in d["cellstyle"] if any(any(y) for y in x["props"])]
          d["cellstyle"] = trimmed
          d.update(kwargs)
          return self.template.render(**d)
  
      def _update_ctx(self, attrs: DataFrame) -> None:
          """
          Update the state of the Styler.
  
          Collects a mapping of {index_label: ['<property>: <value>']}.
  
          Parameters
          ----------
          attrs : DataFrame
              should contain strings of '<property>: <value>;<prop2>: <val2>'
              Whitespace shouldn't matter and the final trailing ';' shouldn't
              matter.
          """
          coli = {k: i for i, k in enumerate(self.columns)}
          rowi = {k: i for i, k in enumerate(self.index)}
          for jj in range(len(attrs.columns)):
              cn = attrs.columns[jj]
              j = coli[cn]
              for rn, c in attrs[[cn]].itertuples():
                  if not c:
                      continue
                  c = c.rstrip(";")
                  if not c:
                      continue
                  i = rowi[rn]
                  for pair in c.split(";"):
                      self.ctx[(i, j)].append(pair)
  
      def _copy(self, deepcopy: bool = False) -> Styler:
          styler = Styler(
              self.data,
              precision=self.precision,
              caption=self.caption,
              uuid=self.uuid,
              table_styles=self.table_styles,
              na_rep=self.na_rep,
          )
          if deepcopy:
              styler.ctx = copy.deepcopy(self.ctx)
              styler._todo = copy.deepcopy(self._todo)
          else:
              styler.ctx = self.ctx
              styler._todo = self._todo
          return styler
  
      def __copy__(self) -> Styler:
          """
          Deep copy by default.
          """
          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.
          """
          self.ctx.clear()
          self.tooltips = None
          self.cell_context = {}
          self._todo = []
  
      def _compute(self):
          """
          Execute the style functions built up in `self._todo`.
  
          Relies on the conventions that all style functions go through
          .apply or .applymap. The append styles to apply as tuples of
  
          (application method, *args, **kwargs)
          """
          r = self
          for func, args, kwargs in self._todo:
              r = func(self)(*args, **kwargs)
          self._todo = []
          return r
  
      def _apply(
          self,
          func: Callable[..., Styler],
          axis: Optional[Axis] = 0,
          subset=None,
          **kwargs,
      ) -> Styler:
          subset = slice(None) if subset is None else subset
          subset = non_reducing_slice(subset)
          data = self.data.loc[subset]
          if axis is not None:
              result = data.apply(func, axis=axis, result_type="expand", **kwargs)
              result.columns = data.columns
          else:
              result = func(data, **kwargs)
              if not isinstance(result, pd.DataFrame):
                  if not isinstance(result, np.ndarray):
                      raise TypeError(
                          f"Function {repr(func)} must return a DataFrame or ndarray "
                          f"when passed to `Styler.apply` with axis=None"
                      )
                  if not (data.shape == result.shape):
                      raise ValueError(
                          f"Function {repr(func)} 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)
              elif not (
                  result.index.equals(data.index) and result.columns.equals(data.columns)
              ):
                  raise ValueError(
                      f"Result of {repr(func)} must have identical "
                      f"index and columns as the input"
                  )
  
          if result.shape != data.shape:
              raise ValueError(
                  f"Function {repr(func)} returned the wrong shape.\n"
                  f"Result has shape: {result.shape}\n"
                  f"Expected shape:   {data.shape}"
              )
          self._update_ctx(result)
          return self
  
      def apply(
          self,
          func: Callable[..., Styler],
          axis: Optional[Axis] = 0,
          subset=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 or DataFrame (depending
              on ``axis``), and return an object with the same shape.
              Must return a DataFrame with identical index and
              column labels or an ndarray with same shape as input when ``axis=None``.
  
              .. versionchanged:: 1.3.0
  
          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 : IndexSlice
              A valid indexer to limit ``data`` to *before* applying the
              function. Consider using a pandas.IndexSlice.
          **kwargs : dict
              Pass along to ``func``.
  
          Returns
          -------
          self : Styler
  
          Notes
          -----
          The output of ``func`` should be elements having CSS style as string or,
          if nothing is to be applied to that element, an empty string or ``None``.
          The output shape must match the input, i.e. if
          ``x`` is the input row, column, or table (depending on ``axis``),
          then ``func(x).shape == x.shape`` should be ``True``.
  
          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.values), f"color: {color};", None)
          >>> df = pd.DataFrame(np.random.randn(5, 2))
          >>> df.style.apply(highlight_max, color='red')
          >>> df.style.apply(highlight_max, color='blue', axis=1)
          >>> df.style.apply(highlight_max, color='green', axis=None)
          """
          self._todo.append(
              (lambda instance: getattr(instance, "_apply"), (func, axis, subset), kwargs)
          )
          return self
  
      def _applymap(self, func: Callable, subset=None, **kwargs) -> Styler:
          func = partial(func, **kwargs)  # applymap doesn't take kwargs?
          if subset is None:
              subset = pd.IndexSlice[:]
          subset = non_reducing_slice(subset)
          result = self.data.loc[subset].applymap(func)
          self._update_ctx(result)
          return self
  
      def applymap(self, func: Callable, subset=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 scalar.
          subset : IndexSlice
              A valid indexer to limit ``data`` to *before* applying the
              function. Consider using a pandas.IndexSlice.
          **kwargs : dict
              Pass along to ``func``.
  
          Returns
          -------
          self : Styler
  
          See Also
          --------
          Styler.where: Updates the HTML representation with a style which is
              selected in accordance with the return value of a function.
  
          Notes
          -----
          The output of ``func`` should be a CSS style as string or, if nothing is to be
          applied, 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))
          >>> df.style.applymap(color_negative, color='red')
          """
          self._todo.append(
              (lambda instance: getattr(instance, "_applymap"), (func, subset), kwargs)
          )
          return self
  
      def where(
          self,
          cond: Callable,
          value: str,
          other: Optional[str] = None,
          subset=None,
          **kwargs,
      ) -> Styler:
          """
          Apply a function elementwise.
  
          Updates the HTML representation with a style which is
          selected in accordance with the return value of a function.
  
          Parameters
          ----------
          cond : callable
              ``cond`` should take a scalar and return a boolean.
          value : str
              Applied when ``cond`` returns true.
          other : str
              Applied when ``cond`` returns false.
          subset : IndexSlice
              A valid indexer to limit ``data`` to *before* applying the
              function. Consider using a pandas.IndexSlice.
          **kwargs : dict
              Pass along to ``cond``.
  
          Returns
          -------
          self : Styler
  
          See Also
          --------
          Styler.applymap: Updates the HTML representation with the result.
          """
          if other is None:
              other = ""
  
          return self.applymap(
              lambda val: value if cond(val) else other, subset=subset, **kwargs
          )
  
      def set_precision(self, precision: int) -> Styler:
          """
          Set the precision used to render.
  
          Parameters
          ----------
          precision : int
  
          Returns
          -------
          self : Styler
          """
          self.precision = precision
          return self
  
      def set_table_attributes(self, attributes: str) -> Styler:
          """
          Set the table attributes.
  
          These are the items that show up in the opening ``<table>`` tag
          in addition to automatic (by default) id.
  
          Parameters
          ----------
          attributes : str
  
          Returns
          -------
          self : Styler
  
          Examples
          --------
          >>> df = pd.DataFrame(np.random.randn(10, 4))
          >>> df.style.set_table_attributes('class="pure-table"')
          # ... <table class="pure-table"> ...
          """
          self.table_attributes = attributes
          return self
  
      def export(self) -> List[Tuple[Callable, Tuple, Dict]]:
          """
          Export the styles to applied to the current Styler.
  
          Can be applied to a second style with ``Styler.use``.
  
          Returns
          -------
          styles : list
  
          See Also
          --------
          Styler.use: Set the styles on the current Styler.
          """
          return self._todo
  
      def use(self, styles: List[Tuple[Callable, Tuple, Dict]]) -> Styler:
          """
          Set the styles on the current Styler.
  
          Possibly uses styles from ``Styler.export``.
  
          Parameters
          ----------
          styles : list
              List of style functions.
  
          Returns
          -------
          self : Styler
  
          See Also
          --------
          Styler.export : Export the styles to applied to the current Styler.
          """
          self._todo.extend(styles)
          return self
  
      def set_uuid(self, uuid: str) -> Styler:
          """
          Set the uuid for a Styler.
  
          Parameters
          ----------
          uuid : str
  
          Returns
          -------
          self : Styler
          """
          self.uuid = uuid
          return self
  
      def set_caption(self, caption: str) -> Styler:
          """
          Set the caption on a Styler.
  
          Parameters
          ----------
          caption : str
  
          Returns
          -------
          self : Styler
          """
          self.caption = caption
          return self
  
      def set_table_styles(
          self,
          table_styles: Union[Dict[Any, CSSStyles], CSSStyles],
          axis: int = 0,
          overwrite: bool = True,
      ) -> Styler:
          """
          Set the table styles on a Styler.
  
          These are placed in a ``<style>`` tag before the generated HTML table.
  
          This function can be used to style the entire table, columns, rows or
          specific HTML selectors.
  
          Parameters
          ----------
          table_styles : list or dict
              If supplying a list, each individual table_style should be a
              dictionary with ``selector`` and ``props`` keys. ``selector``
              should be a CSS selector that the style will be applied to
              (automatically prefixed by the table's UUID) and ``props``
              should be a list of tuples with ``(attribute, value)``.
              If supplying a dict, the dict keys should correspond to
              column names or index values, depending upon the specified
              `axis` argument. These will be mapped to row or col CSS
              selectors. MultiIndex values as dict keys should be
              in their respective tuple form. The dict values should be
              a list as specified in the form with CSS selectors and
              props that will be applied to the specified row or column.
  
              .. versionchanged:: 1.2.0
  
          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'``). Only used if `table_styles` is
              dict.
  
              .. versionadded:: 1.2.0
  
          overwrite : boolean, default True
              Styles are replaced if `True`, or extended if `False`. CSS
              rules are preserved so most recent styles set will dominate
              if selectors intersect.
  
              .. versionadded:: 1.2.0
  
          Returns
          -------
          self : Styler
  
          Examples
          --------
          >>> df = pd.DataFrame(np.random.randn(10, 4),
          ...                   columns=['A', 'B', 'C', 'D'])
          >>> df.style.set_table_styles(
          ...     [{'selector': 'tr:hover',
          ...       'props': [('background-color', 'yellow')]}]
          ... )
  
          Or with CSS strings
  
          >>> df.style.set_table_styles(
          ...     [{'selector': 'tr:hover',
          ...       'props': 'background-color: yellow; font-size: 1em;']}]
          ... )
  
          Adding column styling by name
  
          >>> df.style.set_table_styles({
          ...     'A': [{'selector': '',
          ...            'props': [('color', 'red')]}],
          ...     'B': [{'selector': 'td',
          ...            'props': 'color: blue;']}]
          ... }, overwrite=False)
  
          Adding row styling
  
          >>> df.style.set_table_styles({
          ...     0: [{'selector': 'td:hover',
          ...          'props': [('font-size', '25px')]}]
          ... }, axis=1, overwrite=False)
          """
          if isinstance(table_styles, dict):
              if axis in [0, "index"]:
                  obj, idf = self.data.columns, ".col"
              else:
                  obj, idf = self.data.index, ".row"
  
              table_styles = [
                  {
                      "selector": str(s["selector"]) + idf + str(obj.get_loc(key)),
                      "props": _maybe_convert_css_to_tuples(s["props"]),
                  }
                  for key, styles in table_styles.items()
                  for s in styles
              ]
          else:
              table_styles = [
                  {
                      "selector": s["selector"],
                      "props": _maybe_convert_css_to_tuples(s["props"]),
                  }
                  for s in table_styles
              ]
  
          if not overwrite and self.table_styles is not None:
              self.table_styles.extend(table_styles)
          else:
              self.table_styles = table_styles
          return self
  
      def set_na_rep(self, na_rep: str) -> Styler:
          """
          Set the missing data representation on a Styler.
  
          .. versionadded:: 1.0.0
  
          Parameters
          ----------
          na_rep : str
  
          Returns
          -------
          self : Styler
          """
          self.na_rep = na_rep
          return self
  
      def hide_index(self) -> Styler:
          """
          Hide any indices from rendering.
  
          Returns
          -------
          self : Styler
          """
          self.hidden_index = True
          return self
  
      def hide_columns(self, subset) -> Styler:
          """
          Hide columns from rendering.
  
          Parameters
          ----------
          subset : IndexSlice
              An argument to ``DataFrame.loc`` that identifies which columns
              are hidden.
  
          Returns
          -------
          self : Styler
          """
          subset = non_reducing_slice(subset)
          hidden_df = self.data.loc[subset]
          self.hidden_columns = self.columns.get_indexer_for(hidden_df.columns)
          return self
  
      # -----------------------------------------------------------------------
      # A collection of "builtin" styles
      # -----------------------------------------------------------------------
  
      def background_gradient(
          self,
          cmap="PuBu",
          low: float = 0,
          high: float = 0,
          axis: Optional[Axis] = 0,
          subset=None,
          text_color_threshold: float = 0.408,
          vmin: Optional[float] = None,
          vmax: Optional[float] = None,
      ) -> Styler:
          """
          Color the background in a gradient style.
  
          The background color is determined according
          to the data in each column (optionally row). Requires matplotlib.
  
          Parameters
          ----------
          cmap : str or colormap
              Matplotlib colormap.
          low : float
              Compress the range by the low.
          high : float
              Compress the range by the high.
          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 : IndexSlice
              A valid slice for ``data`` to limit the style application to.
          text_color_threshold : float or int
              Luminance threshold for determining text color. Facilitates text
              visibility across varying background colors. From 0 to 1.
              0 = all text is dark colored, 1 = all text is light colored.
  
              .. versionadded:: 0.24.0
  
          vmin : float, optional
              Minimum data value that corresponds to colormap minimum value.
              When None (default): the minimum value of the data will be used.
  
              .. versionadded:: 1.0.0
  
          vmax : float, optional
              Maximum data value that corresponds to colormap maximum value.
              When None (default): the maximum value of the data will be used.
  
              .. versionadded:: 1.0.0
  
          Returns
          -------
          self : Styler
  
          Raises
          ------
          ValueError
              If ``text_color_threshold`` is not a value from 0 to 1.
  
          Notes
          -----
          Set ``text_color_threshold`` or tune ``low`` and ``high`` to keep the
          text legible by not using the entire range of the color map. The range
          of the data is extended by ``low * (x.max() - x.min())`` and ``high *
          (x.max() - x.min())`` before normalizing.
          """
          subset = maybe_numeric_slice(self.data, subset)
          subset = non_reducing_slice(subset)
          self.apply(
              self._background_gradient,
              cmap=cmap,
              subset=subset,
              axis=axis,
              low=low,
              high=high,
              text_color_threshold=text_color_threshold,
              vmin=vmin,
              vmax=vmax,
          )
          return self
  
      @staticmethod
      def _background_gradient(
          s,
          cmap="PuBu",
          low: float = 0,
          high: float = 0,
          text_color_threshold: float = 0.408,
          vmin: Optional[float] = None,
          vmax: Optional[float] = None,
      ):
          """
          Color background in a range according to the data.
          """
          if (
              not isinstance(text_color_threshold, (float, int))
              or not 0 <= text_color_threshold <= 1
          ):
              msg = "`text_color_threshold` must be a value from 0 to 1."
              raise ValueError(msg)
  
          with _mpl(Styler.background_gradient) as (plt, colors):
              smin = np.nanmin(s.to_numpy()) if vmin is None else vmin
              smax = np.nanmax(s.to_numpy()) if vmax is None else vmax
              rng = smax - smin
              # extend lower / upper bounds, compresses color range
              norm = colors.Normalize(smin - (rng * low), smax + (rng * high))
              # matplotlib colors.Normalize modifies inplace?
              # https://github.com/matplotlib/matplotlib/issues/5427
              rgbas = plt.cm.get_cmap(cmap)(norm(s.to_numpy(dtype=float)))
  
              def relative_luminance(rgba) -> float:
                  """
                  Calculate relative luminance of a color.
  
                  The calculation adheres to the W3C standards
                  (https://www.w3.org/WAI/GL/wiki/Relative_luminance)
  
                  Parameters
                  ----------
                  color : rgb or rgba tuple
  
                  Returns
                  -------
                  float
                      The relative luminance as a value from 0 to 1
                  """
                  r, g, b = (
                      x / 12.92 if x <= 0.03928 else ((x + 0.055) / 1.055 ** 2.4)
                      for x in rgba[:3]
                  )
                  return 0.2126 * r + 0.7152 * g + 0.0722 * b
  
              def css(rgba) -> str:
                  dark = relative_luminance(rgba) < text_color_threshold
                  text_color = "#f1f1f1" if dark else "#000000"
                  return f"background-color: {colors.rgb2hex(rgba)};color: {text_color};"
  
              if s.ndim == 1:
                  return [css(rgba) for rgba in rgbas]
              else:
                  return pd.DataFrame(
                      [[css(rgba) for rgba in row] for row in rgbas],
                      index=s.index,
                      columns=s.columns,
                  )
  
      def set_properties(self, subset=None, **kwargs) -> Styler:
          """
          Method to set one or more non-data dependent properties or each cell.
  
          Parameters
          ----------
          subset : IndexSlice
              A valid slice for ``data`` to limit the style application to.
          **kwargs : dict
              A dictionary of property, value pairs to be set for each cell.
  
          Returns
          -------
          self : Styler
  
          Examples
          --------
          >>> df = pd.DataFrame(np.random.randn(10, 4))
          >>> df.style.set_properties(color="white", align="right")
          >>> df.style.set_properties(**{'background-color': 'yellow'})
          """
          values = "".join(f"{p}: {v};" for p, v in kwargs.items())
          return self.applymap(lambda x: values, subset=subset)
  
      @staticmethod
      def _bar(
          s,
          align: str,
          colors: List[str],
          width: float = 100,
          vmin: Optional[float] = None,
          vmax: Optional[float] = None,
      ):
          """
          Draw bar chart in dataframe cells.
          """
          # Get input value range.
          smin = np.nanmin(s.to_numpy()) if vmin is None else vmin
          smax = np.nanmax(s.to_numpy()) if vmax is None else vmax
          if align == "mid":
              smin = min(0, smin)
              smax = max(0, smax)
          elif align == "zero":
              # For "zero" mode, we want the range to be symmetrical around zero.
              smax = max(abs(smin), abs(smax))
              smin = -smax
          # Transform to percent-range of linear-gradient
          normed = width * (s.to_numpy(dtype=float) - smin) / (smax - smin + 1e-12)
          zero = -width * smin / (smax - smin + 1e-12)
  
          def css_bar(start: float, end: float, color: str) -> str:
              """
              Generate CSS code to draw a bar from start to end.
              """
              css = "width: 10em; height: 80%;"
              if end > start:
                  css += "background: linear-gradient(90deg,"
                  if start > 0:
                      css += f" transparent {start:.1f}%, {color} {start:.1f}%, "
                  e = min(end, width)
                  css += f"{color} {e:.1f}%, transparent {e:.1f}%)"
              return css
  
          def css(x):
              if pd.isna(x):
                  return ""
  
              # avoid deprecated indexing `colors[x > zero]`
              color = colors[1] if x > zero else colors[0]
  
              if align == "left":
                  return css_bar(0, x, color)
              else:
                  return css_bar(min(x, zero), max(x, zero), color)
  
          if s.ndim == 1:
              return [css(x) for x in normed]
          else:
              return pd.DataFrame(
                  [[css(x) for x in row] for row in normed],
                  index=s.index,
                  columns=s.columns,
              )
  
      def bar(
          self,
          subset=None,
          axis: Optional[Axis] = 0,
          color="#d65f5f",
          width: float = 100,
          align: str = "left",
          vmin: Optional[float] = None,
          vmax: Optional[float] = None,
      ) -> Styler:
          """
          Draw bar chart in the cell backgrounds.
  
          Parameters
          ----------
          subset : IndexSlice, optional
              A valid slice for `data` to limit the style application to.
          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``.
          color : str or 2-tuple/list
              If a str is passed, the color is the same for both
              negative and positive numbers. If 2-tuple/list is used, the
              first element is the color_negative and the second is the
              color_positive (eg: ['#d65f5f', '#5fba7d']).
          width : float, default 100
              A number between 0 or 100. The largest value will cover `width`
              percent of the cell's width.
          align : {'left', 'zero',' mid'}, default 'left'
              How to align the bars with the cells.
  
              - 'left' : the min value starts at the left of the cell.
              - 'zero' : a value of zero is located at the center of the cell.
              - 'mid' : the center of the cell is at (max-min)/2, or
                if values are all negative (positive) the zero is aligned
                at the right (left) of the cell.
          vmin : float, optional
              Minimum bar value, defining the left hand limit
              of the bar drawing range, lower values are clipped to `vmin`.
              When None (default): the minimum value of the data will be used.
  
              .. versionadded:: 0.24.0
  
          vmax : float, optional
              Maximum bar value, defining the right hand limit
              of the bar drawing range, higher values are clipped to `vmax`.
              When None (default): the maximum value of the data will be used.
  
              .. versionadded:: 0.24.0
  
          Returns
          -------
          self : Styler
          """
          if align not in ("left", "zero", "mid"):
              raise ValueError("`align` must be one of {'left', 'zero',' mid'}")
  
          if not (is_list_like(color)):
              color = [color, color]
          elif len(color) == 1:
              color = [color[0], color[0]]
          elif len(color) > 2:
              raise ValueError(
                  "`color` must be string or a list-like "
                  "of length 2: [`color_neg`, `color_pos`] "
                  "(eg: color=['#d65f5f', '#5fba7d'])"
              )
  
          subset = maybe_numeric_slice(self.data, subset)
          subset = non_reducing_slice(subset)
          self.apply(
              self._bar,
              subset=subset,
              axis=axis,
              align=align,
              colors=color,
              width=width,
              vmin=vmin,
              vmax=vmax,
          )
  
          return self
  
      def highlight_null(
          self,
          null_color: str = "red",
          subset: Optional[IndexLabel] = None,
      ) -> Styler:
          """
          Shade the background ``null_color`` for missing values.
  
          Parameters
          ----------
          null_color : str, default 'red'
          subset : label or list of labels, default None
              A valid slice for ``data`` to limit the style application to.
  
              .. versionadded:: 1.1.0
  
          Returns
          -------
          self : Styler
          """
  
          def f(data: DataFrame, props: str) -> np.ndarray:
              return np.where(pd.isna(data).values, props, "")
  
          return self.apply(
              f, axis=None, subset=subset, props=f"background-color: {null_color};"
          )
  
      def highlight_max(
          self,
          subset: Optional[IndexLabel] = None,
          color: str = "yellow",
          axis: Optional[Axis] = 0,
      ) -> Styler:
          """
          Highlight the maximum by shading the background.
  
          Parameters
          ----------
          subset : IndexSlice, default None
              A valid slice for ``data`` to limit the style application to.
          color : str, default 'yellow'
          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``.
  
          Returns
          -------
          self : Styler
          """
  
          def f(data: FrameOrSeries, props: str) -> np.ndarray:
              return np.where(data == np.nanmax(data.values), props, "")
  
          return self.apply(
              f, axis=axis, subset=subset, props=f"background-color: {color};"
          )
  
      def highlight_min(
          self,
          subset: Optional[IndexLabel] = None,
          color: str = "yellow",
          axis: Optional[Axis] = 0,
      ) -> Styler:
          """
          Highlight the minimum by shading the background.
  
          Parameters
          ----------
          subset : IndexSlice, default None
              A valid slice for ``data`` to limit the style application to.
          color : str, default 'yellow'
          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``.
  
          Returns
          -------
          self : Styler
          """
  
          def f(data: FrameOrSeries, props: str) -> np.ndarray:
              return np.where(data == np.nanmin(data.values), props, "")
  
          return self.apply(
              f, axis=axis, subset=subset, props=f"background-color: {color};"
          )
  
      @classmethod
      def from_custom_template(cls, searchpath, name):
          """
          Factory function for creating a subclass of ``Styler``.
  
          Uses a custom template and Jinja environment.
  
          Parameters
          ----------
          searchpath : str or list
              Path or paths of directories containing the templates.
          name : str
              Name of your custom template to use for rendering.
  
          Returns
          -------
          MyStyler : subclass of Styler
              Has the correct ``env`` and ``template`` class attributes set.
          """
          loader = jinja2.ChoiceLoader([jinja2.FileSystemLoader(searchpath), cls.loader])
  
          # mypy doesn't like dynamically-defined classes
          # error: Variable "cls" is not valid as a type
          # error: Invalid base class "cls"
          class MyStyler(cls):  # type:ignore[valid-type,misc]
              env = jinja2.Environment(loader=loader)
              template = env.get_template(name)
  
          return MyStyler
  
      def pipe(self, func: Callable, *args, **kwargs):
          """
          Apply ``func(self, *args, **kwargs)``, and return the result.
  
          .. versionadded:: 0.24.0
  
          Parameters
          ----------
          func : function
              Function to apply to the Styler.  Alternatively, a
              ``(callable, keyword)`` tuple where ``keyword`` is a string
              indicating the keyword of ``callable`` that expects the Styler.
          *args : optional
              Arguments passed to `func`.
          **kwargs : optional
              A dictionary of keyword arguments passed into ``func``.
  
          Returns
          -------
          object :
              The value returned by ``func``.
  
          See Also
          --------
          DataFrame.pipe : Analogous method for DataFrame.
          Styler.apply : Apply a function row-wise, column-wise, or table-wise to
              modify the dataframe's styling.
  
          Notes
          -----
          Like :meth:`DataFrame.pipe`, this method can simplify the
          application of several user-defined functions to a styler.  Instead
          of writing:
  
          .. code-block:: python
  
              f(g(df.style.set_precision(3), arg1=a), arg2=b, arg3=c)
  
          users can write:
  
          .. code-block:: python
  
              (df.style.set_precision(3)
                 .pipe(g, arg1=a)
                 .pipe(f, arg2=b, arg3=c))
  
          In particular, this allows users to define functions that take a
          styler object, along with other parameters, and return the styler after
          making styling changes (such as calling :meth:`Styler.apply` or
          :meth:`Styler.set_properties`).  Using ``.pipe``, these user-defined
          style "transformations" can be interleaved with calls to the built-in
          Styler interface.
  
          Examples
          --------
          >>> def format_conversion(styler):
          ...     return (styler.set_properties(**{'text-align': 'right'})
          ...                   .format({'conversion': '{:.1%}'}))
  
          The user-defined ``format_conversion`` function above can be called
          within a sequence of other style modifications:
  
          >>> df = pd.DataFrame({'trial': list(range(5)),
          ...                    'conversion': [0.75, 0.85, np.nan, 0.7, 0.72]})
          >>> (df.style
          ...    .highlight_min(subset=['conversion'], color='yellow')
          ...    .pipe(format_conversion)
          ...    .set_caption("Results with minimum conversion highlighted."))
          """
          return com.pipe(self, func, *args, **kwargs)
  
  
  class _Tooltips:
      """
      An extension to ``Styler`` that allows for and manipulates tooltips on hover
      of table data-cells in the HTML result.
  
      Parameters
      ----------
      css_name: str, default "pd-t"
          Name of the CSS class that controls visualisation of tooltips.
      css_props: list-like, default; see Notes
          List of (attr, value) tuples defining properties of the CSS class.
      tooltips: DataFrame, default empty
          DataFrame of strings aligned with underlying ``Styler`` data for tooltip
          display.
  
      Notes
      -----
      The default properties for the tooltip CSS class are:
  
          - visibility: hidden
          - position: absolute
          - z-index: 1
          - background-color: black
          - color: white
          - transform: translate(-20px, -20px)
  
      Hidden visibility is a key prerequisite to the hover functionality, and should
      always be included in any manual properties specification.
      """
  
      def __init__(
          self,
          css_props: CSSProperties = [
              ("visibility", "hidden"),
              ("position", "absolute"),
              ("z-index", 1),
              ("background-color", "black"),
              ("color", "white"),
              ("transform", "translate(-20px, -20px)"),
          ],
          css_name: str = "pd-t",
          tooltips: DataFrame = DataFrame(),
      ):
          self.class_name = css_name
          self.class_properties = css_props
          self.tt_data = tooltips
          self.table_styles: CSSStyles = []
  
      @property
      def _class_styles(self):
          """
          Combine the ``_Tooltips`` CSS class name and CSS properties to the format
          required to extend the underlying ``Styler`` `table_styles` to allow
          tooltips to render in HTML.
  
          Returns
          -------
          styles : List
          """
          return [
              {
                  "selector": f".{self.class_name}",
                  "props": _maybe_convert_css_to_tuples(self.class_properties),
              }
          ]
  
      def _pseudo_css(self, uuid: str, name: str, row: int, col: int, text: str):
          """
          For every table data-cell that has a valid tooltip (not None, NaN or
          empty string) must create two pseudo CSS entries for the specific
          <td> element id which are added to overall table styles:
          an on hover visibility change and a content change
          dependent upon the user's chosen display string.
  
          For example:
              [{"selector": "T__row1_col1:hover .pd-t",
               "props": [("visibility", "visible")]},
              {"selector": "T__row1_col1 .pd-t::after",
               "props": [("content", "Some Valid Text String")]}]
  
          Parameters
          ----------
          uuid: str
              The uuid of the Styler instance
          name: str
              The css-name of the class used for styling tooltips
          row : int
              The row index of the specified tooltip string data
          col : int
              The col index of the specified tooltip string data
          text : str
              The textual content of the tooltip to be displayed in HTML.
  
          Returns
          -------
          pseudo_css : List
          """
          return [
              {
                  "selector": "#T_"
                  + uuid
                  + "row"
                  + str(row)
                  + "_col"
                  + str(col)
                  + f":hover .{name}",
                  "props": [("visibility", "visible")],
              },
              {
                  "selector": "#T_"
                  + uuid
                  + "row"
                  + str(row)
                  + "_col"
                  + str(col)
                  + f" .{name}::after",
                  "props": [("content", f'"{text}"')],
              },
          ]
  
      def _translate(self, styler_data: FrameOrSeriesUnion, uuid: str, d: Dict):
          """
          Mutate the render dictionary to allow for tooltips:
  
          - Add `<span>` HTML element to each data cells `display_value`. Ignores
            headers.
          - Add table level CSS styles to control pseudo classes.
  
          Parameters
          ----------
          styler_data : DataFrame
              Underlying ``Styler`` DataFrame used for reindexing.
          uuid : str
              The underlying ``Styler`` uuid for CSS id.
          d : dict
              The dictionary prior to final render
  
          Returns
          -------
          render_dict : Dict
          """
          self.tt_data = self.tt_data.reindex_like(styler_data)
  
          if self.tt_data.empty:
              return d
  
          name = self.class_name
  
          mask = (self.tt_data.isna()) | (self.tt_data.eq(""))  # empty string = no ttip
          self.table_styles = [
              style
              for sublist in [
                  self._pseudo_css(uuid, name, i, j, str(self.tt_data.iloc[i, j]))
                  for i in range(len(self.tt_data.index))
                  for j in range(len(self.tt_data.columns))
                  if not mask.iloc[i, j]
              ]
              for style in sublist
          ]
  
          if self.table_styles:
              # add span class to every cell only if at least 1 non-empty tooltip
              for row in d["body"]:
                  for item in row:
                      if item["type"] == "td":
                          item["display_value"] = (
                              str(item["display_value"])
                              + f'<span class="{self.class_name}"></span>'
                          )
              d["table_styles"].extend(self._class_styles)
              d["table_styles"].extend(self.table_styles)
  
          return d
  
  
  def _is_visible(idx_row, idx_col, lengths) -> bool:
      """
      Index -> {(idx_row, idx_col): bool}).
      """
      return (idx_col, idx_row) in lengths
  
  
  def _get_level_lengths(index, hidden_elements=None):
      """
      Given an index, find the level length for each element.
  
      Optional argument is a list of index positions which
      should not be visible.
  
      Result is a dictionary of (level, initial_position): span
      """
      if isinstance(index, pd.MultiIndex):
          levels = index.format(sparsify=lib.no_default, adjoin=False)
      else:
          levels = index.format()
  
      if hidden_elements is None:
          hidden_elements = []
  
      lengths = {}
      if index.nlevels == 1:
          for i, value in enumerate(levels):
              if i not in hidden_elements:
                  lengths[(0, i)] = 1
          return lengths
  
      for i, lvl in enumerate(levels):
          for j, row in enumerate(lvl):
              if not get_option("display.multi_sparse"):
                  lengths[(i, j)] = 1
              elif (row is not lib.no_default) and (j not in hidden_elements):
                  last_label = j
                  lengths[(i, last_label)] = 1
              elif row is not lib.no_default:
                  # even if its hidden, keep track of it in case
                  # length >1 and later elements are visible
                  last_label = j
                  lengths[(i, last_label)] = 0
              elif j not in hidden_elements:
                  lengths[(i, last_label)] += 1
  
      non_zero_lengths = {
          element: length for element, length in lengths.items() if length >= 1
      }
  
      return non_zero_lengths
  
  
  def _maybe_wrap_formatter(
      formatter: Union[Callable, str], na_rep: Optional[str]
  ) -> Callable:
      if isinstance(formatter, str):
          formatter_func = lambda x: formatter.format(x)
      elif callable(formatter):
          formatter_func = formatter
      else:
          msg = f"Expected a template string or callable, got {formatter} instead"
          raise TypeError(msg)
  
      if na_rep is None:
          return formatter_func
      elif isinstance(na_rep, str):
          return lambda x: na_rep if pd.isna(x) else formatter_func(x)
      else:
          msg = f"Expected a string, got {na_rep} instead"
          raise TypeError(msg)
  
  
  def _maybe_convert_css_to_tuples(style: CSSProperties) -> CSSSequence:
      """
      Convert css-string to sequence of tuples format if needed.
      'color:red; border:1px solid black;' -> [('color', 'red'),
                                               ('border','1px solid red')]
      """
      if isinstance(style, str):
          s = style.split(";")
          try:
              return [
                  (x.split(":")[0].strip(), x.split(":")[1].strip())
                  for x in s
                  if x.strip() != ""
              ]
          except IndexError:
              raise ValueError(
                  "Styles supplied as string must follow CSS rule formats, "
                  f"for example 'attr: val;'. '{style}' was given."
              )
      return style
  