1{
2 "cells": [
3  {
4   "cell_type": "markdown",
5   "metadata": {},
6   "source": [
7    "# Styling\n",
8    "\n",
9    "This document is written as a Jupyter Notebook, and can be viewed or downloaded [here](https://nbviewer.ipython.org/github/pandas-dev/pandas/blob/master/doc/source/user_guide/style.ipynb).\n",
10    "\n",
11    "You can apply **conditional formatting**, the visual styling of a DataFrame\n",
12    "depending on the data within, by using the ``DataFrame.style`` property.\n",
13    "This is a property that returns a ``Styler`` object, which has\n",
14    "useful methods for formatting and displaying DataFrames.\n",
15    "\n",
16    "The styling is accomplished using CSS.\n",
17    "You write \"style functions\" that take scalars, `DataFrame`s or `Series`, and return *like-indexed* DataFrames or Series with CSS `\"attribute: value\"` pairs for the values.\n",
18    "These functions can be incrementally passed to the `Styler` which collects the styles before rendering."
19   ]
20  },
21  {
22   "cell_type": "markdown",
23   "metadata": {},
24   "source": [
25    "## Building styles\n",
26    "\n",
27    "Pass your style functions into one of the following methods:\n",
28    "\n",
29    "- ``Styler.applymap``: elementwise\n",
30    "- ``Styler.apply``: column-/row-/table-wise\n",
31    "\n",
32    "Both of those methods take a function (and some other keyword arguments) and applies your function to the DataFrame in a certain way.\n",
33    "`Styler.applymap` works through the DataFrame elementwise.\n",
34    "`Styler.apply` passes each column or row into your DataFrame one-at-a-time or the entire table at once, depending on the `axis` keyword argument.\n",
35    "For columnwise use `axis=0`, rowwise use `axis=1`, and for the entire table at once use `axis=None`.\n",
36    "\n",
37    "For `Styler.applymap` your function should take a scalar and return a single string with the CSS attribute-value pair.\n",
38    "\n",
39    "For `Styler.apply` your function should take a Series or DataFrame (depending on the axis parameter), and return a Series or DataFrame with an identical shape where each value is a string with a CSS attribute-value pair.\n",
40    "\n",
41    "Let's see some examples."
42   ]
43  },
44  {
45   "cell_type": "code",
46   "execution_count": null,
47   "metadata": {
48    "nbsphinx": "hidden"
49   },
50   "outputs": [],
51   "source": [
52    "import matplotlib.pyplot\n",
53    "# We have this here to trigger matplotlib's font cache stuff.\n",
54    "# This cell is hidden from the output"
55   ]
56  },
57  {
58   "cell_type": "code",
59   "execution_count": null,
60   "metadata": {},
61   "outputs": [],
62   "source": [
63    "import pandas as pd\n",
64    "import numpy as np\n",
65    "\n",
66    "np.random.seed(24)\n",
67    "df = pd.DataFrame({'A': np.linspace(1, 10, 10)})\n",
68    "df = pd.concat([df, pd.DataFrame(np.random.randn(10, 4), columns=list('BCDE'))],\n",
69    "               axis=1)\n",
70    "df.iloc[3, 3] = np.nan\n",
71    "df.iloc[0, 2] = np.nan"
72   ]
73  },
74  {
75   "cell_type": "markdown",
76   "metadata": {},
77   "source": [
78    "Here's a boring example of rendering a DataFrame, without any (visible) styles:"
79   ]
80  },
81  {
82   "cell_type": "code",
83   "execution_count": null,
84   "metadata": {},
85   "outputs": [],
86   "source": [
87    "df.style"
88   ]
89  },
90  {
91   "cell_type": "markdown",
92   "metadata": {},
93   "source": [
94    "*Note*: The `DataFrame.style` attribute is a property that returns a `Styler` object. `Styler` has a `_repr_html_` method defined on it so they are rendered automatically. If you want the actual HTML back for further processing or for writing to file call the `.render()` method which returns a string.\n",
95    "\n",
96    "The above output looks very similar to the standard DataFrame HTML representation. But we've done some work behind the scenes to attach CSS classes to each cell. We can view these by calling the `.render` method."
97   ]
98  },
99  {
100   "cell_type": "code",
101   "execution_count": null,
102   "metadata": {},
103   "outputs": [],
104   "source": [
105    "df.style.highlight_null().render().split('\\n')[:10]"
106   ]
107  },
108  {
109   "cell_type": "markdown",
110   "metadata": {},
111   "source": [
112    "The `row0_col2` is the identifier for that particular cell. We've also prepended each row/column identifier with a UUID unique to each DataFrame so that the style from one doesn't collide with the styling from another within the same notebook or page (you can set the `uuid` if you'd like to tie together the styling of two DataFrames).\n",
113    "\n",
114    "When writing style functions, you take care of producing the CSS attribute / value pairs you want. Pandas matches those up with the CSS classes that identify each cell."
115   ]
116  },
117  {
118   "cell_type": "markdown",
119   "metadata": {},
120   "source": [
121    "Let's write a simple style function that will color negative numbers red and positive numbers black."
122   ]
123  },
124  {
125   "cell_type": "code",
126   "execution_count": null,
127   "metadata": {},
128   "outputs": [],
129   "source": [
130    "def color_negative_red(val):\n",
131    "    \"\"\"\n",
132    "    Takes a scalar and returns a string with\n",
133    "    the css property `'color: red'` for negative\n",
134    "    strings, black otherwise.\n",
135    "    \"\"\"\n",
136    "    color = 'red' if val < 0 else 'black'\n",
137    "    return 'color: %s' % color"
138   ]
139  },
140  {
141   "cell_type": "markdown",
142   "metadata": {},
143   "source": [
144    "In this case, the cell's style depends only on its own value.\n",
145    "That means we should use the `Styler.applymap` method which works elementwise."
146   ]
147  },
148  {
149   "cell_type": "code",
150   "execution_count": null,
151   "metadata": {},
152   "outputs": [],
153   "source": [
154    "s = df.style.applymap(color_negative_red)\n",
155    "s"
156   ]
157  },
158  {
159   "cell_type": "markdown",
160   "metadata": {},
161   "source": [
162    "Notice the similarity with the standard `df.applymap`, which operates on DataFrames elementwise. We want you to be able to reuse your existing knowledge of how to interact with DataFrames.\n",
163    "\n",
164    "Notice also that our function returned a string containing the CSS attribute and value, separated by a colon just like in a `<style>` tag. This will be a common theme.\n",
165    "\n",
166    "Finally, the input shapes matched. `Styler.applymap` calls the function on each scalar input, and the function returns a scalar output."
167   ]
168  },
169  {
170   "cell_type": "markdown",
171   "metadata": {},
172   "source": [
173    "Now suppose you wanted to highlight the maximum value in each column.\n",
174    "We can't use `.applymap` anymore since that operated elementwise.\n",
175    "Instead, we'll turn to `.apply` which operates columnwise (or rowwise using the `axis` keyword). Later on we'll see that something like `highlight_max` is already defined on `Styler` so you wouldn't need to write this yourself."
176   ]
177  },
178  {
179   "cell_type": "code",
180   "execution_count": null,
181   "metadata": {},
182   "outputs": [],
183   "source": [
184    "def highlight_max(s):\n",
185    "    '''\n",
186    "    highlight the maximum in a Series yellow.\n",
187    "    '''\n",
188    "    is_max = s == s.max()\n",
189    "    return ['background-color: yellow' if v else '' for v in is_max]"
190   ]
191  },
192  {
193   "cell_type": "code",
194   "execution_count": null,
195   "metadata": {},
196   "outputs": [],
197   "source": [
198    "df.style.apply(highlight_max)"
199   ]
200  },
201  {
202   "cell_type": "markdown",
203   "metadata": {},
204   "source": [
205    "In this case the input is a `Series`, one column at a time.\n",
206    "Notice that the output shape of `highlight_max` matches the input shape, an array with `len(s)` items."
207   ]
208  },
209  {
210   "cell_type": "markdown",
211   "metadata": {},
212   "source": [
213    "We encourage you to use method chains to build up a style piecewise, before finally rending at the end of the chain."
214   ]
215  },
216  {
217   "cell_type": "code",
218   "execution_count": null,
219   "metadata": {},
220   "outputs": [],
221   "source": [
222    "df.style.\\\n",
223    "    applymap(color_negative_red).\\\n",
224    "    apply(highlight_max)"
225   ]
226  },
227  {
228   "cell_type": "markdown",
229   "metadata": {},
230   "source": [
231    "Above we used `Styler.apply` to pass in each column one at a time.\n",
232    "\n",
233    "<span style=\"background-color: #DEDEBE\">*Debugging Tip*: If you're having trouble writing your style function, try just passing it into <code style=\"background-color: #DEDEBE\">DataFrame.apply</code>. Internally, <code style=\"background-color: #DEDEBE\">Styler.apply</code> uses <code style=\"background-color: #DEDEBE\">DataFrame.apply</code> so the result should be the same.</span>\n",
234    "\n",
235    "What if you wanted to highlight just the maximum value in the entire table?\n",
236    "Use `.apply(function, axis=None)` to indicate that your function wants the entire table, not one column or row at a time. Let's try that next.\n",
237    "\n",
238    "We'll rewrite our `highlight-max` to handle either Series (from `.apply(axis=0 or 1)`) or DataFrames (from `.apply(axis=None)`). We'll also allow the color to be adjustable, to demonstrate that `.apply`, and `.applymap` pass along keyword arguments."
239   ]
240  },
241  {
242   "cell_type": "code",
243   "execution_count": null,
244   "metadata": {},
245   "outputs": [],
246   "source": [
247    "def highlight_max(data, color='yellow'):\n",
248    "    '''\n",
249    "    highlight the maximum in a Series or DataFrame\n",
250    "    '''\n",
251    "    attr = 'background-color: {}'.format(color)\n",
252    "    if data.ndim == 1:  # Series from .apply(axis=0) or axis=1\n",
253    "        is_max = data == data.max()\n",
254    "        return [attr if v else '' for v in is_max]\n",
255    "    else:  # from .apply(axis=None)\n",
256    "        is_max = data == data.max().max()\n",
257    "        return pd.DataFrame(np.where(is_max, attr, ''),\n",
258    "                            index=data.index, columns=data.columns)"
259   ]
260  },
261  {
262   "cell_type": "markdown",
263   "metadata": {},
264   "source": [
265    "When using ``Styler.apply(func, axis=None)``, the function must return a DataFrame with the same index and column labels."
266   ]
267  },
268  {
269   "cell_type": "code",
270   "execution_count": null,
271   "metadata": {},
272   "outputs": [],
273   "source": [
274    "df.style.apply(highlight_max, color='darkorange', axis=None)"
275   ]
276  },
277  {
278   "cell_type": "markdown",
279   "metadata": {},
280   "source": [
281    "### Building Styles Summary\n",
282    "\n",
283    "Style functions should return strings with one or more CSS `attribute: value` delimited by semicolons. Use\n",
284    "\n",
285    "- `Styler.applymap(func)` for elementwise styles\n",
286    "- `Styler.apply(func, axis=0)` for columnwise styles\n",
287    "- `Styler.apply(func, axis=1)` for rowwise styles\n",
288    "- `Styler.apply(func, axis=None)` for tablewise styles\n",
289    "\n",
290    "And crucially the input and output shapes of `func` must match. If `x` is the input then ``func(x).shape == x.shape``."
291   ]
292  },
293  {
294   "cell_type": "markdown",
295   "metadata": {},
296   "source": [
297    "## Finer control: slicing"
298   ]
299  },
300  {
301   "cell_type": "markdown",
302   "metadata": {},
303   "source": [
304    "Both `Styler.apply`, and `Styler.applymap` accept a `subset` keyword.\n",
305    "This allows you to apply styles to specific rows or columns, without having to code that logic into your `style` function.\n",
306    "\n",
307    "The value passed to `subset` behaves similar to slicing a DataFrame.\n",
308    "\n",
309    "- A scalar is treated as a column label\n",
310    "- A list (or series or numpy array)\n",
311    "- A tuple is treated as `(row_indexer, column_indexer)`\n",
312    "\n",
313    "Consider using `pd.IndexSlice` to construct the tuple for the last one."
314   ]
315  },
316  {
317   "cell_type": "code",
318   "execution_count": null,
319   "metadata": {},
320   "outputs": [],
321   "source": [
322    "df.style.apply(highlight_max, subset=['B', 'C', 'D'])"
323   ]
324  },
325  {
326   "cell_type": "markdown",
327   "metadata": {},
328   "source": [
329    "For row and column slicing, any valid indexer to `.loc` will work."
330   ]
331  },
332  {
333   "cell_type": "code",
334   "execution_count": null,
335   "metadata": {},
336   "outputs": [],
337   "source": [
338    "df.style.applymap(color_negative_red,\n",
339    "                  subset=pd.IndexSlice[2:5, ['B', 'D']])"
340   ]
341  },
342  {
343   "cell_type": "markdown",
344   "metadata": {},
345   "source": [
346    "Only label-based slicing is supported right now, not positional.\n",
347    "\n",
348    "If your style function uses a `subset` or `axis` keyword argument, consider wrapping your function in a `functools.partial`, partialing out that keyword.\n",
349    "\n",
350    "```python\n",
351    "my_func2 = functools.partial(my_func, subset=42)\n",
352    "```"
353   ]
354  },
355  {
356   "cell_type": "markdown",
357   "metadata": {},
358   "source": [
359    "## Finer Control: Display Values\n",
360    "\n",
361    "We distinguish the *display* value from the *actual* value in `Styler`.\n",
362    "To control the display value, the text is printed in each cell, use `Styler.format`. Cells can be formatted according to a [format spec string](https://docs.python.org/3/library/string.html#format-specification-mini-language) or a callable that takes a single value and returns a string."
363   ]
364  },
365  {
366   "cell_type": "code",
367   "execution_count": null,
368   "metadata": {},
369   "outputs": [],
370   "source": [
371    "df.style.format(\"{:.2%}\")"
372   ]
373  },
374  {
375   "cell_type": "markdown",
376   "metadata": {},
377   "source": [
378    "Use a dictionary to format specific columns."
379   ]
380  },
381  {
382   "cell_type": "code",
383   "execution_count": null,
384   "metadata": {},
385   "outputs": [],
386   "source": [
387    "df.style.format({'B': \"{:0<4.0f}\", 'D': '{:+.2f}'})"
388   ]
389  },
390  {
391   "cell_type": "markdown",
392   "metadata": {},
393   "source": [
394    "Or pass in a callable (or dictionary of callables) for more flexible handling."
395   ]
396  },
397  {
398   "cell_type": "code",
399   "execution_count": null,
400   "metadata": {},
401   "outputs": [],
402   "source": [
403    "df.style.format({\"B\": lambda x: \"±{:.2f}\".format(abs(x))})"
404   ]
405  },
406  {
407   "cell_type": "markdown",
408   "metadata": {},
409   "source": [
410    "You can format the text displayed for missing values by `na_rep`."
411   ]
412  },
413  {
414   "cell_type": "code",
415   "execution_count": null,
416   "metadata": {},
417   "outputs": [],
418   "source": [
419    "df.style.format(\"{:.2%}\", na_rep=\"-\")"
420   ]
421  },
422  {
423   "cell_type": "markdown",
424   "metadata": {},
425   "source": [
426    "These formatting techniques can be used in combination with styling."
427   ]
428  },
429  {
430   "cell_type": "code",
431   "execution_count": null,
432   "metadata": {},
433   "outputs": [],
434   "source": [
435    "df.style.highlight_max().format(None, na_rep=\"-\")"
436   ]
437  },
438  {
439   "cell_type": "markdown",
440   "metadata": {},
441   "source": [
442    "## Builtin styles"
443   ]
444  },
445  {
446   "cell_type": "markdown",
447   "metadata": {},
448   "source": [
449    "Finally, we expect certain styling functions to be common enough that we've included a few \"built-in\" to the `Styler`, so you don't have to write them yourself."
450   ]
451  },
452  {
453   "cell_type": "code",
454   "execution_count": null,
455   "metadata": {},
456   "outputs": [],
457   "source": [
458    "df.style.highlight_null(null_color='red')"
459   ]
460  },
461  {
462   "cell_type": "markdown",
463   "metadata": {},
464   "source": [
465    "You can create \"heatmaps\" with the `background_gradient` method. These require matplotlib, and we'll use [Seaborn](https://stanford.edu/~mwaskom/software/seaborn/) to get a nice colormap."
466   ]
467  },
468  {
469   "cell_type": "code",
470   "execution_count": null,
471   "metadata": {},
472   "outputs": [],
473   "source": [
474    "import seaborn as sns\n",
475    "\n",
476    "cm = sns.light_palette(\"green\", as_cmap=True)\n",
477    "\n",
478    "s = df.style.background_gradient(cmap=cm)\n",
479    "s"
480   ]
481  },
482  {
483   "cell_type": "markdown",
484   "metadata": {},
485   "source": [
486    "`Styler.background_gradient` takes the keyword arguments `low` and `high`. Roughly speaking these extend the range of your data by `low` and `high` percent so that when we convert the colors, the colormap's entire range isn't used. This is useful so that you can actually read the text still."
487   ]
488  },
489  {
490   "cell_type": "code",
491   "execution_count": null,
492   "metadata": {},
493   "outputs": [],
494   "source": [
495    "# Uses the full color range\n",
496    "df.loc[:4].style.background_gradient(cmap='viridis')"
497   ]
498  },
499  {
500   "cell_type": "code",
501   "execution_count": null,
502   "metadata": {},
503   "outputs": [],
504   "source": [
505    "# Compress the color range\n",
506    "(df.loc[:4]\n",
507    "    .style\n",
508    "    .background_gradient(cmap='viridis', low=.5, high=0)\n",
509    "    .highlight_null('red'))"
510   ]
511  },
512  {
513   "cell_type": "markdown",
514   "metadata": {},
515   "source": [
516    "There's also `.highlight_min` and `.highlight_max`."
517   ]
518  },
519  {
520   "cell_type": "code",
521   "execution_count": null,
522   "metadata": {},
523   "outputs": [],
524   "source": [
525    "df.style.highlight_max(axis=0)"
526   ]
527  },
528  {
529   "cell_type": "markdown",
530   "metadata": {},
531   "source": [
532    "Use `Styler.set_properties` when the style doesn't actually depend on the values."
533   ]
534  },
535  {
536   "cell_type": "code",
537   "execution_count": null,
538   "metadata": {},
539   "outputs": [],
540   "source": [
541    "df.style.set_properties(**{'background-color': 'black',\n",
542    "                           'color': 'lawngreen',\n",
543    "                           'border-color': 'white'})"
544   ]
545  },
546  {
547   "cell_type": "markdown",
548   "metadata": {},
549   "source": [
550    "### Bar charts"
551   ]
552  },
553  {
554   "cell_type": "markdown",
555   "metadata": {},
556   "source": [
557    "You can include \"bar charts\" in your DataFrame."
558   ]
559  },
560  {
561   "cell_type": "code",
562   "execution_count": null,
563   "metadata": {},
564   "outputs": [],
565   "source": [
566    "df.style.bar(subset=['A', 'B'], color='#d65f5f')"
567   ]
568  },
569  {
570   "cell_type": "markdown",
571   "metadata": {},
572   "source": [
573    "New in version 0.20.0 is the ability to customize further the bar chart: You can now have the `df.style.bar` be centered on zero or midpoint value (in addition to the already existing way of having the min value at the left side of the cell), and you can pass a list of `[color_negative, color_positive]`.\n",
574    "\n",
575    "Here's how you can change the above with the new `align='mid'` option:"
576   ]
577  },
578  {
579   "cell_type": "code",
580   "execution_count": null,
581   "metadata": {},
582   "outputs": [],
583   "source": [
584    "df.style.bar(subset=['A', 'B'], align='mid', color=['#d65f5f', '#5fba7d'])"
585   ]
586  },
587  {
588   "cell_type": "markdown",
589   "metadata": {},
590   "source": [
591    "The following example aims to give a highlight of the behavior of the new align options:"
592   ]
593  },
594  {
595   "cell_type": "code",
596   "execution_count": null,
597   "metadata": {},
598   "outputs": [],
599   "source": [
600    "import pandas as pd\n",
601    "from IPython.display import HTML\n",
602    "\n",
603    "# Test series\n",
604    "test1 = pd.Series([-100,-60,-30,-20], name='All Negative')\n",
605    "test2 = pd.Series([10,20,50,100], name='All Positive')\n",
606    "test3 = pd.Series([-10,-5,0,90], name='Both Pos and Neg')\n",
607    "\n",
608    "head = \"\"\"\n",
609    "<table>\n",
610    "    <thead>\n",
611    "        <th>Align</th>\n",
612    "        <th>All Negative</th>\n",
613    "        <th>All Positive</th>\n",
614    "        <th>Both Neg and Pos</th>\n",
615    "    </thead>\n",
616    "    </tbody>\n",
617    "\n",
618    "\"\"\"\n",
619    "\n",
620    "aligns = ['left','zero','mid']\n",
621    "for align in aligns:\n",
622    "    row = \"<tr><th>{}</th>\".format(align)\n",
623    "    for series in [test1,test2,test3]:\n",
624    "        s = series.copy()\n",
625    "        s.name=''\n",
626    "        row += \"<td>{}</td>\".format(s.to_frame().style.bar(align=align, \n",
627    "                                                           color=['#d65f5f', '#5fba7d'], \n",
628    "                                                           width=100).render()) #testn['width']\n",
629    "    row += '</tr>'\n",
630    "    head += row\n",
631    "    \n",
632    "head+= \"\"\"\n",
633    "</tbody>\n",
634    "</table>\"\"\"\n",
635    "        \n",
636    "\n",
637    "HTML(head)"
638   ]
639  },
640  {
641   "cell_type": "markdown",
642   "metadata": {},
643   "source": [
644    "## Sharing styles"
645   ]
646  },
647  {
648   "cell_type": "markdown",
649   "metadata": {},
650   "source": [
651    "Say you have a lovely style built up for a DataFrame, and now you want to apply the same style to a second DataFrame. Export the style with `df1.style.export`, and import it on the second DataFrame with `df1.style.set`"
652   ]
653  },
654  {
655   "cell_type": "code",
656   "execution_count": null,
657   "metadata": {},
658   "outputs": [],
659   "source": [
660    "df2 = -df\n",
661    "style1 = df.style.applymap(color_negative_red)\n",
662    "style1"
663   ]
664  },
665  {
666   "cell_type": "code",
667   "execution_count": null,
668   "metadata": {},
669   "outputs": [],
670   "source": [
671    "style2 = df2.style\n",
672    "style2.use(style1.export())\n",
673    "style2"
674   ]
675  },
676  {
677   "cell_type": "markdown",
678   "metadata": {},
679   "source": [
680    "Notice that you're able to share the styles even though they're data aware. The styles are re-evaluated on the new DataFrame they've been `use`d upon."
681   ]
682  },
683  {
684   "cell_type": "markdown",
685   "metadata": {},
686   "source": [
687    "## Other Options\n",
688    "\n",
689    "You've seen a few methods for data-driven styling.\n",
690    "`Styler` also provides a few other options for styles that don't depend on the data.\n",
691    "\n",
692    "- precision\n",
693    "- captions\n",
694    "- table-wide styles\n",
695    "- missing values representation\n",
696    "- hiding the index or columns\n",
697    "\n",
698    "Each of these can be specified in two ways:\n",
699    "\n",
700    "- A keyword argument to `Styler.__init__`\n",
701    "- A call to one of the `.set_` or `.hide_` methods, e.g. `.set_caption` or `.hide_columns`\n",
702    "\n",
703    "The best method to use depends on the context. Use the `Styler` constructor when building many styled DataFrames that should all share the same properties. For interactive use, the`.set_` and `.hide_` methods are more convenient."
704   ]
705  },
706  {
707   "cell_type": "markdown",
708   "metadata": {},
709   "source": [
710    "### Precision"
711   ]
712  },
713  {
714   "cell_type": "markdown",
715   "metadata": {},
716   "source": [
717    "You can control the precision of floats using pandas' regular `display.precision` option."
718   ]
719  },
720  {
721   "cell_type": "code",
722   "execution_count": null,
723   "metadata": {},
724   "outputs": [],
725   "source": [
726    "with pd.option_context('display.precision', 2):\n",
727    "    html = (df.style\n",
728    "              .applymap(color_negative_red)\n",
729    "              .apply(highlight_max))\n",
730    "html"
731   ]
732  },
733  {
734   "cell_type": "markdown",
735   "metadata": {},
736   "source": [
737    "Or through a `set_precision` method."
738   ]
739  },
740  {
741   "cell_type": "code",
742   "execution_count": null,
743   "metadata": {},
744   "outputs": [],
745   "source": [
746    "df.style\\\n",
747    "  .applymap(color_negative_red)\\\n",
748    "  .apply(highlight_max)\\\n",
749    "  .set_precision(2)"
750   ]
751  },
752  {
753   "cell_type": "markdown",
754   "metadata": {},
755   "source": [
756    "Setting the precision only affects the printed number; the full-precision values are always passed to your style functions. You can always use `df.round(2).style` if you'd prefer to round from the start."
757   ]
758  },
759  {
760   "cell_type": "markdown",
761   "metadata": {},
762   "source": [
763    "### Captions"
764   ]
765  },
766  {
767   "cell_type": "markdown",
768   "metadata": {},
769   "source": [
770    "Regular table captions can be added in a few ways."
771   ]
772  },
773  {
774   "cell_type": "code",
775   "execution_count": null,
776   "metadata": {},
777   "outputs": [],
778   "source": [
779    "df.style.set_caption('Colormaps, with a caption.')\\\n",
780    "    .background_gradient(cmap=cm)"
781   ]
782  },
783  {
784   "cell_type": "markdown",
785   "metadata": {},
786   "source": [
787    "### Table styles"
788   ]
789  },
790  {
791   "cell_type": "markdown",
792   "metadata": {},
793   "source": [
794    "The next option you have are \"table styles\".\n",
795    "These are styles that apply to the table as a whole, but don't look at the data.\n",
796    "Certain stylings, including pseudo-selectors like `:hover` can only be used this way.\n",
797    "These can also be used to set specific row or column based class selectors, as will be shown."
798   ]
799  },
800  {
801   "cell_type": "code",
802   "execution_count": null,
803   "metadata": {},
804   "outputs": [],
805   "source": [
806    "from IPython.display import HTML\n",
807    "\n",
808    "def hover(hover_color=\"#ffff99\"):\n",
809    "    return dict(selector=\"tr:hover\",\n",
810    "                props=[(\"background-color\", \"%s\" % hover_color)])\n",
811    "\n",
812    "styles = [\n",
813    "    hover(),\n",
814    "    dict(selector=\"th\", props=[(\"font-size\", \"150%\"),\n",
815    "                               (\"text-align\", \"center\")]),\n",
816    "    dict(selector=\"caption\", props=[(\"caption-side\", \"bottom\")])\n",
817    "]\n",
818    "html = (df.style.set_table_styles(styles)\n",
819    "          .set_caption(\"Hover to highlight.\"))\n",
820    "html"
821   ]
822  },
823  {
824   "cell_type": "markdown",
825   "metadata": {},
826   "source": [
827    "`table_styles` should be a list of dictionaries.\n",
828    "Each dictionary should have the `selector` and `props` keys.\n",
829    "The value for `selector` should be a valid CSS selector.\n",
830    "Recall that all the styles are already attached to an `id`, unique to\n",
831    "each `Styler`. This selector is in addition to that `id`.\n",
832    "The value for `props` should be a list of tuples of `('attribute', 'value')`.\n",
833    "\n",
834    "`table_styles` are extremely flexible, but not as fun to type out by hand.\n",
835    "We hope to collect some useful ones either in pandas, or preferable in a new package that [builds on top](#Extensibility) the tools here.\n",
836    "\n",
837    "`table_styles` can be used to add column and row based class descriptors. For large tables this can increase performance by avoiding repetitive individual css for each cell, and it can also simplify style construction in some cases.\n",
838    "If `table_styles` is given as a dictionary each key should be a specified column or index value and this will map to specific class CSS selectors of the given column or row.\n",
839    "\n",
840    "Note that `Styler.set_table_styles` will overwrite existing styles but can be chained by setting the `overwrite` argument to `False`."
841   ]
842  },
843  {
844   "cell_type": "code",
845   "execution_count": null,
846   "outputs": [],
847   "source": [
848    "html = html.set_table_styles({\n",
849    "    'B': [dict(selector='', props=[('color', 'green')])],\n",
850    "    'C': [dict(selector='td', props=[('color', 'red')])], \n",
851    "    }, overwrite=False)\n",
852    "html"
853   ],
854   "metadata": {
855    "collapsed": false,
856    "pycharm": {
857     "name": "#%%\n"
858    }
859   }
860  },
861  {
862   "cell_type": "markdown",
863   "metadata": {},
864   "source": [
865    "### Missing values"
866   ]
867  },
868  {
869   "cell_type": "markdown",
870   "metadata": {},
871   "source": [
872    "You can control the default missing values representation for the entire table through `set_na_rep` method."
873   ]
874  },
875  {
876   "cell_type": "code",
877   "execution_count": null,
878   "metadata": {},
879   "outputs": [],
880   "source": [
881    "(df.style\n",
882    "   .set_na_rep(\"FAIL\")\n",
883    "   .format(None, na_rep=\"PASS\", subset=[\"D\"])\n",
884    "   .highlight_null(\"yellow\"))"
885   ]
886  },
887  {
888   "cell_type": "markdown",
889   "metadata": {},
890   "source": [
891    "### Hiding the Index or Columns"
892   ]
893  },
894  {
895   "cell_type": "markdown",
896   "metadata": {},
897   "source": [
898    "The index can be hidden from rendering by calling `Styler.hide_index`. Columns can be hidden from rendering by calling `Styler.hide_columns` and passing in the name of a column, or a slice of columns."
899   ]
900  },
901  {
902   "cell_type": "code",
903   "execution_count": null,
904   "metadata": {},
905   "outputs": [],
906   "source": [
907    "df.style.hide_index()"
908   ]
909  },
910  {
911   "cell_type": "code",
912   "execution_count": null,
913   "metadata": {},
914   "outputs": [],
915   "source": [
916    "df.style.hide_columns(['C','D'])"
917   ]
918  },
919  {
920   "cell_type": "markdown",
921   "metadata": {},
922   "source": [
923    "### CSS classes\n",
924    "\n",
925    "Certain CSS classes are attached to cells.\n",
926    "\n",
927    "- Index and Column names include `index_name` and `level<k>` where `k` is its level in a MultiIndex\n",
928    "- Index label cells include\n",
929    "  + `row_heading`\n",
930    "  + `row<n>` where `n` is the numeric position of the row\n",
931    "  + `level<k>` where `k` is the level in a MultiIndex\n",
932    "- Column label cells include\n",
933    "  + `col_heading`\n",
934    "  + `col<n>` where `n` is the numeric position of the column\n",
935    "  + `level<k>` where `k` is the level in a MultiIndex\n",
936    "- Blank cells include `blank`\n",
937    "- Data cells include `data`"
938   ]
939  },
940  {
941   "cell_type": "markdown",
942   "metadata": {},
943   "source": [
944    "### Limitations\n",
945    "\n",
946    "- DataFrame only `(use Series.to_frame().style)`\n",
947    "- The index and columns must be unique\n",
948    "- No large repr, and performance isn't great; this is intended for summary DataFrames\n",
949    "- You can only style the *values*, not the index or columns (except with `table_styles` above)\n",
950    "- You can only apply styles, you can't insert new HTML entities\n",
951    "\n",
952    "Some of these will be addressed in the future.\n",
953    "Performance can suffer when adding styles to each cell in a large DataFrame.\n",
954    "It is recommended to apply table or column based styles where possible to limit overall HTML length, as well as setting a shorter UUID to avoid unnecessary repeated data transmission. \n"
955   ]
956  },
957  {
958   "cell_type": "markdown",
959   "metadata": {},
960   "source": [
961    "### Terms\n",
962    "\n",
963    "- Style function: a function that's passed into `Styler.apply` or `Styler.applymap` and returns values like `'css attribute: value'`\n",
964    "- Builtin style functions: style functions that are methods on `Styler`\n",
965    "- table style: a dictionary with the two keys `selector` and `props`. `selector` is the CSS selector that `props` will apply to. `props` is a list of `(attribute, value)` tuples. A list of table styles passed into `Styler`."
966   ]
967  },
968  {
969   "cell_type": "markdown",
970   "metadata": {},
971   "source": [
972    "## Fun stuff\n",
973    "\n",
974    "Here are a few interesting examples.\n",
975    "\n",
976    "`Styler` interacts pretty well with widgets. If you're viewing this online instead of running the notebook yourself, you're missing out on interactively adjusting the color palette."
977   ]
978  },
979  {
980   "cell_type": "code",
981   "execution_count": null,
982   "metadata": {},
983   "outputs": [],
984   "source": [
985    "from IPython.html import widgets\n",
986    "@widgets.interact\n",
987    "def f(h_neg=(0, 359, 1), h_pos=(0, 359), s=(0., 99.9), l=(0., 99.9)):\n",
988    "    return df.style.background_gradient(\n",
989    "        cmap=sns.palettes.diverging_palette(h_neg=h_neg, h_pos=h_pos, s=s, l=l,\n",
990    "                                            as_cmap=True)\n",
991    "    )"
992   ]
993  },
994  {
995   "cell_type": "code",
996   "execution_count": null,
997   "metadata": {},
998   "outputs": [],
999   "source": [
1000    "def magnify():\n",
1001    "    return [dict(selector=\"th\",\n",
1002    "                 props=[(\"font-size\", \"4pt\")]),\n",
1003    "            dict(selector=\"td\",\n",
1004    "                 props=[('padding', \"0em 0em\")]),\n",
1005    "            dict(selector=\"th:hover\",\n",
1006    "                 props=[(\"font-size\", \"12pt\")]),\n",
1007    "            dict(selector=\"tr:hover td:hover\",\n",
1008    "                 props=[('max-width', '200px'),\n",
1009    "                        ('font-size', '12pt')])\n",
1010    "]"
1011   ]
1012  },
1013  {
1014   "cell_type": "code",
1015   "execution_count": null,
1016   "metadata": {},
1017   "outputs": [],
1018   "source": [
1019    "np.random.seed(25)\n",
1020    "cmap = cmap=sns.diverging_palette(5, 250, as_cmap=True)\n",
1021    "bigdf = pd.DataFrame(np.random.randn(20, 25)).cumsum()\n",
1022    "\n",
1023    "bigdf.style.background_gradient(cmap, axis=1)\\\n",
1024    "    .set_properties(**{'max-width': '80px', 'font-size': '1pt'})\\\n",
1025    "    .set_caption(\"Hover to magnify\")\\\n",
1026    "    .set_precision(2)\\\n",
1027    "    .set_table_styles(magnify())"
1028   ]
1029  },
1030  {
1031   "cell_type": "markdown",
1032   "metadata": {},
1033   "source": [
1034    "## Export to Excel\n",
1035    "\n",
1036    "*New in version 0.20.0*\n",
1037    "\n",
1038    "<span style=\"color: red\">*Experimental: This is a new feature and still under development. We'll be adding features and possibly making breaking changes in future releases. We'd love to hear your feedback.*</span>\n",
1039    "\n",
1040    "Some support is available for exporting styled `DataFrames` to Excel worksheets using the `OpenPyXL` or `XlsxWriter` engines. CSS2.2 properties handled include:\n",
1041    "\n",
1042    "- `background-color`\n",
1043    "- `border-style`, `border-width`, `border-color` and their {`top`, `right`, `bottom`, `left` variants}\n",
1044    "- `color`\n",
1045    "- `font-family`\n",
1046    "- `font-style`\n",
1047    "- `font-weight`\n",
1048    "- `text-align`\n",
1049    "- `text-decoration`\n",
1050    "- `vertical-align`\n",
1051    "- `white-space: nowrap`\n",
1052    "\n",
1053    "\n",
1054    "- Only CSS2 named colors and hex colors of the form `#rgb` or `#rrggbb` are currently supported.\n",
1055    "- The following pseudo CSS properties are also available to set excel specific style properties:\n",
1056    "    - `number-format`\n"
1057   ]
1058  },
1059  {
1060   "cell_type": "code",
1061   "execution_count": null,
1062   "metadata": {},
1063   "outputs": [],
1064   "source": [
1065    "df.style.\\\n",
1066    "    applymap(color_negative_red).\\\n",
1067    "    apply(highlight_max).\\\n",
1068    "    to_excel('styled.xlsx', engine='openpyxl')"
1069   ]
1070  },
1071  {
1072   "cell_type": "markdown",
1073   "metadata": {},
1074   "source": [
1075    "A screenshot of the output:\n",
1076    "\n",
1077    "![Excel spreadsheet with styled DataFrame](../_static/style-excel.png)\n"
1078   ]
1079  },
1080  {
1081   "cell_type": "markdown",
1082   "metadata": {},
1083   "source": [
1084    "## Extensibility\n",
1085    "\n",
1086    "The core of pandas is, and will remain, its \"high-performance, easy-to-use data structures\".\n",
1087    "With that in mind, we hope that `DataFrame.style` accomplishes two goals\n",
1088    "\n",
1089    "- Provide an API that is pleasing to use interactively and is \"good enough\" for many tasks\n",
1090    "- Provide the foundations for dedicated libraries to build on\n",
1091    "\n",
1092    "If you build a great library on top of this, let us know and we'll [link](https://pandas.pydata.org/pandas-docs/stable/ecosystem.html) to it.\n",
1093    "\n",
1094    "### Subclassing\n",
1095    "\n",
1096    "If the default template doesn't quite suit your needs, you can subclass Styler and extend or override the template.\n",
1097    "We'll show an example of extending the default template to insert a custom header before each table."
1098   ]
1099  },
1100  {
1101   "cell_type": "code",
1102   "execution_count": null,
1103   "metadata": {},
1104   "outputs": [],
1105   "source": [
1106    "from jinja2 import Environment, ChoiceLoader, FileSystemLoader\n",
1107    "from IPython.display import HTML\n",
1108    "from pandas.io.formats.style import Styler"
1109   ]
1110  },
1111  {
1112   "cell_type": "markdown",
1113   "metadata": {},
1114   "source": [
1115    "We'll use the following template:"
1116   ]
1117  },
1118  {
1119   "cell_type": "code",
1120   "execution_count": null,
1121   "metadata": {},
1122   "outputs": [],
1123   "source": [
1124    "with open(\"templates/myhtml.tpl\") as f:\n",
1125    "    print(f.read())"
1126   ]
1127  },
1128  {
1129   "cell_type": "markdown",
1130   "metadata": {},
1131   "source": [
1132    "Now that we've created a template, we need to set up a subclass of ``Styler`` that\n",
1133    "knows about it."
1134   ]
1135  },
1136  {
1137   "cell_type": "code",
1138   "execution_count": null,
1139   "metadata": {},
1140   "outputs": [],
1141   "source": [
1142    "class MyStyler(Styler):\n",
1143    "    env = Environment(\n",
1144    "        loader=ChoiceLoader([\n",
1145    "            FileSystemLoader(\"templates\"),  # contains ours\n",
1146    "            Styler.loader,  # the default\n",
1147    "        ])\n",
1148    "    )\n",
1149    "    template = env.get_template(\"myhtml.tpl\")"
1150   ]
1151  },
1152  {
1153   "cell_type": "markdown",
1154   "metadata": {},
1155   "source": [
1156    "Notice that we include the original loader in our environment's loader.\n",
1157    "That's because we extend the original template, so the Jinja environment needs\n",
1158    "to be able to find it.\n",
1159    "\n",
1160    "Now we can use that custom styler. It's `__init__` takes a DataFrame."
1161   ]
1162  },
1163  {
1164   "cell_type": "code",
1165   "execution_count": null,
1166   "metadata": {},
1167   "outputs": [],
1168   "source": [
1169    "MyStyler(df)"
1170   ]
1171  },
1172  {
1173   "cell_type": "markdown",
1174   "metadata": {},
1175   "source": [
1176    "Our custom template accepts a `table_title` keyword. We can provide the value in the `.render` method."
1177   ]
1178  },
1179  {
1180   "cell_type": "code",
1181   "execution_count": null,
1182   "metadata": {},
1183   "outputs": [],
1184   "source": [
1185    "HTML(MyStyler(df).render(table_title=\"Extending Example\"))"
1186   ]
1187  },
1188  {
1189   "cell_type": "markdown",
1190   "metadata": {},
1191   "source": [
1192    "For convenience, we provide the `Styler.from_custom_template` method that does the same as the custom subclass."
1193   ]
1194  },
1195  {
1196   "cell_type": "code",
1197   "execution_count": null,
1198   "metadata": {},
1199   "outputs": [],
1200   "source": [
1201    "EasyStyler = Styler.from_custom_template(\"templates\", \"myhtml.tpl\")\n",
1202    "EasyStyler(df)"
1203   ]
1204  },
1205  {
1206   "cell_type": "markdown",
1207   "metadata": {},
1208   "source": [
1209    "Here's the template structure:"
1210   ]
1211  },
1212  {
1213   "cell_type": "code",
1214   "execution_count": null,
1215   "metadata": {},
1216   "outputs": [],
1217   "source": [
1218    "with open(\"templates/template_structure.html\") as f:\n",
1219    "    structure = f.read()\n",
1220    "    \n",
1221    "HTML(structure)"
1222   ]
1223  },
1224  {
1225   "cell_type": "markdown",
1226   "metadata": {},
1227   "source": [
1228    "See the template in the [GitHub repo](https://github.com/pandas-dev/pandas) for more details."
1229   ]
1230  },
1231  {
1232   "cell_type": "code",
1233   "execution_count": null,
1234   "metadata": {
1235    "nbsphinx": "hidden"
1236   },
1237   "outputs": [],
1238   "source": [
1239    "# Hack to get the same style in the notebook as the\n",
1240    "# main site. This is hidden in the docs.\n",
1241    "from IPython.display import HTML\n",
1242    "with open(\"themes/nature_with_gtoc/static/nature.css_t\") as f:\n",
1243    "    css = f.read()\n",
1244    "    \n",
1245    "HTML('<style>{}</style>'.format(css))"
1246   ]
1247  }
1248 ],
1249 "metadata": {
1250  "kernelspec": {
1251   "display_name": "Python 3",
1252   "language": "python",
1253   "name": "python3"
1254  },
1255  "language_info": {
1256   "codemirror_mode": {
1257    "name": "ipython",
1258    "version": 3
1259   },
1260   "file_extension": ".py",
1261   "mimetype": "text/x-python",
1262   "name": "python",
1263   "nbconvert_exporter": "python",
1264   "pygments_lexer": "ipython3",
1265   "version": "3.7.0"
1266  }
1267 },
1268 "nbformat": 4,
1269 "nbformat_minor": 1
1270}
1271