Skip to content

Commit fa1d5f6

Browse files
committed
PERF: speed up rendering of styler (pandas-dev#19917)
see pandas-dev#19917
1 parent 3c959fc commit fa1d5f6

File tree

4 files changed

+59
-15
lines changed

4 files changed

+59
-15
lines changed

asv_bench/asv.conf.json

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"xlwt": [],
5454
"odfpy": [],
5555
"pytest": [],
56+
"jinja2": [],
5657
// If using Windows with python 2.7 and want to build using the
5758
// mingw toolchain (rather than MSVC), uncomment the following line.
5859
// "libpython": [],

asv_bench/benchmarks/io/style.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import numpy as np
2+
3+
from pandas import DataFrame
4+
5+
6+
class RenderApply:
7+
8+
params = [[12, 24, 36], [12, 120, 1200]]
9+
param_names = ["cols", "rows"]
10+
11+
def setup(self, cols, rows):
12+
self.df = DataFrame(
13+
np.random.randn(rows, cols),
14+
columns=[f"float_{i+1}" for i in range(cols)],
15+
index=[f"row_{i+1}" for i in range(rows)],
16+
)
17+
self._style_apply()
18+
19+
def time_apply(self, cols, rows):
20+
# skip known performance decrease: 36x1200
21+
# reason unknown, it took less time in v1.0.4 compared to 36x120
22+
# rows ----------------------------------
23+
# cols 12 120 1200
24+
# 36 12.4±0.3μs 12.0±0.1μs 11.8±0.09μs
25+
if rows == 1200 and cols == 36:
26+
return
27+
self._style_apply()
28+
29+
def time_render(self, cols, rows):
30+
self.st.render()
31+
32+
def peakmem_apply(self, cols, rows):
33+
self._style_apply()
34+
35+
def peakmem_render(self, cols, rows):
36+
self.st.render()
37+
38+
def _style_apply(self):
39+
def _apply_func(s):
40+
return [
41+
'background-color: lightcyan' if s.name == 'row_1' else ''
42+
for v in s
43+
]
44+
self.st = self.df.style.apply(
45+
_apply_func, axis=1
46+
)

pandas/io/formats/style.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -561,11 +561,15 @@ def _update_ctx(self, attrs: DataFrame) -> None:
561561
Whitespace shouldn't matter and the final trailing ';' shouldn't
562562
matter.
563563
"""
564-
for row_label, v in attrs.iterrows():
565-
for col_label, col in v.items():
566-
i = self.index.get_indexer([row_label])[0]
567-
j = self.columns.get_indexer([col_label])[0]
568-
for pair in col.rstrip(";").split(";"):
564+
rows = [(row_label, v) for row_label, v in attrs.iterrows()]
565+
row_idx = self.index.get_indexer([x[0] for x in rows])
566+
for ii, row in enumerate(rows):
567+
i = row_idx[ii]
568+
cols = [(col_label, col) for col_label, col in row[1].items() if col]
569+
col_idx = self.columns.get_indexer([x[0] for x in cols])
570+
for jj, itm in enumerate(cols):
571+
j = col_idx[jj]
572+
for pair in itm[1].rstrip(";").split(";"):
569573
self.ctx[(i, j)].append(pair)
570574

571575
def _copy(self, deepcopy: bool = False) -> "Styler":

pandas/tests/io/formats/test_style.py

+3-10
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,10 @@ def f(x):
405405

406406
result = self.df.style.where(f, style1)._compute().ctx
407407
expected = {
408-
(r, c): [style1 if f(self.df.loc[row, col]) else ""]
408+
(r, c): [style1]
409409
for r, row in enumerate(self.df.index)
410410
for c, col in enumerate(self.df.columns)
411+
if f(self.df.loc[row, col])
411412
}
412413
assert result == expected
413414

@@ -966,7 +967,6 @@ def test_bar_align_mid_nans(self):
966967
"transparent 25.0%, #d65f5f 25.0%, "
967968
"#d65f5f 50.0%, transparent 50.0%)",
968969
],
969-
(1, 0): [""],
970970
(0, 1): [
971971
"width: 10em",
972972
" height: 80%",
@@ -994,7 +994,6 @@ def test_bar_align_zero_nans(self):
994994
"transparent 50.0%, #d65f5f 50.0%, "
995995
"#d65f5f 75.0%, transparent 75.0%)",
996996
],
997-
(1, 0): [""],
998997
(0, 1): [
999998
"width: 10em",
1000999
" height: 80%",
@@ -1091,7 +1090,7 @@ def test_format_with_bad_na_rep(self):
10911090
def test_highlight_null(self, null_color="red"):
10921091
df = pd.DataFrame({"A": [0, np.nan]})
10931092
result = df.style.highlight_null()._compute().ctx
1094-
expected = {(0, 0): [""], (1, 0): ["background-color: red"]}
1093+
expected = {(1, 0): ["background-color: red"]}
10951094
assert result == expected
10961095

10971096
def test_highlight_null_subset(self):
@@ -1104,9 +1103,7 @@ def test_highlight_null_subset(self):
11041103
.ctx
11051104
)
11061105
expected = {
1107-
(0, 0): [""],
11081106
(1, 0): ["background-color: red"],
1109-
(0, 1): [""],
11101107
(1, 1): ["background-color: green"],
11111108
}
11121109
assert result == expected
@@ -1219,17 +1216,13 @@ def test_highlight_max(self):
12191216
expected = {
12201217
(1, 0): ["background-color: yellow"],
12211218
(1, 1): ["background-color: yellow"],
1222-
(0, 1): [""],
1223-
(0, 0): [""],
12241219
}
12251220
assert result == expected
12261221

12271222
result = getattr(df.style, attr)(axis=1)._compute().ctx
12281223
expected = {
12291224
(0, 1): ["background-color: yellow"],
12301225
(1, 1): ["background-color: yellow"],
1231-
(0, 0): [""],
1232-
(1, 0): [""],
12331226
}
12341227
assert result == expected
12351228

0 commit comments

Comments
 (0)