Skip to content

Commit de919ff

Browse files
authored
ENH: Styler column and row styles (#35607)
1 parent 76216ba commit de919ff

File tree

4 files changed

+123
-13
lines changed

4 files changed

+123
-13
lines changed

doc/source/user_guide/style.ipynb

+30-4
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,8 @@
793793
"source": [
794794
"The next option you have are \"table styles\".\n",
795795
"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."
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."
797798
]
798799
},
799800
{
@@ -831,9 +832,32 @@
831832
"The value for `props` should be a list of tuples of `('attribute', 'value')`.\n",
832833
"\n",
833834
"`table_styles` are extremely flexible, but not as fun to type out by hand.\n",
834-
"We hope to collect some useful ones either in pandas, or preferable in a new package that [builds on top](#Extensibility) the tools here."
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`."
835841
]
836842
},
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+
},
837861
{
838862
"cell_type": "markdown",
839863
"metadata": {},
@@ -922,10 +946,12 @@
922946
"- DataFrame only `(use Series.to_frame().style)`\n",
923947
"- The index and columns must be unique\n",
924948
"- No large repr, and performance isn't great; this is intended for summary DataFrames\n",
925-
"- You can only style the *values*, not the index or columns\n",
949+
"- You can only style the *values*, not the index or columns (except with `table_styles` above)\n",
926950
"- You can only apply styles, you can't insert new HTML entities\n",
927951
"\n",
928-
"Some of these will be addressed in the future.\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"
929955
]
930956
},
931957
{

doc/source/whatsnew/v1.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ Other enhancements
232232
- :class:`Index` with object dtype supports division and multiplication (:issue:`34160`)
233233
- :meth:`DataFrame.explode` and :meth:`Series.explode` now support exploding of sets (:issue:`35614`)
234234
- :meth:`DataFrame.hist` now supports time series (datetime) data (:issue:`32590`)
235+
- :meth:`Styler.set_table_styles` now allows the direct styling of rows and columns and can be chained (:issue:`35607`)
235236
- ``Styler`` now allows direct CSS class name addition to individual data cells (:issue:`36159`)
236237
- :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`)
237238
- :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`)

pandas/io/formats/style.py

+70-9
Original file line numberDiff line numberDiff line change
@@ -990,34 +990,95 @@ def set_caption(self, caption: str) -> "Styler":
990990
self.caption = caption
991991
return self
992992

993-
def set_table_styles(self, table_styles) -> "Styler":
993+
def set_table_styles(self, table_styles, axis=0, overwrite=True) -> "Styler":
994994
"""
995995
Set the table styles on a Styler.
996996
997997
These are placed in a ``<style>`` tag before the generated HTML table.
998998
999+
This function can be used to style the entire table, columns, rows or
1000+
specific HTML selectors.
1001+
9991002
Parameters
10001003
----------
1001-
table_styles : list
1002-
Each individual table_style should be a dictionary with
1003-
``selector`` and ``props`` keys. ``selector`` should be a CSS
1004-
selector that the style will be applied to (automatically
1005-
prefixed by the table's UUID) and ``props`` should be a list of
1006-
tuples with ``(attribute, value)``.
1004+
table_styles : list or dict
1005+
If supplying a list, each individual table_style should be a
1006+
dictionary with ``selector`` and ``props`` keys. ``selector``
1007+
should be a CSS selector that the style will be applied to
1008+
(automatically prefixed by the table's UUID) and ``props``
1009+
should be a list of tuples with ``(attribute, value)``.
1010+
If supplying a dict, the dict keys should correspond to
1011+
column names or index values, depending upon the specified
1012+
`axis` argument. These will be mapped to row or col CSS
1013+
selectors. MultiIndex values as dict keys should be
1014+
in their respective tuple form. The dict values should be
1015+
a list as specified in the form with CSS selectors and
1016+
props that will be applied to the specified row or column.
1017+
1018+
.. versionchanged:: 1.2.0
1019+
1020+
axis : {0 or 'index', 1 or 'columns', None}, default 0
1021+
Apply to each column (``axis=0`` or ``'index'``), to each row
1022+
(``axis=1`` or ``'columns'``). Only used if `table_styles` is
1023+
dict.
1024+
1025+
.. versionadded:: 1.2.0
1026+
1027+
overwrite : boolean, default True
1028+
Styles are replaced if `True`, or extended if `False`. CSS
1029+
rules are preserved so most recent styles set will dominate
1030+
if selectors intersect.
1031+
1032+
.. versionadded:: 1.2.0
10071033
10081034
Returns
10091035
-------
10101036
self : Styler
10111037
10121038
Examples
10131039
--------
1014-
>>> df = pd.DataFrame(np.random.randn(10, 4))
1040+
>>> df = pd.DataFrame(np.random.randn(10, 4),
1041+
... columns=['A', 'B', 'C', 'D'])
10151042
>>> df.style.set_table_styles(
10161043
... [{'selector': 'tr:hover',
10171044
... 'props': [('background-color', 'yellow')]}]
10181045
... )
1046+
1047+
Adding column styling by name
1048+
1049+
>>> df.style.set_table_styles({
1050+
... 'A': [{'selector': '',
1051+
... 'props': [('color', 'red')]}],
1052+
... 'B': [{'selector': 'td',
1053+
... 'props': [('color', 'blue')]}]
1054+
... }, overwrite=False)
1055+
1056+
Adding row styling
1057+
1058+
>>> df.style.set_table_styles({
1059+
... 0: [{'selector': 'td:hover',
1060+
... 'props': [('font-size', '25px')]}]
1061+
... }, axis=1, overwrite=False)
10191062
"""
1020-
self.table_styles = table_styles
1063+
if is_dict_like(table_styles):
1064+
if axis in [0, "index"]:
1065+
obj, idf = self.data.columns, ".col"
1066+
else:
1067+
obj, idf = self.data.index, ".row"
1068+
1069+
table_styles = [
1070+
{
1071+
"selector": s["selector"] + idf + str(obj.get_loc(key)),
1072+
"props": s["props"],
1073+
}
1074+
for key, styles in table_styles.items()
1075+
for s in styles
1076+
]
1077+
1078+
if not overwrite and self.table_styles is not None:
1079+
self.table_styles.extend(table_styles)
1080+
else:
1081+
self.table_styles = table_styles
10211082
return self
10221083

10231084
def set_na_rep(self, na_rep: str) -> "Styler":

pandas/tests/io/formats/test_style.py

+22
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,28 @@ def test_set_data_classes(self, classes):
17121712
assert '<td class="data row1 col0" >2</td>' in s
17131713
assert '<td class="data row1 col1" >3</td>' in s
17141714

1715+
def test_chaining_table_styles(self):
1716+
# GH 35607
1717+
df = DataFrame(data=[[0, 1], [1, 2]], columns=["A", "B"])
1718+
styler = df.style.set_table_styles(
1719+
[{"selector": "", "props": [("background-color", "yellow")]}]
1720+
).set_table_styles(
1721+
[{"selector": ".col0", "props": [("background-color", "blue")]}],
1722+
overwrite=False,
1723+
)
1724+
assert len(styler.table_styles) == 2
1725+
1726+
def test_column_and_row_styling(self):
1727+
# GH 35607
1728+
df = DataFrame(data=[[0, 1], [1, 2]], columns=["A", "B"])
1729+
s = Styler(df, uuid_len=0)
1730+
s = s.set_table_styles({"A": [{"selector": "", "props": [("color", "blue")]}]})
1731+
assert "#T__ .col0 {\n color: blue;\n }" in s.render()
1732+
s = s.set_table_styles(
1733+
{0: [{"selector": "", "props": [("color", "blue")]}]}, axis=1
1734+
)
1735+
assert "#T__ .row0 {\n color: blue;\n }" in s.render()
1736+
17151737
def test_colspan_w3(self):
17161738
# GH 36223
17171739
df = DataFrame(data=[[1, 2]], columns=[["l0", "l0"], ["l1a", "l1b"]])

0 commit comments

Comments
 (0)