df.style
¶You can apply conditional formatting, the visual styling of a DataFrame
depending on the data within, by using the DataFrame.style
property.
This is a property that returns a Styler
object, which has
useful methods for formatting and displaying DataFrames.
The styling is accomplished using CSS.
You write functions that take DataFrame
s or Series
, and return like-indexed
DataFrames or Series with CSS "attribute: value"
pairs for the values.
You can build up your styles incrementally using method chains, before rending.
The main class you'll work with is pd.Styler
.
You can create this class by either
DataFrame.style
propertypd.Styler(dataframe)
This class holds a reference to your dataframe in the .data
attribute.
Pass your style functions into one of the following methods:
Styler.applymap
: elementwiseStyler.apply
: column/row-wiseStyler.tee
: tablewiseEach of those methods take a function (and some other keyword arguments) and apply your function to the DataFrame in a certain way. applymap
works through the DataFrame elementwise, apply
passes each column or row into your DataFrame one-at-a-time, and .tee
pass in the entire DataFrame at once.
Let's create a DataFrame to work with.
import pandas as pd
import numpy as np
np.random.seed(24)
df = pd.DataFrame({'A': np.linspace(1, 10, 10)})
df = pd.concat([df, pd.DataFrame(np.random.randn(10, 4), columns=list('BCDE'))],
axis=1)
df.iloc[0, 2] = np.nan
Here's a (boring) example:
df.style
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
Note: The DataFrame.style
is a propetry 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.
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.
df.style.render().split('\n')[:10]
['', ' <style type="text/css" >', ' ', ' ', ' #T_4b97aef0_81d8_11e5_8076_a45e60bd97fbrow0_col0 {', ' ', ' }', ' ', ' #T_4b97aef0_81d8_11e5_8076_a45e60bd97fbrow0_col1 {', ' ']
The row0_col0
is the identifier for that particular cell. We've also prepended each row/column identifier with a UUID unique each DataFrame so that the style from one doesn't collied with the styling from another within the same notebook / page (you can set the uuid
if you'd like to tie together the styling of two DataFrames).
Let's write a simple function that will color negative numbers red and positive numbers black.
def color_negative_red(val):
"""
Takes a scalar and returns a string with
the css property `'color: red'` for negative
strings, black otherwise.
"""
color = 'red' if val < 0 else 'black'
return 'color: %s' % color
df.style.applymap(color_negative_red)
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
First, notice the similarity with the standard df.applymap
, which operates on DataFrames elementwise. We want you to be able to resuse your existing knowledge of how to interact with DataFrames. df.style.applymap
also takes a function that operates elementwise.
Second, notice that our function returned a string containing the CSS attribute and value. This will be a common theme.
Now suppose you wanted to highlight the maximum value in each column.
We can't use .applymap
anymore since that operated elementwise.
Instead, we'll turn to .apply
which operates columnwise (or rowwise using the axis
keyword).
def highlight_max(s):
'''
highlight the maximum in a Series
'''
is_max = s == s.max()
return pd.Series(['background-color: yellow' if v else '' for v in is_max], name=s.name)
df.style.apply(highlight_max)
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
We encourage you to use method chains to build up a style piecewise, before finally rending at the end of the chain.
df.style.\
applymap(color_negative_red).\
apply(highlight_max)
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
We've seen .applymap
and .apply
. The final method for passing in style functions is .tee
.
Let's rewrite our highlight-max
to handle either Series (from .apply
) or DataFrames (from .tee
). We'll also allow the color to be adjustable, to demonstrate that .tee
, .apply
, and .applymap
all pass along keyword arguments.
def highlight_max(data, color='yellow'):
'''
highlight the maximum in a Series or DataFrame
'''
attr = 'background-color: {}'.format(color)
if data.ndim == 1: # Series from .apply
is_max = data == data.max()
return pd.Series([attr if v else '' for v in is_max])
else: # DataFrame from .tee
is_max = data == data.max().max()
return pd.DataFrame(np.where(is_max, attr, ''),
index=data.index, columns=data.columns)
df.style.tee(highlight_max, color='darkorange')
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
You can control the precision of floats using pandas' regular display.precision
option.
with pd.option_context('display.precision', 2):
html = (df.style
.applymap(color_negative_red)
.apply(highlight_max))
html
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.33 | nan | -0.32 | -0.99 |
1 | 2.0 | -1.07 | -1.44 | 0.56 | 0.3 |
2 | 3.0 | -1.63 | 0.22 | 0.68 | 1.89 |
3 | 4.0 | 0.96 | 0.1 | -0.48 | 0.85 |
4 | 5.0 | 1.45 | 1.06 | 0.17 | 0.52 |
5 | 6.0 | -1.34 | 0.56 | 1.39 | -0.06 |
6 | 7.0 | 0.12 | 1.21 | -0.0 | 1.63 |
7 | 8.0 | 0.35 | 1.04 | -0.39 | 0.52 |
8 | 9.0 | 1.69 | -1.33 | 1.43 | -2.09 |
9 | 10.0 | -0.13 | 0.63 | -0.59 | 0.29 |
The signatures for Styler.apply
, Styler.applymap
and Styler.tee
all include a subset
keyword.
This allows you to apply styles to specific rows or columns, without having to code that logic into your style
function.
df.style.applymap(color_negative_red, subset=pd.IndexSlice[2:5, ['B', 'D']])
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
We reuse DataFrame.loc
internally, so subset
can be anything that will slice the original DataFrame. Consider using pd.IndexSlice
to make writing the slices easier.
N.B. If your style function uses a subset
keyword argument, consider wrapping your function in a functools.partial
, partialing out that keyword.
my_func2 = functools.partial(my_func, subset=42)
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.
df.style.highlight_null(null_color='red')
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
import seaborn as sns
cm = sns.light_palette("green", as_cmap=True)
s = df.style.color_bg_range(cmap=cm)
s
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
# Use .set_properties when it doesn't actually depend on the values
df.style.set_properties(**{'background-color': 'black',
'color': 'lawngreen',
'border-color': 'white'})
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
You've seen a few methods for data-driven styling.
Styler
also provides a few other options for styling that don't depend on the data.
Each of these can be specified in two ways:
pd.Styler
.render
from pandas.core.style import Styler
s = Styler(df, caption='Colormaps, with a caption.')
s.color_bg_range(cmap=cm)
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
Alternativly, pass the caption in when rendering.
from IPython.display import HTML
HTML(
df.style.color_bg_range(cmap=cm).render(caption="Caption, from render.")
)
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
The next option you have to pass in are "table styles".
These are styles that apply to the table as a whole, and don't look at the data.
Certain sytlings, including pseudo-selectors like :hover
can only be used this way.
def hover(hover_color="#ffff99"):
return dict(selector="tr:hover", props=[("background-color", "%s" % hover_color)])
styles = [hover(), dict(selector="th", props=[("font-size", "150%"),
("text-align", "center")])]
html = df.style.render(style=styles)
HTML(html)
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
A few example styling functions. Perhaps we'll have a repo of these somewhere.
Fun stuff.
# https://developer.mozilla.org/en-US/docs/Web/CSS/animation#Cylon_Eye
# no animation yet :(
def cylon(s):
tpl = """
background-color: red;
background-image: -webkit-linear-gradient( left, rgba( 0,0,0,0.9 ) 25%, rgba( 0,0,0,0.1 ) 50%, rgba( 0,0,0,0.9 ) 75%);
background-image: -moz-linear-gradient( left, rgba( 0,0,0,0.9 ) 25%, rgba( 0,0,0,0.1 ) 50%, rgba( 0,0,0,0.9 ) 75%);
background-image: -o-linear-gradient( left, rgba( 0,0,0,0.9 ) 25%, rgba( 0,0,0,0.1 ) 50%, rgba( 0,0,0,0.9 ) 75%);
background-image: linear-gradient(to right, rgba( 0,0,0,0.9 ) 25%, rgba( 0,0,0,0.1 ) 50%, rgba( 0,0,0,0.9 ) 75%);
color: white;
height: 100%;
width: 20"""
return pd.Series([tpl for i in s], index=s.index, name=s.name)
df.style.apply(cylon)
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
Interacts pretty well with widgets.
from IPython.html import widgets
@widgets.interact
def f(h_neg=(0, 359, 1), h_pos=(0, 359), s=(0., 99.9), l=(0., 99.9)):
return df.style.color_bg_range(
cmap=sns.palettes.diverging_palette(h_neg=h_neg, h_pos=h_pos, s=s, l=l,
as_cmap=True)
)
A | B | C | D | E | |
---|---|---|---|---|---|
0 | 1.0 | 1.329212 | nan | -0.31628 | -0.99081 |
1 | 2.0 | -1.070816 | -1.438713 | 0.564417 | 0.295722 |
2 | 3.0 | -1.626404 | 0.219565 | 0.678805 | 1.889273 |
3 | 4.0 | 0.961538 | 0.104011 | -0.481165 | 0.850229 |
4 | 5.0 | 1.453425 | 1.057737 | 0.165562 | 0.515018 |
5 | 6.0 | -1.336936 | 0.562861 | 1.392855 | -0.063328 |
6 | 7.0 | 0.121668 | 1.207603 | -0.00204 | 1.627796 |
7 | 8.0 | 0.354493 | 1.037528 | -0.385684 | 0.519818 |
8 | 9.0 | 1.686583 | -1.325963 | 1.428984 | -2.089354 |
9 | 10.0 | -0.12982 | 0.631523 | -0.586538 | 0.29072 |
from IPython.display import HTML
np.random.seed(24)
df = pd.DataFrame({'A': np.linspace(1, 10, 10)})
df = pd.concat([df, pd.DataFrame(np.random.randn(10, 4), columns=list('BCDE'))],
axis=1)
df.iloc[0, 2] = np.nan
def panda(df):
spots = [
# left ear
(0, 0), (1, 0), (0, 1),
# right ear
(0, 10), (1, 10), (0, 9),
# left eye
(5, 2), (4, 3), (5, 3),
# right eye
(5, 7), (4, 7), (5, 8),
# nose
(10, 4), (10, 5), (10, 6), (11, 5),
(15, 5), (16, 5), (17, 5), (17, 4), (17, 6), (17, 3), (17, 7), (16, 2), (16, 8)
]
rows = []
for r in df.index:
cols = []
for c in df.columns:
if (r, c) in spots:
cols.append("background-color: black; color: white")
else:
cols.append("background-color: white; color: black")
rows.append(cols)
return pd.DataFrame(rows)
df = pd.DataFrame(np.random.randn(20, 11))
s = df.round(2).style
s.update_ctx(panda(df))
s
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.7 | 1.28 | -2.97 | 1.48 | -1.63 | -1.0 | 0.01 | 0.11 | -0.65 | -0.56 | -0.61 |
1 | -0.26 | -0.03 | 1.25 | -0.74 | -0.04 | -0.26 | -0.94 | 0.05 | 1.74 | -0.99 | 0.66 |
2 | -0.07 | -0.49 | -1.47 | -0.55 | -1.72 | -0.42 | 2.32 | -0.91 | -0.43 | 1.25 | 0.64 |
3 | -1.56 | -2.69 | -0.89 | 0.21 | 0.85 | 1.74 | 1.94 | 0.19 | -0.81 | 1.72 | -0.16 |
4 | -0.56 | 0.42 | -1.13 | 0.38 | -2.37 | -0.56 | -0.2 | -0.92 | -0.43 | -1.47 | 0.15 |
5 | -2.42 | -0.67 | -0.57 | 0.76 | -2.04 | -1.16 | -0.97 | -0.08 | -0.88 | -0.22 | 0.54 |
6 | -1.16 | 1.07 | -0.58 | 1.44 | -0.92 | -2.6 | -1.12 | 0.44 | 0.02 | 1.15 | 0.5 |
7 | 0.89 | -0.25 | 0.92 | -2.19 | 0.53 | -0.71 | 1.42 | -0.14 | 0.23 | -2.29 | 0.53 |
8 | 0.17 | -0.81 | -1.88 | -1.22 | 0.78 | -0.97 | 0.34 | -0.29 | -1.73 | -0.78 | 0.02 |
9 | 1.69 | -0.56 | -0.3 | 0.98 | -0.29 | 0.32 | 0.3 | 2.04 | 0.6 | -0.93 | -0.42 |
10 | -0.09 | 1.91 | -0.08 | 2.48 | -0.18 | 0.75 | -0.42 | -1.05 | -0.66 | 1.51 | 0.6 |
11 | -0.44 | 0.47 | -0.86 | -1.56 | 0.73 | 0.44 | 1.34 | 0.63 | 1.75 | -1.28 | -0.57 |
12 | 0.22 | 0.66 | -1.88 | 1.52 | 0.95 | -0.21 | -0.07 | -1.16 | 1.14 | -0.07 | -0.76 |
13 | 0.69 | -1.61 | 0.27 | 0.18 | 0.91 | 0.14 | -0.37 | -0.11 | 1.27 | -0.14 | 0.28 |
14 | -2.87 | 0.14 | 0.54 | -1.07 | 0.14 | -0.41 | -1.44 | -0.8 | 2.45 | -0.87 | -1.29 |
15 | 0.02 | -1.39 | 0.64 | 0.6 | -0.56 | -0.75 | 0.56 | 0.35 | -0.07 | -0.49 | -0.37 |
16 | -1.86 | 0.88 | 1.89 | -0.57 | -0.67 | -0.66 | 0.73 | 1.14 | -0.27 | -1.4 | 0.31 |
17 | -0.27 | -0.36 | 0.02 | 1.26 | 0.16 | -0.47 | -0.07 | -0.05 | -0.51 | -0.21 | 0.71 |
18 | 0.35 | 1.48 | -0.86 | -1.15 | -0.37 | -0.79 | 2.04 | -0.96 | -1.8 | 1.56 | -0.92 |
19 | 1.38 | 0.97 | -0.79 | -1.57 | 0.05 | -0.37 | -0.26 | -0.4 | -0.83 | -1.08 | -0.06 |
Unlike most objects that pandas exposes, Styler
should be designed with subclassing in mind. There's no particular reason why CSS should be used over other backend, other than it's convinient for users of Notebooks, and relatively straightforward compared to, say, $\LaTeX$. I've laid out a bit of the internal implementation here.
As users apply styles (via .apply
, .applymap
and .tee
), we modify an internal dict, self.ctx
. This maps (row_position, col_position)
to a list of (for CSS) 'attribute: value:
strings. Given this ctx
, the rendering roughly consists of two steps
translate
to a dictionary ready to be passed into the template Styler.t
Styler.translate
This is done in Styler.render
, but most of the heavy lifitng is done in translate
.
We've used Jinja templates to build up the HTML.
The template is stored as a class variable on Styler
(called Styler.t
for now) Subclasses can override that.
class CustomStyle(Styler):
t = Template("""...""")