style.py 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367
  1. """
  2. Module for applying conditional formatting to
  3. DataFrames and Series.
  4. """
  5. from collections import defaultdict
  6. from contextlib import contextmanager
  7. import copy
  8. from functools import partial
  9. from itertools import product
  10. from uuid import uuid1
  11. import numpy as np
  12. from pandas.compat import range
  13. from pandas.util._decorators import Appender
  14. from pandas.core.dtypes.common import is_float, is_string_like
  15. from pandas.core.dtypes.generic import ABCSeries
  16. import pandas as pd
  17. from pandas.api.types import is_dict_like, is_list_like
  18. import pandas.core.common as com
  19. from pandas.core.config import get_option
  20. from pandas.core.generic import _shared_docs
  21. from pandas.core.indexing import _maybe_numeric_slice, _non_reducing_slice
  22. try:
  23. from jinja2 import (
  24. PackageLoader, Environment, ChoiceLoader, FileSystemLoader
  25. )
  26. except ImportError:
  27. raise ImportError("pandas.Styler requires jinja2. "
  28. "Please install with `conda install Jinja2`\n"
  29. "or `pip install Jinja2`")
  30. try:
  31. import matplotlib.pyplot as plt
  32. from matplotlib import colors
  33. has_mpl = True
  34. except ImportError:
  35. has_mpl = False
  36. no_mpl_message = "{0} requires matplotlib."
  37. @contextmanager
  38. def _mpl(func):
  39. if has_mpl:
  40. yield plt, colors
  41. else:
  42. raise ImportError(no_mpl_message.format(func.__name__))
  43. class Styler(object):
  44. """
  45. Helps style a DataFrame or Series according to the data with HTML and CSS.
  46. Parameters
  47. ----------
  48. data : Series or DataFrame
  49. precision : int
  50. precision to round floats to, defaults to pd.options.display.precision
  51. table_styles : list-like, default None
  52. list of {selector: (attr, value)} dicts; see Notes
  53. uuid : str, default None
  54. a unique identifier to avoid CSS collisions; generated automatically
  55. caption : str, default None
  56. caption to attach to the table
  57. cell_ids : bool, default True
  58. If True, each cell will have an ``id`` attribute in their HTML tag.
  59. The ``id`` takes the form ``T_<uuid>_row<num_row>_col<num_col>``
  60. where ``<uuid>`` is the unique identifier, ``<num_row>`` is the row
  61. number and ``<num_col>`` is the column number.
  62. Attributes
  63. ----------
  64. env : Jinja2 Environment
  65. template : Jinja2 Template
  66. loader : Jinja2 Loader
  67. See Also
  68. --------
  69. pandas.DataFrame.style
  70. Notes
  71. -----
  72. Most styling will be done by passing style functions into
  73. ``Styler.apply`` or ``Styler.applymap``. Style functions should
  74. return values with strings containing CSS ``'attr: value'`` that will
  75. be applied to the indicated cells.
  76. If using in the Jupyter notebook, Styler has defined a ``_repr_html_``
  77. to automatically render itself. Otherwise call Styler.render to get
  78. the generated HTML.
  79. CSS classes are attached to the generated HTML
  80. * Index and Column names include ``index_name`` and ``level<k>``
  81. where `k` is its level in a MultiIndex
  82. * Index label cells include
  83. * ``row_heading``
  84. * ``row<n>`` where `n` is the numeric position of the row
  85. * ``level<k>`` where `k` is the level in a MultiIndex
  86. * Column label cells include
  87. * ``col_heading``
  88. * ``col<n>`` where `n` is the numeric position of the column
  89. * ``evel<k>`` where `k` is the level in a MultiIndex
  90. * Blank cells include ``blank``
  91. * Data cells include ``data``
  92. """
  93. loader = PackageLoader("pandas", "io/formats/templates")
  94. env = Environment(
  95. loader=loader,
  96. trim_blocks=True,
  97. )
  98. template = env.get_template("html.tpl")
  99. def __init__(self, data, precision=None, table_styles=None, uuid=None,
  100. caption=None, table_attributes=None, cell_ids=True):
  101. self.ctx = defaultdict(list)
  102. self._todo = []
  103. if not isinstance(data, (pd.Series, pd.DataFrame)):
  104. raise TypeError("``data`` must be a Series or DataFrame")
  105. if data.ndim == 1:
  106. data = data.to_frame()
  107. if not data.index.is_unique or not data.columns.is_unique:
  108. raise ValueError("style is not supported for non-unique indices.")
  109. self.data = data
  110. self.index = data.index
  111. self.columns = data.columns
  112. self.uuid = uuid
  113. self.table_styles = table_styles
  114. self.caption = caption
  115. if precision is None:
  116. precision = get_option('display.precision')
  117. self.precision = precision
  118. self.table_attributes = table_attributes
  119. self.hidden_index = False
  120. self.hidden_columns = []
  121. self.cell_ids = cell_ids
  122. # display_funcs maps (row, col) -> formatting function
  123. def default_display_func(x):
  124. if is_float(x):
  125. return '{:>.{precision}g}'.format(x, precision=self.precision)
  126. else:
  127. return x
  128. self._display_funcs = defaultdict(lambda: default_display_func)
  129. def _repr_html_(self):
  130. """
  131. Hooks into Jupyter notebook rich display system.
  132. """
  133. return self.render()
  134. @Appender(_shared_docs['to_excel'] % dict(
  135. axes='index, columns', klass='Styler',
  136. axes_single_arg="{0 or 'index', 1 or 'columns'}",
  137. optional_by="""
  138. by : str or list of str
  139. Name or list of names which refer to the axis items.""",
  140. versionadded_to_excel='\n .. versionadded:: 0.20'))
  141. def to_excel(self, excel_writer, sheet_name='Sheet1', na_rep='',
  142. float_format=None, columns=None, header=True, index=True,
  143. index_label=None, startrow=0, startcol=0, engine=None,
  144. merge_cells=True, encoding=None, inf_rep='inf', verbose=True,
  145. freeze_panes=None):
  146. from pandas.io.formats.excel import ExcelFormatter
  147. formatter = ExcelFormatter(self, na_rep=na_rep, cols=columns,
  148. header=header,
  149. float_format=float_format, index=index,
  150. index_label=index_label,
  151. merge_cells=merge_cells,
  152. inf_rep=inf_rep)
  153. formatter.write(excel_writer, sheet_name=sheet_name, startrow=startrow,
  154. startcol=startcol, freeze_panes=freeze_panes,
  155. engine=engine)
  156. def _translate(self):
  157. """
  158. Convert the DataFrame in `self.data` and the attrs from `_build_styles`
  159. into a dictionary of {head, body, uuid, cellstyle}.
  160. """
  161. table_styles = self.table_styles or []
  162. caption = self.caption
  163. ctx = self.ctx
  164. precision = self.precision
  165. hidden_index = self.hidden_index
  166. hidden_columns = self.hidden_columns
  167. uuid = self.uuid or str(uuid1()).replace("-", "_")
  168. ROW_HEADING_CLASS = "row_heading"
  169. COL_HEADING_CLASS = "col_heading"
  170. INDEX_NAME_CLASS = "index_name"
  171. DATA_CLASS = "data"
  172. BLANK_CLASS = "blank"
  173. BLANK_VALUE = ""
  174. def format_attr(pair):
  175. return "{key}={value}".format(**pair)
  176. # for sparsifying a MultiIndex
  177. idx_lengths = _get_level_lengths(self.index)
  178. col_lengths = _get_level_lengths(self.columns, hidden_columns)
  179. cell_context = dict()
  180. n_rlvls = self.data.index.nlevels
  181. n_clvls = self.data.columns.nlevels
  182. rlabels = self.data.index.tolist()
  183. clabels = self.data.columns.tolist()
  184. if n_rlvls == 1:
  185. rlabels = [[x] for x in rlabels]
  186. if n_clvls == 1:
  187. clabels = [[x] for x in clabels]
  188. clabels = list(zip(*clabels))
  189. cellstyle = []
  190. head = []
  191. for r in range(n_clvls):
  192. # Blank for Index columns...
  193. row_es = [{"type": "th",
  194. "value": BLANK_VALUE,
  195. "display_value": BLANK_VALUE,
  196. "is_visible": not hidden_index,
  197. "class": " ".join([BLANK_CLASS])}] * (n_rlvls - 1)
  198. # ... except maybe the last for columns.names
  199. name = self.data.columns.names[r]
  200. cs = [BLANK_CLASS if name is None else INDEX_NAME_CLASS,
  201. "level{lvl}".format(lvl=r)]
  202. name = BLANK_VALUE if name is None else name
  203. row_es.append({"type": "th",
  204. "value": name,
  205. "display_value": name,
  206. "class": " ".join(cs),
  207. "is_visible": not hidden_index})
  208. if clabels:
  209. for c, value in enumerate(clabels[r]):
  210. cs = [COL_HEADING_CLASS, "level{lvl}".format(lvl=r),
  211. "col{col}".format(col=c)]
  212. cs.extend(cell_context.get(
  213. "col_headings", {}).get(r, {}).get(c, []))
  214. es = {
  215. "type": "th",
  216. "value": value,
  217. "display_value": value,
  218. "class": " ".join(cs),
  219. "is_visible": _is_visible(c, r, col_lengths),
  220. }
  221. colspan = col_lengths.get((r, c), 0)
  222. if colspan > 1:
  223. es["attributes"] = [
  224. format_attr({"key": "colspan", "value": colspan})
  225. ]
  226. row_es.append(es)
  227. head.append(row_es)
  228. if (self.data.index.names and
  229. com._any_not_none(*self.data.index.names) and
  230. not hidden_index):
  231. index_header_row = []
  232. for c, name in enumerate(self.data.index.names):
  233. cs = [INDEX_NAME_CLASS,
  234. "level{lvl}".format(lvl=c)]
  235. name = '' if name is None else name
  236. index_header_row.append({"type": "th", "value": name,
  237. "class": " ".join(cs)})
  238. index_header_row.extend(
  239. [{"type": "th",
  240. "value": BLANK_VALUE,
  241. "class": " ".join([BLANK_CLASS])
  242. }] * (len(clabels[0]) - len(hidden_columns)))
  243. head.append(index_header_row)
  244. body = []
  245. for r, idx in enumerate(self.data.index):
  246. row_es = []
  247. for c, value in enumerate(rlabels[r]):
  248. rid = [ROW_HEADING_CLASS, "level{lvl}".format(lvl=c),
  249. "row{row}".format(row=r)]
  250. es = {
  251. "type": "th",
  252. "is_visible": (_is_visible(r, c, idx_lengths) and
  253. not hidden_index),
  254. "value": value,
  255. "display_value": value,
  256. "id": "_".join(rid[1:]),
  257. "class": " ".join(rid)
  258. }
  259. rowspan = idx_lengths.get((c, r), 0)
  260. if rowspan > 1:
  261. es["attributes"] = [
  262. format_attr({"key": "rowspan", "value": rowspan})
  263. ]
  264. row_es.append(es)
  265. for c, col in enumerate(self.data.columns):
  266. cs = [DATA_CLASS, "row{row}".format(row=r),
  267. "col{col}".format(col=c)]
  268. cs.extend(cell_context.get("data", {}).get(r, {}).get(c, []))
  269. formatter = self._display_funcs[(r, c)]
  270. value = self.data.iloc[r, c]
  271. row_dict = {"type": "td",
  272. "value": value,
  273. "class": " ".join(cs),
  274. "display_value": formatter(value),
  275. "is_visible": (c not in hidden_columns)}
  276. # only add an id if the cell has a style
  277. if (self.cell_ids or
  278. not(len(ctx[r, c]) == 1 and ctx[r, c][0] == '')):
  279. row_dict["id"] = "_".join(cs[1:])
  280. row_es.append(row_dict)
  281. props = []
  282. for x in ctx[r, c]:
  283. # have to handle empty styles like ['']
  284. if x.count(":"):
  285. props.append(x.split(":"))
  286. else:
  287. props.append(['', ''])
  288. cellstyle.append({'props': props,
  289. 'selector': "row{row}_col{col}"
  290. .format(row=r, col=c)})
  291. body.append(row_es)
  292. table_attr = self.table_attributes
  293. use_mathjax = get_option("display.html.use_mathjax")
  294. if not use_mathjax:
  295. table_attr = table_attr or ''
  296. if 'class="' in table_attr:
  297. table_attr = table_attr.replace('class="',
  298. 'class="tex2jax_ignore ')
  299. else:
  300. table_attr += ' class="tex2jax_ignore"'
  301. return dict(head=head, cellstyle=cellstyle, body=body, uuid=uuid,
  302. precision=precision, table_styles=table_styles,
  303. caption=caption, table_attributes=table_attr)
  304. def format(self, formatter, subset=None):
  305. """
  306. Format the text display value of cells.
  307. .. versionadded:: 0.18.0
  308. Parameters
  309. ----------
  310. formatter : str, callable, or dict
  311. subset : IndexSlice
  312. An argument to ``DataFrame.loc`` that restricts which elements
  313. ``formatter`` is applied to.
  314. Returns
  315. -------
  316. self : Styler
  317. Notes
  318. -----
  319. ``formatter`` is either an ``a`` or a dict ``{column name: a}`` where
  320. ``a`` is one of
  321. - str: this will be wrapped in: ``a.format(x)``
  322. - callable: called with the value of an individual cell
  323. The default display value for numeric values is the "general" (``g``)
  324. format with ``pd.options.display.precision`` precision.
  325. Examples
  326. --------
  327. >>> df = pd.DataFrame(np.random.randn(4, 2), columns=['a', 'b'])
  328. >>> df.style.format("{:.2%}")
  329. >>> df['c'] = ['a', 'b', 'c', 'd']
  330. >>> df.style.format({'c': str.upper})
  331. """
  332. if subset is None:
  333. row_locs = range(len(self.data))
  334. col_locs = range(len(self.data.columns))
  335. else:
  336. subset = _non_reducing_slice(subset)
  337. if len(subset) == 1:
  338. subset = subset, self.data.columns
  339. sub_df = self.data.loc[subset]
  340. row_locs = self.data.index.get_indexer_for(sub_df.index)
  341. col_locs = self.data.columns.get_indexer_for(sub_df.columns)
  342. if is_dict_like(formatter):
  343. for col, col_formatter in formatter.items():
  344. # formatter must be callable, so '{}' are converted to lambdas
  345. col_formatter = _maybe_wrap_formatter(col_formatter)
  346. col_num = self.data.columns.get_indexer_for([col])[0]
  347. for row_num in row_locs:
  348. self._display_funcs[(row_num, col_num)] = col_formatter
  349. else:
  350. # single scalar to format all cells with
  351. locs = product(*(row_locs, col_locs))
  352. for i, j in locs:
  353. formatter = _maybe_wrap_formatter(formatter)
  354. self._display_funcs[(i, j)] = formatter
  355. return self
  356. def render(self, **kwargs):
  357. """
  358. Render the built up styles to HTML.
  359. Parameters
  360. ----------
  361. `**kwargs` : Any additional keyword arguments are passed through
  362. to ``self.template.render``. This is useful when you need to provide
  363. additional variables for a custom template.
  364. .. versionadded:: 0.20
  365. Returns
  366. -------
  367. rendered : str
  368. the rendered HTML
  369. Notes
  370. -----
  371. ``Styler`` objects have defined the ``_repr_html_`` method
  372. which automatically calls ``self.render()`` when it's the
  373. last item in a Notebook cell. When calling ``Styler.render()``
  374. directly, wrap the result in ``IPython.display.HTML`` to view
  375. the rendered HTML in the notebook.
  376. Pandas uses the following keys in render. Arguments passed
  377. in ``**kwargs`` take precedence, so think carefully if you want
  378. to override them:
  379. * head
  380. * cellstyle
  381. * body
  382. * uuid
  383. * precision
  384. * table_styles
  385. * caption
  386. * table_attributes
  387. """
  388. self._compute()
  389. # TODO: namespace all the pandas keys
  390. d = self._translate()
  391. # filter out empty styles, every cell will have a class
  392. # but the list of props may just be [['', '']].
  393. # so we have the neested anys below
  394. trimmed = [x for x in d['cellstyle']
  395. if any(any(y) for y in x['props'])]
  396. d['cellstyle'] = trimmed
  397. d.update(kwargs)
  398. return self.template.render(**d)
  399. def _update_ctx(self, attrs):
  400. """
  401. Update the state of the Styler.
  402. Collects a mapping of {index_label: ['<property>: <value>']}.
  403. attrs : Series or DataFrame
  404. should contain strings of '<property>: <value>;<prop2>: <val2>'
  405. Whitespace shouldn't matter and the final trailing ';' shouldn't
  406. matter.
  407. """
  408. for row_label, v in attrs.iterrows():
  409. for col_label, col in v.iteritems():
  410. i = self.index.get_indexer([row_label])[0]
  411. j = self.columns.get_indexer([col_label])[0]
  412. for pair in col.rstrip(";").split(";"):
  413. self.ctx[(i, j)].append(pair)
  414. def _copy(self, deepcopy=False):
  415. styler = Styler(self.data, precision=self.precision,
  416. caption=self.caption, uuid=self.uuid,
  417. table_styles=self.table_styles)
  418. if deepcopy:
  419. styler.ctx = copy.deepcopy(self.ctx)
  420. styler._todo = copy.deepcopy(self._todo)
  421. else:
  422. styler.ctx = self.ctx
  423. styler._todo = self._todo
  424. return styler
  425. def __copy__(self):
  426. """
  427. Deep copy by default.
  428. """
  429. return self._copy(deepcopy=False)
  430. def __deepcopy__(self, memo):
  431. return self._copy(deepcopy=True)
  432. def clear(self):
  433. """
  434. Reset the styler, removing any previously applied styles.
  435. Returns None.
  436. """
  437. self.ctx.clear()
  438. self._todo = []
  439. def _compute(self):
  440. """
  441. Execute the style functions built up in `self._todo`.
  442. Relies on the conventions that all style functions go through
  443. .apply or .applymap. The append styles to apply as tuples of
  444. (application method, *args, **kwargs)
  445. """
  446. r = self
  447. for func, args, kwargs in self._todo:
  448. r = func(self)(*args, **kwargs)
  449. return r
  450. def _apply(self, func, axis=0, subset=None, **kwargs):
  451. subset = slice(None) if subset is None else subset
  452. subset = _non_reducing_slice(subset)
  453. data = self.data.loc[subset]
  454. if axis is not None:
  455. result = data.apply(func, axis=axis,
  456. result_type='expand', **kwargs)
  457. result.columns = data.columns
  458. else:
  459. result = func(data, **kwargs)
  460. if not isinstance(result, pd.DataFrame):
  461. raise TypeError(
  462. "Function {func!r} must return a DataFrame when "
  463. "passed to `Styler.apply` with axis=None"
  464. .format(func=func))
  465. if not (result.index.equals(data.index) and
  466. result.columns.equals(data.columns)):
  467. msg = ('Result of {func!r} must have identical index and '
  468. 'columns as the input'.format(func=func))
  469. raise ValueError(msg)
  470. result_shape = result.shape
  471. expected_shape = self.data.loc[subset].shape
  472. if result_shape != expected_shape:
  473. msg = ("Function {func!r} returned the wrong shape.\n"
  474. "Result has shape: {res}\n"
  475. "Expected shape: {expect}".format(func=func,
  476. res=result.shape,
  477. expect=expected_shape))
  478. raise ValueError(msg)
  479. self._update_ctx(result)
  480. return self
  481. def apply(self, func, axis=0, subset=None, **kwargs):
  482. """
  483. Apply a function column-wise, row-wise, or table-wise,
  484. updating the HTML representation with the result.
  485. Parameters
  486. ----------
  487. func : function
  488. ``func`` should take a Series or DataFrame (depending
  489. on ``axis``), and return an object with the same shape.
  490. Must return a DataFrame with identical index and
  491. column labels when ``axis=None``
  492. axis : int, str or None
  493. apply to each column (``axis=0`` or ``'index'``)
  494. or to each row (``axis=1`` or ``'columns'``) or
  495. to the entire DataFrame at once with ``axis=None``
  496. subset : IndexSlice
  497. a valid indexer to limit ``data`` to *before* applying the
  498. function. Consider using a pandas.IndexSlice
  499. kwargs : dict
  500. pass along to ``func``
  501. Returns
  502. -------
  503. self : Styler
  504. Notes
  505. -----
  506. The output shape of ``func`` should match the input, i.e. if
  507. ``x`` is the input row, column, or table (depending on ``axis``),
  508. then ``func(x).shape == x.shape`` should be true.
  509. This is similar to ``DataFrame.apply``, except that ``axis=None``
  510. applies the function to the entire DataFrame at once,
  511. rather than column-wise or row-wise.
  512. Examples
  513. --------
  514. >>> def highlight_max(x):
  515. ... return ['background-color: yellow' if v == x.max() else ''
  516. for v in x]
  517. ...
  518. >>> df = pd.DataFrame(np.random.randn(5, 2))
  519. >>> df.style.apply(highlight_max)
  520. """
  521. self._todo.append((lambda instance: getattr(instance, '_apply'),
  522. (func, axis, subset), kwargs))
  523. return self
  524. def _applymap(self, func, subset=None, **kwargs):
  525. func = partial(func, **kwargs) # applymap doesn't take kwargs?
  526. if subset is None:
  527. subset = pd.IndexSlice[:]
  528. subset = _non_reducing_slice(subset)
  529. result = self.data.loc[subset].applymap(func)
  530. self._update_ctx(result)
  531. return self
  532. def applymap(self, func, subset=None, **kwargs):
  533. """
  534. Apply a function elementwise, updating the HTML
  535. representation with the result.
  536. Parameters
  537. ----------
  538. func : function
  539. ``func`` should take a scalar and return a scalar
  540. subset : IndexSlice
  541. a valid indexer to limit ``data`` to *before* applying the
  542. function. Consider using a pandas.IndexSlice
  543. kwargs : dict
  544. pass along to ``func``
  545. Returns
  546. -------
  547. self : Styler
  548. See Also
  549. --------
  550. Styler.where
  551. """
  552. self._todo.append((lambda instance: getattr(instance, '_applymap'),
  553. (func, subset), kwargs))
  554. return self
  555. def where(self, cond, value, other=None, subset=None, **kwargs):
  556. """
  557. Apply a function elementwise, updating the HTML
  558. representation with a style which is selected in
  559. accordance with the return value of a function.
  560. .. versionadded:: 0.21.0
  561. Parameters
  562. ----------
  563. cond : callable
  564. ``cond`` should take a scalar and return a boolean
  565. value : str
  566. applied when ``cond`` returns true
  567. other : str
  568. applied when ``cond`` returns false
  569. subset : IndexSlice
  570. a valid indexer to limit ``data`` to *before* applying the
  571. function. Consider using a pandas.IndexSlice
  572. kwargs : dict
  573. pass along to ``cond``
  574. Returns
  575. -------
  576. self : Styler
  577. See Also
  578. --------
  579. Styler.applymap
  580. """
  581. if other is None:
  582. other = ''
  583. return self.applymap(lambda val: value if cond(val) else other,
  584. subset=subset, **kwargs)
  585. def set_precision(self, precision):
  586. """
  587. Set the precision used to render.
  588. Parameters
  589. ----------
  590. precision : int
  591. Returns
  592. -------
  593. self : Styler
  594. """
  595. self.precision = precision
  596. return self
  597. def set_table_attributes(self, attributes):
  598. """
  599. Set the table attributes.
  600. These are the items that show up in the opening ``<table>`` tag
  601. in addition to to automatic (by default) id.
  602. Parameters
  603. ----------
  604. attributes : string
  605. Returns
  606. -------
  607. self : Styler
  608. Examples
  609. --------
  610. >>> df = pd.DataFrame(np.random.randn(10, 4))
  611. >>> df.style.set_table_attributes('class="pure-table"')
  612. # ... <table class="pure-table"> ...
  613. """
  614. self.table_attributes = attributes
  615. return self
  616. def export(self):
  617. """
  618. Export the styles to applied to the current Styler.
  619. Can be applied to a second style with ``Styler.use``.
  620. Returns
  621. -------
  622. styles : list
  623. See Also
  624. --------
  625. Styler.use
  626. """
  627. return self._todo
  628. def use(self, styles):
  629. """
  630. Set the styles on the current Styler, possibly using styles
  631. from ``Styler.export``.
  632. Parameters
  633. ----------
  634. styles : list
  635. list of style functions
  636. Returns
  637. -------
  638. self : Styler
  639. See Also
  640. --------
  641. Styler.export
  642. """
  643. self._todo.extend(styles)
  644. return self
  645. def set_uuid(self, uuid):
  646. """
  647. Set the uuid for a Styler.
  648. Parameters
  649. ----------
  650. uuid : str
  651. Returns
  652. -------
  653. self : Styler
  654. """
  655. self.uuid = uuid
  656. return self
  657. def set_caption(self, caption):
  658. """
  659. Set the caption on a Styler
  660. Parameters
  661. ----------
  662. caption : str
  663. Returns
  664. -------
  665. self : Styler
  666. """
  667. self.caption = caption
  668. return self
  669. def set_table_styles(self, table_styles):
  670. """
  671. Set the table styles on a Styler.
  672. These are placed in a ``<style>`` tag before the generated HTML table.
  673. Parameters
  674. ----------
  675. table_styles : list
  676. Each individual table_style should be a dictionary with
  677. ``selector`` and ``props`` keys. ``selector`` should be a CSS
  678. selector that the style will be applied to (automatically
  679. prefixed by the table's UUID) and ``props`` should be a list of
  680. tuples with ``(attribute, value)``.
  681. Returns
  682. -------
  683. self : Styler
  684. Examples
  685. --------
  686. >>> df = pd.DataFrame(np.random.randn(10, 4))
  687. >>> df.style.set_table_styles(
  688. ... [{'selector': 'tr:hover',
  689. ... 'props': [('background-color', 'yellow')]}]
  690. ... )
  691. """
  692. self.table_styles = table_styles
  693. return self
  694. def hide_index(self):
  695. """
  696. Hide any indices from rendering.
  697. .. versionadded:: 0.23.0
  698. Returns
  699. -------
  700. self : Styler
  701. """
  702. self.hidden_index = True
  703. return self
  704. def hide_columns(self, subset):
  705. """
  706. Hide columns from rendering.
  707. .. versionadded:: 0.23.0
  708. Parameters
  709. ----------
  710. subset : IndexSlice
  711. An argument to ``DataFrame.loc`` that identifies which columns
  712. are hidden.
  713. Returns
  714. -------
  715. self : Styler
  716. """
  717. subset = _non_reducing_slice(subset)
  718. hidden_df = self.data.loc[subset]
  719. self.hidden_columns = self.columns.get_indexer_for(hidden_df.columns)
  720. return self
  721. # -----------------------------------------------------------------------
  722. # A collection of "builtin" styles
  723. # -----------------------------------------------------------------------
  724. @staticmethod
  725. def _highlight_null(v, null_color):
  726. return ('background-color: {color}'.format(color=null_color)
  727. if pd.isna(v) else '')
  728. def highlight_null(self, null_color='red'):
  729. """
  730. Shade the background ``null_color`` for missing values.
  731. Parameters
  732. ----------
  733. null_color : str
  734. Returns
  735. -------
  736. self : Styler
  737. """
  738. self.applymap(self._highlight_null, null_color=null_color)
  739. return self
  740. def background_gradient(self, cmap='PuBu', low=0, high=0, axis=0,
  741. subset=None, text_color_threshold=0.408):
  742. """
  743. Color the background in a gradient according to
  744. the data in each column (optionally row).
  745. Requires matplotlib.
  746. Parameters
  747. ----------
  748. cmap : str or colormap
  749. matplotlib colormap
  750. low, high : float
  751. compress the range by these values.
  752. axis : int or str
  753. 1 or 'columns' for columnwise, 0 or 'index' for rowwise
  754. subset : IndexSlice
  755. a valid slice for ``data`` to limit the style application to
  756. text_color_threshold : float or int
  757. luminance threshold for determining text color. Facilitates text
  758. visibility across varying background colors. From 0 to 1.
  759. 0 = all text is dark colored, 1 = all text is light colored.
  760. .. versionadded:: 0.24.0
  761. Returns
  762. -------
  763. self : Styler
  764. Raises
  765. ------
  766. ValueError
  767. If ``text_color_threshold`` is not a value from 0 to 1.
  768. Notes
  769. -----
  770. Set ``text_color_threshold`` or tune ``low`` and ``high`` to keep the
  771. text legible by not using the entire range of the color map. The range
  772. of the data is extended by ``low * (x.max() - x.min())`` and ``high *
  773. (x.max() - x.min())`` before normalizing.
  774. """
  775. subset = _maybe_numeric_slice(self.data, subset)
  776. subset = _non_reducing_slice(subset)
  777. self.apply(self._background_gradient, cmap=cmap, subset=subset,
  778. axis=axis, low=low, high=high,
  779. text_color_threshold=text_color_threshold)
  780. return self
  781. @staticmethod
  782. def _background_gradient(s, cmap='PuBu', low=0, high=0,
  783. text_color_threshold=0.408):
  784. """
  785. Color background in a range according to the data.
  786. """
  787. if (not isinstance(text_color_threshold, (float, int)) or
  788. not 0 <= text_color_threshold <= 1):
  789. msg = "`text_color_threshold` must be a value from 0 to 1."
  790. raise ValueError(msg)
  791. with _mpl(Styler.background_gradient) as (plt, colors):
  792. smin = s.values.min()
  793. smax = s.values.max()
  794. rng = smax - smin
  795. # extend lower / upper bounds, compresses color range
  796. norm = colors.Normalize(smin - (rng * low), smax + (rng * high))
  797. # matplotlib colors.Normalize modifies inplace?
  798. # https://github.com/matplotlib/matplotlib/issues/5427
  799. rgbas = plt.cm.get_cmap(cmap)(norm(s.values))
  800. def relative_luminance(rgba):
  801. """
  802. Calculate relative luminance of a color.
  803. The calculation adheres to the W3C standards
  804. (https://www.w3.org/WAI/GL/wiki/Relative_luminance)
  805. Parameters
  806. ----------
  807. color : rgb or rgba tuple
  808. Returns
  809. -------
  810. float
  811. The relative luminance as a value from 0 to 1
  812. """
  813. r, g, b = (
  814. x / 12.92 if x <= 0.03928 else ((x + 0.055) / 1.055 ** 2.4)
  815. for x in rgba[:3]
  816. )
  817. return 0.2126 * r + 0.7152 * g + 0.0722 * b
  818. def css(rgba):
  819. dark = relative_luminance(rgba) < text_color_threshold
  820. text_color = '#f1f1f1' if dark else '#000000'
  821. return 'background-color: {b};color: {c};'.format(
  822. b=colors.rgb2hex(rgba), c=text_color
  823. )
  824. if s.ndim == 1:
  825. return [css(rgba) for rgba in rgbas]
  826. else:
  827. return pd.DataFrame(
  828. [[css(rgba) for rgba in row] for row in rgbas],
  829. index=s.index, columns=s.columns
  830. )
  831. def set_properties(self, subset=None, **kwargs):
  832. """
  833. Convenience method for setting one or more non-data dependent
  834. properties or each cell.
  835. Parameters
  836. ----------
  837. subset : IndexSlice
  838. a valid slice for ``data`` to limit the style application to
  839. kwargs : dict
  840. property: value pairs to be set for each cell
  841. Returns
  842. -------
  843. self : Styler
  844. Examples
  845. --------
  846. >>> df = pd.DataFrame(np.random.randn(10, 4))
  847. >>> df.style.set_properties(color="white", align="right")
  848. >>> df.style.set_properties(**{'background-color': 'yellow'})
  849. """
  850. values = ';'.join('{p}: {v}'.format(p=p, v=v)
  851. for p, v in kwargs.items())
  852. f = lambda x: values
  853. return self.applymap(f, subset=subset)
  854. @staticmethod
  855. def _bar(s, align, colors, width=100, vmin=None, vmax=None):
  856. """
  857. Draw bar chart in dataframe cells.
  858. """
  859. # Get input value range.
  860. smin = s.min() if vmin is None else vmin
  861. if isinstance(smin, ABCSeries):
  862. smin = smin.min()
  863. smax = s.max() if vmax is None else vmax
  864. if isinstance(smax, ABCSeries):
  865. smax = smax.max()
  866. if align == 'mid':
  867. smin = min(0, smin)
  868. smax = max(0, smax)
  869. elif align == 'zero':
  870. # For "zero" mode, we want the range to be symmetrical around zero.
  871. smax = max(abs(smin), abs(smax))
  872. smin = -smax
  873. # Transform to percent-range of linear-gradient
  874. normed = width * (s.values - smin) / (smax - smin + 1e-12)
  875. zero = -width * smin / (smax - smin + 1e-12)
  876. def css_bar(start, end, color):
  877. """
  878. Generate CSS code to draw a bar from start to end.
  879. """
  880. css = 'width: 10em; height: 80%;'
  881. if end > start:
  882. css += 'background: linear-gradient(90deg,'
  883. if start > 0:
  884. css += ' transparent {s:.1f}%, {c} {s:.1f}%, '.format(
  885. s=start, c=color
  886. )
  887. css += '{c} {e:.1f}%, transparent {e:.1f}%)'.format(
  888. e=min(end, width), c=color,
  889. )
  890. return css
  891. def css(x):
  892. if pd.isna(x):
  893. return ''
  894. # avoid deprecated indexing `colors[x > zero]`
  895. color = colors[1] if x > zero else colors[0]
  896. if align == 'left':
  897. return css_bar(0, x, color)
  898. else:
  899. return css_bar(min(x, zero), max(x, zero), color)
  900. if s.ndim == 1:
  901. return [css(x) for x in normed]
  902. else:
  903. return pd.DataFrame(
  904. [[css(x) for x in row] for row in normed],
  905. index=s.index, columns=s.columns
  906. )
  907. def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
  908. align='left', vmin=None, vmax=None):
  909. """
  910. Draw bar chart in the cell backgrounds.
  911. Parameters
  912. ----------
  913. subset : IndexSlice, optional
  914. A valid slice for `data` to limit the style application to.
  915. axis : int, str or None, default 0
  916. Apply to each column (`axis=0` or `'index'`)
  917. or to each row (`axis=1` or `'columns'`) or
  918. to the entire DataFrame at once with `axis=None`.
  919. color : str or 2-tuple/list
  920. If a str is passed, the color is the same for both
  921. negative and positive numbers. If 2-tuple/list is used, the
  922. first element is the color_negative and the second is the
  923. color_positive (eg: ['#d65f5f', '#5fba7d']).
  924. width : float, default 100
  925. A number between 0 or 100. The largest value will cover `width`
  926. percent of the cell's width.
  927. align : {'left', 'zero',' mid'}, default 'left'
  928. How to align the bars with the cells.
  929. - 'left' : the min value starts at the left of the cell.
  930. - 'zero' : a value of zero is located at the center of the cell.
  931. - 'mid' : the center of the cell is at (max-min)/2, or
  932. if values are all negative (positive) the zero is aligned
  933. at the right (left) of the cell.
  934. .. versionadded:: 0.20.0
  935. vmin : float, optional
  936. Minimum bar value, defining the left hand limit
  937. of the bar drawing range, lower values are clipped to `vmin`.
  938. When None (default): the minimum value of the data will be used.
  939. .. versionadded:: 0.24.0
  940. vmax : float, optional
  941. Maximum bar value, defining the right hand limit
  942. of the bar drawing range, higher values are clipped to `vmax`.
  943. When None (default): the maximum value of the data will be used.
  944. .. versionadded:: 0.24.0
  945. Returns
  946. -------
  947. self : Styler
  948. """
  949. if align not in ('left', 'zero', 'mid'):
  950. raise ValueError("`align` must be one of {'left', 'zero',' mid'}")
  951. if not (is_list_like(color)):
  952. color = [color, color]
  953. elif len(color) == 1:
  954. color = [color[0], color[0]]
  955. elif len(color) > 2:
  956. raise ValueError("`color` must be string or a list-like"
  957. " of length 2: [`color_neg`, `color_pos`]"
  958. " (eg: color=['#d65f5f', '#5fba7d'])")
  959. subset = _maybe_numeric_slice(self.data, subset)
  960. subset = _non_reducing_slice(subset)
  961. self.apply(self._bar, subset=subset, axis=axis,
  962. align=align, colors=color, width=width,
  963. vmin=vmin, vmax=vmax)
  964. return self
  965. def highlight_max(self, subset=None, color='yellow', axis=0):
  966. """
  967. Highlight the maximum by shading the background.
  968. Parameters
  969. ----------
  970. subset : IndexSlice, default None
  971. a valid slice for ``data`` to limit the style application to
  972. color : str, default 'yellow'
  973. axis : int, str, or None; default 0
  974. 0 or 'index' for columnwise (default), 1 or 'columns' for rowwise,
  975. or ``None`` for tablewise
  976. Returns
  977. -------
  978. self : Styler
  979. """
  980. return self._highlight_handler(subset=subset, color=color, axis=axis,
  981. max_=True)
  982. def highlight_min(self, subset=None, color='yellow', axis=0):
  983. """
  984. Highlight the minimum by shading the background.
  985. Parameters
  986. ----------
  987. subset : IndexSlice, default None
  988. a valid slice for ``data`` to limit the style application to
  989. color : str, default 'yellow'
  990. axis : int, str, or None; default 0
  991. 0 or 'index' for columnwise (default), 1 or 'columns' for rowwise,
  992. or ``None`` for tablewise
  993. Returns
  994. -------
  995. self : Styler
  996. """
  997. return self._highlight_handler(subset=subset, color=color, axis=axis,
  998. max_=False)
  999. def _highlight_handler(self, subset=None, color='yellow', axis=None,
  1000. max_=True):
  1001. subset = _non_reducing_slice(_maybe_numeric_slice(self.data, subset))
  1002. self.apply(self._highlight_extrema, color=color, axis=axis,
  1003. subset=subset, max_=max_)
  1004. return self
  1005. @staticmethod
  1006. def _highlight_extrema(data, color='yellow', max_=True):
  1007. """
  1008. Highlight the min or max in a Series or DataFrame.
  1009. """
  1010. attr = 'background-color: {0}'.format(color)
  1011. if data.ndim == 1: # Series from .apply
  1012. if max_:
  1013. extrema = data == data.max()
  1014. else:
  1015. extrema = data == data.min()
  1016. return [attr if v else '' for v in extrema]
  1017. else: # DataFrame from .tee
  1018. if max_:
  1019. extrema = data == data.max().max()
  1020. else:
  1021. extrema = data == data.min().min()
  1022. return pd.DataFrame(np.where(extrema, attr, ''),
  1023. index=data.index, columns=data.columns)
  1024. @classmethod
  1025. def from_custom_template(cls, searchpath, name):
  1026. """
  1027. Factory function for creating a subclass of ``Styler``
  1028. with a custom template and Jinja environment.
  1029. Parameters
  1030. ----------
  1031. searchpath : str or list
  1032. Path or paths of directories containing the templates
  1033. name : str
  1034. Name of your custom template to use for rendering
  1035. Returns
  1036. -------
  1037. MyStyler : subclass of Styler
  1038. has the correct ``env`` and ``template`` class attributes set.
  1039. """
  1040. loader = ChoiceLoader([
  1041. FileSystemLoader(searchpath),
  1042. cls.loader,
  1043. ])
  1044. class MyStyler(cls):
  1045. env = Environment(loader=loader)
  1046. template = env.get_template(name)
  1047. return MyStyler
  1048. def pipe(self, func, *args, **kwargs):
  1049. """
  1050. Apply ``func(self, *args, **kwargs)``, and return the result.
  1051. .. versionadded:: 0.24.0
  1052. Parameters
  1053. ----------
  1054. func : function
  1055. Function to apply to the Styler. Alternatively, a
  1056. ``(callable, keyword)`` tuple where ``keyword`` is a string
  1057. indicating the keyword of ``callable`` that expects the Styler.
  1058. *args, **kwargs :
  1059. Arguments passed to `func`.
  1060. Returns
  1061. -------
  1062. object :
  1063. The value returned by ``func``.
  1064. See Also
  1065. --------
  1066. DataFrame.pipe : Analogous method for DataFrame.
  1067. Styler.apply : Apply a function row-wise, column-wise, or table-wise to
  1068. modify the dataframe's styling.
  1069. Notes
  1070. -----
  1071. Like :meth:`DataFrame.pipe`, this method can simplify the
  1072. application of several user-defined functions to a styler. Instead
  1073. of writing:
  1074. .. code-block:: python
  1075. f(g(df.style.set_precision(3), arg1=a), arg2=b, arg3=c)
  1076. users can write:
  1077. .. code-block:: python
  1078. (df.style.set_precision(3)
  1079. .pipe(g, arg1=a)
  1080. .pipe(f, arg2=b, arg3=c))
  1081. In particular, this allows users to define functions that take a
  1082. styler object, along with other parameters, and return the styler after
  1083. making styling changes (such as calling :meth:`Styler.apply` or
  1084. :meth:`Styler.set_properties`). Using ``.pipe``, these user-defined
  1085. style "transformations" can be interleaved with calls to the built-in
  1086. Styler interface.
  1087. Examples
  1088. --------
  1089. >>> def format_conversion(styler):
  1090. ... return (styler.set_properties(**{'text-align': 'right'})
  1091. ... .format({'conversion': '{:.1%}'}))
  1092. The user-defined ``format_conversion`` function above can be called
  1093. within a sequence of other style modifications:
  1094. >>> df = pd.DataFrame({'trial': list(range(5)),
  1095. ... 'conversion': [0.75, 0.85, np.nan, 0.7, 0.72]})
  1096. >>> (df.style
  1097. ... .highlight_min(subset=['conversion'], color='yellow')
  1098. ... .pipe(format_conversion)
  1099. ... .set_caption("Results with minimum conversion highlighted."))
  1100. """
  1101. return com._pipe(self, func, *args, **kwargs)
  1102. def _is_visible(idx_row, idx_col, lengths):
  1103. """
  1104. Index -> {(idx_row, idx_col): bool}).
  1105. """
  1106. return (idx_col, idx_row) in lengths
  1107. def _get_level_lengths(index, hidden_elements=None):
  1108. """
  1109. Given an index, find the level length for each element.
  1110. Optional argument is a list of index positions which
  1111. should not be visible.
  1112. Result is a dictionary of (level, inital_position): span
  1113. """
  1114. sentinel = com.sentinel_factory()
  1115. levels = index.format(sparsify=sentinel, adjoin=False, names=False)
  1116. if hidden_elements is None:
  1117. hidden_elements = []
  1118. lengths = {}
  1119. if index.nlevels == 1:
  1120. for i, value in enumerate(levels):
  1121. if(i not in hidden_elements):
  1122. lengths[(0, i)] = 1
  1123. return lengths
  1124. for i, lvl in enumerate(levels):
  1125. for j, row in enumerate(lvl):
  1126. if not get_option('display.multi_sparse'):
  1127. lengths[(i, j)] = 1
  1128. elif (row != sentinel) and (j not in hidden_elements):
  1129. last_label = j
  1130. lengths[(i, last_label)] = 1
  1131. elif (row != sentinel):
  1132. # even if its hidden, keep track of it in case
  1133. # length >1 and later elements are visible
  1134. last_label = j
  1135. lengths[(i, last_label)] = 0
  1136. elif(j not in hidden_elements):
  1137. lengths[(i, last_label)] += 1
  1138. non_zero_lengths = {
  1139. element: length for element, length in lengths.items() if length >= 1}
  1140. return non_zero_lengths
  1141. def _maybe_wrap_formatter(formatter):
  1142. if is_string_like(formatter):
  1143. return lambda x: formatter.format(x)
  1144. elif callable(formatter):
  1145. return formatter
  1146. else:
  1147. msg = ("Expected a template string or callable, got {formatter} "
  1148. "instead".format(formatter=formatter))
  1149. raise TypeError(msg)