Skip to content

ENH: Styler column and row styles #35607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
af99333
ENH: add named based column css styling to pandas Styler
attack68 Aug 7, 2020
c8097ed
ENH: add named based column css styling to pandas Styler (black fixup)
attack68 Aug 7, 2020
07591da
ENH: add named based column css styling to pandas Styler (black fixup)
attack68 Aug 7, 2020
d143824
ENH: add named based column css styling to pandas Styler (black fixup)
attack68 Aug 7, 2020
a4ebd1a
ENH: restructure code for no new functions, only keyword additions
attack68 Aug 7, 2020
cae9159
ENH: black fix
attack68 Aug 7, 2020
08fc864
ENH: changes requested in issue
attack68 Aug 8, 2020
eb4abea
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Sep 8, 2020
f5b4b79
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Sep 9, 2020
6cea000
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Sep 13, 2020
fc281af
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Sep 17, 2020
f61b6ba
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Sep 19, 2020
6abbe99
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Sep 20, 2020
d7f7e72
fix test after recent merge master
attack68 Sep 20, 2020
18d5d68
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Sep 21, 2020
29e02df
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
TomAugspurger Sep 25, 2020
31499ba
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Sep 28, 2020
6dbd2ca
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Sep 29, 2020
c0ea225
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Nov 5, 2020
0792752
style fix
attack68 Nov 5, 2020
6f6c758
Merge remote-tracking branch 'upstream/master' into styler_column_sty…
attack68 Nov 22, 2020
93d3f8d
TYPE: add type for set_table_styles
attack68 Nov 22, 2020
d5a6fbb
TYPE: revert chgs
attack68 Nov 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions doc/source/user_guide/style.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,8 @@
"source": [
"The next option you have are \"table styles\".\n",
"These are styles that apply to the table as a whole, but don't look at the data.\n",
"Certain stylings, including pseudo-selectors like `:hover` can only be used this way."
"Certain stylings, including pseudo-selectors like `:hover` can only be used this way.\n",
"These can also be used to set specific row or column based class selectors, as will be shown."
]
},
{
Expand Down Expand Up @@ -831,9 +832,32 @@
"The value for `props` should be a list of tuples of `('attribute', 'value')`.\n",
"\n",
"`table_styles` are extremely flexible, but not as fun to type out by hand.\n",
"We hope to collect some useful ones either in pandas, or preferable in a new package that [builds on top](#Extensibility) the tools here."
"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",
"\n",
"`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",
"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",
"\n",
"Note that `Styler.set_table_styles` will overwrite existing styles but can be chained by setting the `overwrite` argument to `False`."
]
},
{
"cell_type": "code",
"execution_count": null,
"outputs": [],
"source": [
"html = html.set_table_styles({\n",
" 'B': [dict(selector='', props=[('color', 'green')])],\n",
" 'C': [dict(selector='td', props=[('color', 'red')])], \n",
" }, overwrite=False)\n",
"html"
],
"metadata": {
"collapsed": false,
"pycharm": {
"name": "#%%\n"
}
}
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -922,10 +946,12 @@
"- DataFrame only `(use Series.to_frame().style)`\n",
"- The index and columns must be unique\n",
"- No large repr, and performance isn't great; this is intended for summary DataFrames\n",
"- You can only style the *values*, not the index or columns\n",
"- You can only style the *values*, not the index or columns (except with `table_styles` above)\n",
"- You can only apply styles, you can't insert new HTML entities\n",
"\n",
"Some of these will be addressed in the future.\n"
"Some of these will be addressed in the future.\n",
"Performance can suffer when adding styles to each cell in a large DataFrame.\n",
"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"
]
},
{
Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.2.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ Other enhancements
- :class:`Index` with object dtype supports division and multiplication (:issue:`34160`)
- :meth:`DataFrame.explode` and :meth:`Series.explode` now support exploding of sets (:issue:`35614`)
- :meth:`DataFrame.hist` now supports time series (datetime) data (:issue:`32590`)
- :meth:`Styler.set_table_styles` now allows the direct styling of rows and columns and can be chained (:issue:`35607`)
- ``Styler`` now allows direct CSS class name addition to individual data cells (:issue:`36159`)
- :meth:`Rolling.mean()` and :meth:`Rolling.sum()` use Kahan summation to calculate the mean to avoid numerical problems (:issue:`10319`, :issue:`11645`, :issue:`13254`, :issue:`32761`, :issue:`36031`)
- :meth:`DatetimeIndex.searchsorted`, :meth:`TimedeltaIndex.searchsorted`, :meth:`PeriodIndex.searchsorted`, and :meth:`Series.searchsorted` with datetimelike dtypes will now try to cast string arguments (listlike and scalar) to the matching datetimelike type (:issue:`36346`)
Expand Down
79 changes: 70 additions & 9 deletions pandas/io/formats/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,34 +991,95 @@ def set_caption(self, caption: str) -> "Styler":
self.caption = caption
return self

def set_table_styles(self, table_styles) -> "Styler":
def set_table_styles(self, table_styles, axis=0, overwrite=True) -> "Styler":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you type table_styles

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried adding the following to func def, but it got errors, I don't know my way around mypy and this seems slightly more complicated than normal with the multiple acceptable input types and programming logic:

table_styles: Union[
        List[Dict[str, Union[str, List[Tuple[str, str]]]]],
        Dict[
            Union[str, Tuple[Any, ...]],
            List[Dict[str, Union[str, List[Tuple[str, str]]]]],
        ],
    ] = []

So I reverted the attempt.

However, all green, pinging @jreback

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok if you can follow and make this much more intuitive (e.g. define aliases here)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it is not-trivial to actually construct this union

"""
Set the table styles on a Styler.

These are placed in a ``<style>`` tag before the generated HTML table.

This function can be used to style the entire table, columns, rows or
specific HTML selectors.

Parameters
----------
table_styles : list
Each individual table_style should be a dictionary with
``selector`` and ``props`` keys. ``selector`` should be a CSS
selector that the style will be applied to (automatically
prefixed by the table's UUID) and ``props`` should be a list of
tuples with ``(attribute, value)``.
table_styles : list or dict
If supplying a list, each individual table_style should be a
dictionary with ``selector`` and ``props`` keys. ``selector``
should be a CSS selector that the style will be applied to
(automatically prefixed by the table's UUID) and ``props``
should be a list of tuples with ``(attribute, value)``.
If supplying a dict, the dict keys should correspond to
column names or index values, depending upon the specified
`axis` argument. These will be mapped to row or col CSS
selectors. MultiIndex values as dict keys should be
in their respective tuple form. The dict values should be
a list as specified in the form with CSS selectors and
props that will be applied to the specified row or column.

.. versionchanged:: 1.2.0

axis : {0 or 'index', 1 or 'columns', None}, default 0
Apply to each column (``axis=0`` or ``'index'``), to each row
(``axis=1`` or ``'columns'``). Only used if `table_styles` is
dict.

.. versionadded:: 1.2.0

overwrite : boolean, default True
Styles are replaced if `True`, or extended if `False`. CSS
rules are preserved so most recent styles set will dominate
if selectors intersect.

.. versionadded:: 1.2.0

Returns
-------
self : Styler

Examples
--------
>>> df = pd.DataFrame(np.random.randn(10, 4))
>>> df = pd.DataFrame(np.random.randn(10, 4),
... columns=['A', 'B', 'C', 'D'])
>>> df.style.set_table_styles(
... [{'selector': 'tr:hover',
... 'props': [('background-color', 'yellow')]}]
... )

Adding column styling by name

>>> df.style.set_table_styles({
... 'A': [{'selector': '',
... 'props': [('color', 'red')]}],
... 'B': [{'selector': 'td',
... 'props': [('color', 'blue')]}]
... }, overwrite=False)

Adding row styling

>>> df.style.set_table_styles({
... 0: [{'selector': 'td:hover',
... 'props': [('font-size', '25px')]}]
... }, axis=1, overwrite=False)
"""
self.table_styles = table_styles
if is_dict_like(table_styles):
if axis in [0, "index"]:
obj, idf = self.data.columns, ".col"
else:
obj, idf = self.data.index, ".row"

table_styles = [
{
"selector": s["selector"] + idf + str(obj.get_loc(key)),
"props": s["props"],
}
for key, styles in table_styles.items()
for s in styles
]

if not overwrite and self.table_styles is not None:
self.table_styles.extend(table_styles)
else:
self.table_styles = table_styles
return self

def set_na_rep(self, na_rep: str) -> "Styler":
Expand Down
22 changes: 22 additions & 0 deletions pandas/tests/io/formats/test_style.py
Original file line number Diff line number Diff line change
Expand Up @@ -1712,6 +1712,28 @@ def test_set_data_classes(self, classes):
assert '<td class="data row1 col0" >2</td>' in s
assert '<td class="data row1 col1" >3</td>' in s

def test_chaining_table_styles(self):
# GH 35607
df = DataFrame(data=[[0, 1], [1, 2]], columns=["A", "B"])
styler = df.style.set_table_styles(
[{"selector": "", "props": [("background-color", "yellow")]}]
).set_table_styles(
[{"selector": ".col0", "props": [("background-color", "blue")]}],
overwrite=False,
)
assert len(styler.table_styles) == 2

def test_column_and_row_styling(self):
# GH 35607
df = DataFrame(data=[[0, 1], [1, 2]], columns=["A", "B"])
s = Styler(df, uuid_len=0)
s = s.set_table_styles({"A": [{"selector": "", "props": [("color", "blue")]}]})
assert "#T__ .col0 {\n color: blue;\n }" in s.render()
s = s.set_table_styles(
{0: [{"selector": "", "props": [("color", "blue")]}]}, axis=1
)
assert "#T__ .row0 {\n color: blue;\n }" in s.render()

def test_colspan_w3(self):
# GH 36223
df = DataFrame(data=[[1, 2]], columns=[["l0", "l0"], ["l1a", "l1b"]])
Expand Down