test_style.py 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315
  1. import copy
  2. import re
  3. import textwrap
  4. import numpy as np
  5. import pytest
  6. import pandas.util._test_decorators as td
  7. import pandas as pd
  8. from pandas import DataFrame
  9. import pandas.util.testing as tm
  10. jinja2 = pytest.importorskip('jinja2')
  11. from pandas.io.formats.style import Styler, _get_level_lengths # noqa # isort:skip
  12. class TestStyler(object):
  13. def setup_method(self, method):
  14. np.random.seed(24)
  15. self.s = DataFrame({'A': np.random.permutation(range(6))})
  16. self.df = DataFrame({'A': [0, 1], 'B': np.random.randn(2)})
  17. self.f = lambda x: x
  18. self.g = lambda x: x
  19. def h(x, foo='bar'):
  20. return pd.Series(
  21. 'color: {foo}'.format(foo=foo), index=x.index, name=x.name)
  22. self.h = h
  23. self.styler = Styler(self.df)
  24. self.attrs = pd.DataFrame({'A': ['color: red', 'color: blue']})
  25. self.dataframes = [
  26. self.df,
  27. pd.DataFrame({'f': [1., 2.], 'o': ['a', 'b'],
  28. 'c': pd.Categorical(['a', 'b'])})
  29. ]
  30. def test_init_non_pandas(self):
  31. with pytest.raises(TypeError):
  32. Styler([1, 2, 3])
  33. def test_init_series(self):
  34. result = Styler(pd.Series([1, 2]))
  35. assert result.data.ndim == 2
  36. def test_repr_html_ok(self):
  37. self.styler._repr_html_()
  38. def test_repr_html_mathjax(self):
  39. # gh-19824
  40. assert 'tex2jax_ignore' not in self.styler._repr_html_()
  41. with pd.option_context('display.html.use_mathjax', False):
  42. assert 'tex2jax_ignore' in self.styler._repr_html_()
  43. def test_update_ctx(self):
  44. self.styler._update_ctx(self.attrs)
  45. expected = {(0, 0): ['color: red'],
  46. (1, 0): ['color: blue']}
  47. assert self.styler.ctx == expected
  48. def test_update_ctx_flatten_multi(self):
  49. attrs = DataFrame({"A": ['color: red; foo: bar',
  50. 'color: blue; foo: baz']})
  51. self.styler._update_ctx(attrs)
  52. expected = {(0, 0): ['color: red', ' foo: bar'],
  53. (1, 0): ['color: blue', ' foo: baz']}
  54. assert self.styler.ctx == expected
  55. def test_update_ctx_flatten_multi_traliing_semi(self):
  56. attrs = DataFrame({"A": ['color: red; foo: bar;',
  57. 'color: blue; foo: baz;']})
  58. self.styler._update_ctx(attrs)
  59. expected = {(0, 0): ['color: red', ' foo: bar'],
  60. (1, 0): ['color: blue', ' foo: baz']}
  61. assert self.styler.ctx == expected
  62. def test_copy(self):
  63. s2 = copy.copy(self.styler)
  64. assert self.styler is not s2
  65. assert self.styler.ctx is s2.ctx # shallow
  66. assert self.styler._todo is s2._todo
  67. self.styler._update_ctx(self.attrs)
  68. self.styler.highlight_max()
  69. assert self.styler.ctx == s2.ctx
  70. assert self.styler._todo == s2._todo
  71. def test_deepcopy(self):
  72. s2 = copy.deepcopy(self.styler)
  73. assert self.styler is not s2
  74. assert self.styler.ctx is not s2.ctx
  75. assert self.styler._todo is not s2._todo
  76. self.styler._update_ctx(self.attrs)
  77. self.styler.highlight_max()
  78. assert self.styler.ctx != s2.ctx
  79. assert s2._todo == []
  80. assert self.styler._todo != s2._todo
  81. def test_clear(self):
  82. s = self.df.style.highlight_max()._compute()
  83. assert len(s.ctx) > 0
  84. assert len(s._todo) > 0
  85. s.clear()
  86. assert len(s.ctx) == 0
  87. assert len(s._todo) == 0
  88. def test_render(self):
  89. df = pd.DataFrame({"A": [0, 1]})
  90. style = lambda x: pd.Series(["color: red", "color: blue"], name=x.name)
  91. s = Styler(df, uuid='AB').apply(style)
  92. s.render()
  93. # it worked?
  94. def test_render_empty_dfs(self):
  95. empty_df = DataFrame()
  96. es = Styler(empty_df)
  97. es.render()
  98. # An index but no columns
  99. DataFrame(columns=['a']).style.render()
  100. # A column but no index
  101. DataFrame(index=['a']).style.render()
  102. # No IndexError raised?
  103. def test_render_double(self):
  104. df = pd.DataFrame({"A": [0, 1]})
  105. style = lambda x: pd.Series(["color: red; border: 1px",
  106. "color: blue; border: 2px"], name=x.name)
  107. s = Styler(df, uuid='AB').apply(style)
  108. s.render()
  109. # it worked?
  110. def test_set_properties(self):
  111. df = pd.DataFrame({"A": [0, 1]})
  112. result = df.style.set_properties(color='white',
  113. size='10px')._compute().ctx
  114. # order is deterministic
  115. v = ["color: white", "size: 10px"]
  116. expected = {(0, 0): v, (1, 0): v}
  117. assert result.keys() == expected.keys()
  118. for v1, v2 in zip(result.values(), expected.values()):
  119. assert sorted(v1) == sorted(v2)
  120. def test_set_properties_subset(self):
  121. df = pd.DataFrame({'A': [0, 1]})
  122. result = df.style.set_properties(subset=pd.IndexSlice[0, 'A'],
  123. color='white')._compute().ctx
  124. expected = {(0, 0): ['color: white']}
  125. assert result == expected
  126. def test_empty_index_name_doesnt_display(self):
  127. # https://github.com/pandas-dev/pandas/pull/12090#issuecomment-180695902
  128. df = pd.DataFrame({'A': [1, 2], 'B': [3, 4], 'C': [5, 6]})
  129. result = df.style._translate()
  130. expected = [[{'class': 'blank level0', 'type': 'th', 'value': '',
  131. 'is_visible': True, 'display_value': ''},
  132. {'class': 'col_heading level0 col0',
  133. 'display_value': 'A',
  134. 'type': 'th',
  135. 'value': 'A',
  136. 'is_visible': True,
  137. },
  138. {'class': 'col_heading level0 col1',
  139. 'display_value': 'B',
  140. 'type': 'th',
  141. 'value': 'B',
  142. 'is_visible': True,
  143. },
  144. {'class': 'col_heading level0 col2',
  145. 'display_value': 'C',
  146. 'type': 'th',
  147. 'value': 'C',
  148. 'is_visible': True,
  149. }]]
  150. assert result['head'] == expected
  151. def test_index_name(self):
  152. # https://github.com/pandas-dev/pandas/issues/11655
  153. df = pd.DataFrame({'A': [1, 2], 'B': [3, 4], 'C': [5, 6]})
  154. result = df.set_index('A').style._translate()
  155. expected = [[{'class': 'blank level0', 'type': 'th', 'value': '',
  156. 'display_value': '', 'is_visible': True},
  157. {'class': 'col_heading level0 col0', 'type': 'th',
  158. 'value': 'B', 'display_value': 'B', 'is_visible': True},
  159. {'class': 'col_heading level0 col1', 'type': 'th',
  160. 'value': 'C', 'display_value': 'C', 'is_visible': True}],
  161. [{'class': 'index_name level0', 'type': 'th',
  162. 'value': 'A'},
  163. {'class': 'blank', 'type': 'th', 'value': ''},
  164. {'class': 'blank', 'type': 'th', 'value': ''}]]
  165. assert result['head'] == expected
  166. def test_multiindex_name(self):
  167. # https://github.com/pandas-dev/pandas/issues/11655
  168. df = pd.DataFrame({'A': [1, 2], 'B': [3, 4], 'C': [5, 6]})
  169. result = df.set_index(['A', 'B']).style._translate()
  170. expected = [[
  171. {'class': 'blank', 'type': 'th', 'value': '',
  172. 'display_value': '', 'is_visible': True},
  173. {'class': 'blank level0', 'type': 'th', 'value': '',
  174. 'display_value': '', 'is_visible': True},
  175. {'class': 'col_heading level0 col0', 'type': 'th',
  176. 'value': 'C', 'display_value': 'C', 'is_visible': True}],
  177. [{'class': 'index_name level0', 'type': 'th',
  178. 'value': 'A'},
  179. {'class': 'index_name level1', 'type': 'th',
  180. 'value': 'B'},
  181. {'class': 'blank', 'type': 'th', 'value': ''}]]
  182. assert result['head'] == expected
  183. def test_numeric_columns(self):
  184. # https://github.com/pandas-dev/pandas/issues/12125
  185. # smoke test for _translate
  186. df = pd.DataFrame({0: [1, 2, 3]})
  187. df.style._translate()
  188. def test_apply_axis(self):
  189. df = pd.DataFrame({'A': [0, 0], 'B': [1, 1]})
  190. f = lambda x: ['val: {max}'.format(max=x.max()) for v in x]
  191. result = df.style.apply(f, axis=1)
  192. assert len(result._todo) == 1
  193. assert len(result.ctx) == 0
  194. result._compute()
  195. expected = {(0, 0): ['val: 1'], (0, 1): ['val: 1'],
  196. (1, 0): ['val: 1'], (1, 1): ['val: 1']}
  197. assert result.ctx == expected
  198. result = df.style.apply(f, axis=0)
  199. expected = {(0, 0): ['val: 0'], (0, 1): ['val: 1'],
  200. (1, 0): ['val: 0'], (1, 1): ['val: 1']}
  201. result._compute()
  202. assert result.ctx == expected
  203. result = df.style.apply(f) # default
  204. result._compute()
  205. assert result.ctx == expected
  206. def test_apply_subset(self):
  207. axes = [0, 1]
  208. slices = [pd.IndexSlice[:], pd.IndexSlice[:, ['A']],
  209. pd.IndexSlice[[1], :], pd.IndexSlice[[1], ['A']],
  210. pd.IndexSlice[:2, ['A', 'B']]]
  211. for ax in axes:
  212. for slice_ in slices:
  213. result = self.df.style.apply(self.h, axis=ax, subset=slice_,
  214. foo='baz')._compute().ctx
  215. expected = {(r, c): ['color: baz']
  216. for r, row in enumerate(self.df.index)
  217. for c, col in enumerate(self.df.columns)
  218. if row in self.df.loc[slice_].index and
  219. col in self.df.loc[slice_].columns}
  220. assert result == expected
  221. def test_applymap_subset(self):
  222. def f(x):
  223. return 'foo: bar'
  224. slices = [pd.IndexSlice[:], pd.IndexSlice[:, ['A']],
  225. pd.IndexSlice[[1], :], pd.IndexSlice[[1], ['A']],
  226. pd.IndexSlice[:2, ['A', 'B']]]
  227. for slice_ in slices:
  228. result = self.df.style.applymap(f, subset=slice_)._compute().ctx
  229. expected = {(r, c): ['foo: bar']
  230. for r, row in enumerate(self.df.index)
  231. for c, col in enumerate(self.df.columns)
  232. if row in self.df.loc[slice_].index and
  233. col in self.df.loc[slice_].columns}
  234. assert result == expected
  235. def test_applymap_subset_multiindex(self):
  236. # GH 19861
  237. # Smoke test for applymap
  238. def color_negative_red(val):
  239. """
  240. Takes a scalar and returns a string with
  241. the css property `'color: red'` for negative
  242. strings, black otherwise.
  243. """
  244. color = 'red' if val < 0 else 'black'
  245. return 'color: %s' % color
  246. dic = {
  247. ('a', 'd'): [-1.12, 2.11],
  248. ('a', 'c'): [2.78, -2.88],
  249. ('b', 'c'): [-3.99, 3.77],
  250. ('b', 'd'): [4.21, -1.22],
  251. }
  252. idx = pd.IndexSlice
  253. df = pd.DataFrame(dic, index=[0, 1])
  254. (df.style
  255. .applymap(color_negative_red, subset=idx[:, idx['b', 'd']])
  256. .render())
  257. def test_where_with_one_style(self):
  258. # GH 17474
  259. def f(x):
  260. return x > 0.5
  261. style1 = 'foo: bar'
  262. result = self.df.style.where(f, style1)._compute().ctx
  263. expected = {(r, c): [style1 if f(self.df.loc[row, col]) else '']
  264. for r, row in enumerate(self.df.index)
  265. for c, col in enumerate(self.df.columns)}
  266. assert result == expected
  267. def test_where_subset(self):
  268. # GH 17474
  269. def f(x):
  270. return x > 0.5
  271. style1 = 'foo: bar'
  272. style2 = 'baz: foo'
  273. slices = [pd.IndexSlice[:], pd.IndexSlice[:, ['A']],
  274. pd.IndexSlice[[1], :], pd.IndexSlice[[1], ['A']],
  275. pd.IndexSlice[:2, ['A', 'B']]]
  276. for slice_ in slices:
  277. result = self.df.style.where(f, style1, style2,
  278. subset=slice_)._compute().ctx
  279. expected = {(r, c):
  280. [style1 if f(self.df.loc[row, col]) else style2]
  281. for r, row in enumerate(self.df.index)
  282. for c, col in enumerate(self.df.columns)
  283. if row in self.df.loc[slice_].index and
  284. col in self.df.loc[slice_].columns}
  285. assert result == expected
  286. def test_where_subset_compare_with_applymap(self):
  287. # GH 17474
  288. def f(x):
  289. return x > 0.5
  290. style1 = 'foo: bar'
  291. style2 = 'baz: foo'
  292. def g(x):
  293. return style1 if f(x) else style2
  294. slices = [pd.IndexSlice[:], pd.IndexSlice[:, ['A']],
  295. pd.IndexSlice[[1], :], pd.IndexSlice[[1], ['A']],
  296. pd.IndexSlice[:2, ['A', 'B']]]
  297. for slice_ in slices:
  298. result = self.df.style.where(f, style1, style2,
  299. subset=slice_)._compute().ctx
  300. expected = self.df.style.applymap(g, subset=slice_)._compute().ctx
  301. assert result == expected
  302. def test_empty(self):
  303. df = pd.DataFrame({'A': [1, 0]})
  304. s = df.style
  305. s.ctx = {(0, 0): ['color: red'],
  306. (1, 0): ['']}
  307. result = s._translate()['cellstyle']
  308. expected = [{'props': [['color', ' red']], 'selector': 'row0_col0'},
  309. {'props': [['', '']], 'selector': 'row1_col0'}]
  310. assert result == expected
  311. def test_bar_align_left(self):
  312. df = pd.DataFrame({'A': [0, 1, 2]})
  313. result = df.style.bar()._compute().ctx
  314. expected = {
  315. (0, 0): ['width: 10em', ' height: 80%'],
  316. (1, 0): ['width: 10em', ' height: 80%',
  317. 'background: linear-gradient('
  318. '90deg,#d65f5f 50.0%, transparent 50.0%)'],
  319. (2, 0): ['width: 10em', ' height: 80%',
  320. 'background: linear-gradient('
  321. '90deg,#d65f5f 100.0%, transparent 100.0%)']
  322. }
  323. assert result == expected
  324. result = df.style.bar(color='red', width=50)._compute().ctx
  325. expected = {
  326. (0, 0): ['width: 10em', ' height: 80%'],
  327. (1, 0): ['width: 10em', ' height: 80%',
  328. 'background: linear-gradient('
  329. '90deg,red 25.0%, transparent 25.0%)'],
  330. (2, 0): ['width: 10em', ' height: 80%',
  331. 'background: linear-gradient('
  332. '90deg,red 50.0%, transparent 50.0%)']
  333. }
  334. assert result == expected
  335. df['C'] = ['a'] * len(df)
  336. result = df.style.bar(color='red', width=50)._compute().ctx
  337. assert result == expected
  338. df['C'] = df['C'].astype('category')
  339. result = df.style.bar(color='red', width=50)._compute().ctx
  340. assert result == expected
  341. def test_bar_align_left_0points(self):
  342. df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
  343. result = df.style.bar()._compute().ctx
  344. expected = {(0, 0): ['width: 10em', ' height: 80%'],
  345. (0, 1): ['width: 10em', ' height: 80%'],
  346. (0, 2): ['width: 10em', ' height: 80%'],
  347. (1, 0): ['width: 10em', ' height: 80%',
  348. 'background: linear-gradient(90deg,#d65f5f 50.0%,'
  349. ' transparent 50.0%)'],
  350. (1, 1): ['width: 10em', ' height: 80%',
  351. 'background: linear-gradient(90deg,#d65f5f 50.0%,'
  352. ' transparent 50.0%)'],
  353. (1, 2): ['width: 10em', ' height: 80%',
  354. 'background: linear-gradient(90deg,#d65f5f 50.0%,'
  355. ' transparent 50.0%)'],
  356. (2, 0): ['width: 10em', ' height: 80%',
  357. 'background: linear-gradient(90deg,#d65f5f 100.0%'
  358. ', transparent 100.0%)'],
  359. (2, 1): ['width: 10em', ' height: 80%',
  360. 'background: linear-gradient(90deg,#d65f5f 100.0%'
  361. ', transparent 100.0%)'],
  362. (2, 2): ['width: 10em', ' height: 80%',
  363. 'background: linear-gradient(90deg,#d65f5f 100.0%'
  364. ', transparent 100.0%)']}
  365. assert result == expected
  366. result = df.style.bar(axis=1)._compute().ctx
  367. expected = {(0, 0): ['width: 10em', ' height: 80%'],
  368. (0, 1): ['width: 10em', ' height: 80%',
  369. 'background: linear-gradient(90deg,#d65f5f 50.0%,'
  370. ' transparent 50.0%)'],
  371. (0, 2): ['width: 10em', ' height: 80%',
  372. 'background: linear-gradient(90deg,#d65f5f 100.0%'
  373. ', transparent 100.0%)'],
  374. (1, 0): ['width: 10em', ' height: 80%'],
  375. (1, 1): ['width: 10em', ' height: 80%',
  376. 'background: linear-gradient(90deg,#d65f5f 50.0%'
  377. ', transparent 50.0%)'],
  378. (1, 2): ['width: 10em', ' height: 80%',
  379. 'background: linear-gradient(90deg,#d65f5f 100.0%'
  380. ', transparent 100.0%)'],
  381. (2, 0): ['width: 10em', ' height: 80%'],
  382. (2, 1): ['width: 10em', ' height: 80%',
  383. 'background: linear-gradient(90deg,#d65f5f 50.0%'
  384. ', transparent 50.0%)'],
  385. (2, 2): ['width: 10em', ' height: 80%',
  386. 'background: linear-gradient(90deg,#d65f5f 100.0%'
  387. ', transparent 100.0%)']}
  388. assert result == expected
  389. def test_bar_align_mid_pos_and_neg(self):
  390. df = pd.DataFrame({'A': [-10, 0, 20, 90]})
  391. result = df.style.bar(align='mid', color=[
  392. '#d65f5f', '#5fba7d'])._compute().ctx
  393. expected = {(0, 0): ['width: 10em', ' height: 80%',
  394. 'background: linear-gradient(90deg,'
  395. '#d65f5f 10.0%, transparent 10.0%)'],
  396. (1, 0): ['width: 10em', ' height: 80%', ],
  397. (2, 0): ['width: 10em', ' height: 80%',
  398. 'background: linear-gradient(90deg, '
  399. 'transparent 10.0%, #5fba7d 10.0%'
  400. ', #5fba7d 30.0%, transparent 30.0%)'],
  401. (3, 0): ['width: 10em', ' height: 80%',
  402. 'background: linear-gradient(90deg, '
  403. 'transparent 10.0%, '
  404. '#5fba7d 10.0%, #5fba7d 100.0%, '
  405. 'transparent 100.0%)']}
  406. assert result == expected
  407. def test_bar_align_mid_all_pos(self):
  408. df = pd.DataFrame({'A': [10, 20, 50, 100]})
  409. result = df.style.bar(align='mid', color=[
  410. '#d65f5f', '#5fba7d'])._compute().ctx
  411. expected = {(0, 0): ['width: 10em', ' height: 80%',
  412. 'background: linear-gradient(90deg,'
  413. '#5fba7d 10.0%, transparent 10.0%)'],
  414. (1, 0): ['width: 10em', ' height: 80%',
  415. 'background: linear-gradient(90deg,'
  416. '#5fba7d 20.0%, transparent 20.0%)'],
  417. (2, 0): ['width: 10em', ' height: 80%',
  418. 'background: linear-gradient(90deg,'
  419. '#5fba7d 50.0%, transparent 50.0%)'],
  420. (3, 0): ['width: 10em', ' height: 80%',
  421. 'background: linear-gradient(90deg,'
  422. '#5fba7d 100.0%, transparent 100.0%)']}
  423. assert result == expected
  424. def test_bar_align_mid_all_neg(self):
  425. df = pd.DataFrame({'A': [-100, -60, -30, -20]})
  426. result = df.style.bar(align='mid', color=[
  427. '#d65f5f', '#5fba7d'])._compute().ctx
  428. expected = {(0, 0): ['width: 10em', ' height: 80%',
  429. 'background: linear-gradient(90deg,'
  430. '#d65f5f 100.0%, transparent 100.0%)'],
  431. (1, 0): ['width: 10em', ' height: 80%',
  432. 'background: linear-gradient(90deg, '
  433. 'transparent 40.0%, '
  434. '#d65f5f 40.0%, #d65f5f 100.0%, '
  435. 'transparent 100.0%)'],
  436. (2, 0): ['width: 10em', ' height: 80%',
  437. 'background: linear-gradient(90deg, '
  438. 'transparent 70.0%, '
  439. '#d65f5f 70.0%, #d65f5f 100.0%, '
  440. 'transparent 100.0%)'],
  441. (3, 0): ['width: 10em', ' height: 80%',
  442. 'background: linear-gradient(90deg, '
  443. 'transparent 80.0%, '
  444. '#d65f5f 80.0%, #d65f5f 100.0%, '
  445. 'transparent 100.0%)']}
  446. assert result == expected
  447. def test_bar_align_zero_pos_and_neg(self):
  448. # See https://github.com/pandas-dev/pandas/pull/14757
  449. df = pd.DataFrame({'A': [-10, 0, 20, 90]})
  450. result = df.style.bar(align='zero', color=[
  451. '#d65f5f', '#5fba7d'], width=90)._compute().ctx
  452. expected = {(0, 0): ['width: 10em', ' height: 80%',
  453. 'background: linear-gradient(90deg, '
  454. 'transparent 40.0%, #d65f5f 40.0%, '
  455. '#d65f5f 45.0%, transparent 45.0%)'],
  456. (1, 0): ['width: 10em', ' height: 80%'],
  457. (2, 0): ['width: 10em', ' height: 80%',
  458. 'background: linear-gradient(90deg, '
  459. 'transparent 45.0%, #5fba7d 45.0%, '
  460. '#5fba7d 55.0%, transparent 55.0%)'],
  461. (3, 0): ['width: 10em', ' height: 80%',
  462. 'background: linear-gradient(90deg, '
  463. 'transparent 45.0%, #5fba7d 45.0%, '
  464. '#5fba7d 90.0%, transparent 90.0%)']}
  465. assert result == expected
  466. def test_bar_align_left_axis_none(self):
  467. df = pd.DataFrame({'A': [0, 1], 'B': [2, 4]})
  468. result = df.style.bar(axis=None)._compute().ctx
  469. expected = {
  470. (0, 0): ['width: 10em', ' height: 80%'],
  471. (1, 0): ['width: 10em', ' height: 80%',
  472. 'background: linear-gradient(90deg,'
  473. '#d65f5f 25.0%, transparent 25.0%)'],
  474. (0, 1): ['width: 10em', ' height: 80%',
  475. 'background: linear-gradient(90deg,'
  476. '#d65f5f 50.0%, transparent 50.0%)'],
  477. (1, 1): ['width: 10em', ' height: 80%',
  478. 'background: linear-gradient(90deg,'
  479. '#d65f5f 100.0%, transparent 100.0%)']
  480. }
  481. assert result == expected
  482. def test_bar_align_zero_axis_none(self):
  483. df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
  484. result = df.style.bar(align='zero', axis=None)._compute().ctx
  485. expected = {
  486. (0, 0): ['width: 10em', ' height: 80%'],
  487. (1, 0): ['width: 10em', ' height: 80%',
  488. 'background: linear-gradient(90deg, '
  489. 'transparent 50.0%, #d65f5f 50.0%, '
  490. '#d65f5f 62.5%, transparent 62.5%)'],
  491. (0, 1): ['width: 10em', ' height: 80%',
  492. 'background: linear-gradient(90deg, '
  493. 'transparent 25.0%, #d65f5f 25.0%, '
  494. '#d65f5f 50.0%, transparent 50.0%)'],
  495. (1, 1): ['width: 10em', ' height: 80%',
  496. 'background: linear-gradient(90deg, '
  497. 'transparent 50.0%, #d65f5f 50.0%, '
  498. '#d65f5f 100.0%, transparent 100.0%)']
  499. }
  500. assert result == expected
  501. def test_bar_align_mid_axis_none(self):
  502. df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
  503. result = df.style.bar(align='mid', axis=None)._compute().ctx
  504. expected = {
  505. (0, 0): ['width: 10em', ' height: 80%'],
  506. (1, 0): ['width: 10em', ' height: 80%',
  507. 'background: linear-gradient(90deg, '
  508. 'transparent 33.3%, #d65f5f 33.3%, '
  509. '#d65f5f 50.0%, transparent 50.0%)'],
  510. (0, 1): ['width: 10em', ' height: 80%',
  511. 'background: linear-gradient(90deg,'
  512. '#d65f5f 33.3%, transparent 33.3%)'],
  513. (1, 1): ['width: 10em', ' height: 80%',
  514. 'background: linear-gradient(90deg, '
  515. 'transparent 33.3%, #d65f5f 33.3%, '
  516. '#d65f5f 100.0%, transparent 100.0%)']
  517. }
  518. assert result == expected
  519. def test_bar_align_mid_vmin(self):
  520. df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
  521. result = df.style.bar(align='mid', axis=None, vmin=-6)._compute().ctx
  522. expected = {
  523. (0, 0): ['width: 10em', ' height: 80%'],
  524. (1, 0): ['width: 10em', ' height: 80%',
  525. 'background: linear-gradient(90deg, '
  526. 'transparent 60.0%, #d65f5f 60.0%, '
  527. '#d65f5f 70.0%, transparent 70.0%)'],
  528. (0, 1): ['width: 10em', ' height: 80%',
  529. 'background: linear-gradient(90deg, '
  530. 'transparent 40.0%, #d65f5f 40.0%, '
  531. '#d65f5f 60.0%, transparent 60.0%)'],
  532. (1, 1): ['width: 10em', ' height: 80%',
  533. 'background: linear-gradient(90deg, '
  534. 'transparent 60.0%, #d65f5f 60.0%, '
  535. '#d65f5f 100.0%, transparent 100.0%)']
  536. }
  537. assert result == expected
  538. def test_bar_align_mid_vmax(self):
  539. df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
  540. result = df.style.bar(align='mid', axis=None, vmax=8)._compute().ctx
  541. expected = {
  542. (0, 0): ['width: 10em', ' height: 80%'],
  543. (1, 0): ['width: 10em', ' height: 80%',
  544. 'background: linear-gradient(90deg, '
  545. 'transparent 20.0%, #d65f5f 20.0%, '
  546. '#d65f5f 30.0%, transparent 30.0%)'],
  547. (0, 1): ['width: 10em', ' height: 80%',
  548. 'background: linear-gradient(90deg,'
  549. '#d65f5f 20.0%, transparent 20.0%)'],
  550. (1, 1): ['width: 10em', ' height: 80%',
  551. 'background: linear-gradient(90deg, '
  552. 'transparent 20.0%, #d65f5f 20.0%, '
  553. '#d65f5f 60.0%, transparent 60.0%)']
  554. }
  555. assert result == expected
  556. def test_bar_align_mid_vmin_vmax_wide(self):
  557. df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
  558. result = df.style.bar(align='mid', axis=None,
  559. vmin=-3, vmax=7)._compute().ctx
  560. expected = {
  561. (0, 0): ['width: 10em', ' height: 80%'],
  562. (1, 0): ['width: 10em', ' height: 80%',
  563. 'background: linear-gradient(90deg, '
  564. 'transparent 30.0%, #d65f5f 30.0%, '
  565. '#d65f5f 40.0%, transparent 40.0%)'],
  566. (0, 1): ['width: 10em', ' height: 80%',
  567. 'background: linear-gradient(90deg, '
  568. 'transparent 10.0%, #d65f5f 10.0%, '
  569. '#d65f5f 30.0%, transparent 30.0%)'],
  570. (1, 1): ['width: 10em', ' height: 80%',
  571. 'background: linear-gradient(90deg, '
  572. 'transparent 30.0%, #d65f5f 30.0%, '
  573. '#d65f5f 70.0%, transparent 70.0%)']
  574. }
  575. assert result == expected
  576. def test_bar_align_mid_vmin_vmax_clipping(self):
  577. df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
  578. result = df.style.bar(align='mid', axis=None,
  579. vmin=-1, vmax=3)._compute().ctx
  580. expected = {
  581. (0, 0): ['width: 10em', ' height: 80%'],
  582. (1, 0): ['width: 10em', ' height: 80%',
  583. 'background: linear-gradient(90deg, '
  584. 'transparent 25.0%, #d65f5f 25.0%, '
  585. '#d65f5f 50.0%, transparent 50.0%)'],
  586. (0, 1): ['width: 10em', ' height: 80%',
  587. 'background: linear-gradient(90deg,'
  588. '#d65f5f 25.0%, transparent 25.0%)'],
  589. (1, 1): ['width: 10em', ' height: 80%',
  590. 'background: linear-gradient(90deg, '
  591. 'transparent 25.0%, #d65f5f 25.0%, '
  592. '#d65f5f 100.0%, transparent 100.0%)']
  593. }
  594. assert result == expected
  595. def test_bar_align_mid_nans(self):
  596. df = pd.DataFrame({'A': [1, None], 'B': [-1, 3]})
  597. result = df.style.bar(align='mid', axis=None)._compute().ctx
  598. expected = {
  599. (0, 0): ['width: 10em', ' height: 80%',
  600. 'background: linear-gradient(90deg, '
  601. 'transparent 25.0%, #d65f5f 25.0%, '
  602. '#d65f5f 50.0%, transparent 50.0%)'],
  603. (1, 0): [''],
  604. (0, 1): ['width: 10em', ' height: 80%',
  605. 'background: linear-gradient(90deg,'
  606. '#d65f5f 25.0%, transparent 25.0%)'],
  607. (1, 1): ['width: 10em', ' height: 80%',
  608. 'background: linear-gradient(90deg, '
  609. 'transparent 25.0%, #d65f5f 25.0%, '
  610. '#d65f5f 100.0%, transparent 100.0%)']
  611. }
  612. assert result == expected
  613. def test_bar_align_zero_nans(self):
  614. df = pd.DataFrame({'A': [1, None], 'B': [-1, 2]})
  615. result = df.style.bar(align='zero', axis=None)._compute().ctx
  616. expected = {
  617. (0, 0): ['width: 10em', ' height: 80%',
  618. 'background: linear-gradient(90deg, '
  619. 'transparent 50.0%, #d65f5f 50.0%, '
  620. '#d65f5f 75.0%, transparent 75.0%)'],
  621. (1, 0): [''],
  622. (0, 1): ['width: 10em', ' height: 80%',
  623. 'background: linear-gradient(90deg, '
  624. 'transparent 25.0%, #d65f5f 25.0%, '
  625. '#d65f5f 50.0%, transparent 50.0%)'],
  626. (1, 1): ['width: 10em', ' height: 80%',
  627. 'background: linear-gradient(90deg, '
  628. 'transparent 50.0%, #d65f5f 50.0%, '
  629. '#d65f5f 100.0%, transparent 100.0%)']
  630. }
  631. assert result == expected
  632. def test_bar_bad_align_raises(self):
  633. df = pd.DataFrame({'A': [-100, -60, -30, -20]})
  634. with pytest.raises(ValueError):
  635. df.style.bar(align='poorly', color=['#d65f5f', '#5fba7d'])
  636. def test_highlight_null(self, null_color='red'):
  637. df = pd.DataFrame({'A': [0, np.nan]})
  638. result = df.style.highlight_null()._compute().ctx
  639. expected = {(0, 0): [''],
  640. (1, 0): ['background-color: red']}
  641. assert result == expected
  642. def test_nonunique_raises(self):
  643. df = pd.DataFrame([[1, 2]], columns=['A', 'A'])
  644. with pytest.raises(ValueError):
  645. df.style
  646. with pytest.raises(ValueError):
  647. Styler(df)
  648. def test_caption(self):
  649. styler = Styler(self.df, caption='foo')
  650. result = styler.render()
  651. assert all(['caption' in result, 'foo' in result])
  652. styler = self.df.style
  653. result = styler.set_caption('baz')
  654. assert styler is result
  655. assert styler.caption == 'baz'
  656. def test_uuid(self):
  657. styler = Styler(self.df, uuid='abc123')
  658. result = styler.render()
  659. assert 'abc123' in result
  660. styler = self.df.style
  661. result = styler.set_uuid('aaa')
  662. assert result is styler
  663. assert result.uuid == 'aaa'
  664. def test_unique_id(self):
  665. # See https://github.com/pandas-dev/pandas/issues/16780
  666. df = pd.DataFrame({'a': [1, 3, 5, 6], 'b': [2, 4, 12, 21]})
  667. result = df.style.render(uuid='test')
  668. assert 'test' in result
  669. ids = re.findall('id="(.*?)"', result)
  670. assert np.unique(ids).size == len(ids)
  671. def test_table_styles(self):
  672. style = [{'selector': 'th', 'props': [('foo', 'bar')]}]
  673. styler = Styler(self.df, table_styles=style)
  674. result = ' '.join(styler.render().split())
  675. assert 'th { foo: bar; }' in result
  676. styler = self.df.style
  677. result = styler.set_table_styles(style)
  678. assert styler is result
  679. assert styler.table_styles == style
  680. def test_table_attributes(self):
  681. attributes = 'class="foo" data-bar'
  682. styler = Styler(self.df, table_attributes=attributes)
  683. result = styler.render()
  684. assert 'class="foo" data-bar' in result
  685. result = self.df.style.set_table_attributes(attributes).render()
  686. assert 'class="foo" data-bar' in result
  687. def test_precision(self):
  688. with pd.option_context('display.precision', 10):
  689. s = Styler(self.df)
  690. assert s.precision == 10
  691. s = Styler(self.df, precision=2)
  692. assert s.precision == 2
  693. s2 = s.set_precision(4)
  694. assert s is s2
  695. assert s.precision == 4
  696. def test_apply_none(self):
  697. def f(x):
  698. return pd.DataFrame(np.where(x == x.max(), 'color: red', ''),
  699. index=x.index, columns=x.columns)
  700. result = (pd.DataFrame([[1, 2], [3, 4]])
  701. .style.apply(f, axis=None)._compute().ctx)
  702. assert result[(1, 1)] == ['color: red']
  703. def test_trim(self):
  704. result = self.df.style.render() # trim=True
  705. assert result.count('#') == 0
  706. result = self.df.style.highlight_max().render()
  707. assert result.count('#') == len(self.df.columns)
  708. def test_highlight_max(self):
  709. df = pd.DataFrame([[1, 2], [3, 4]], columns=['A', 'B'])
  710. # max(df) = min(-df)
  711. for max_ in [True, False]:
  712. if max_:
  713. attr = 'highlight_max'
  714. else:
  715. df = -df
  716. attr = 'highlight_min'
  717. result = getattr(df.style, attr)()._compute().ctx
  718. assert result[(1, 1)] == ['background-color: yellow']
  719. result = getattr(df.style, attr)(color='green')._compute().ctx
  720. assert result[(1, 1)] == ['background-color: green']
  721. result = getattr(df.style, attr)(subset='A')._compute().ctx
  722. assert result[(1, 0)] == ['background-color: yellow']
  723. result = getattr(df.style, attr)(axis=0)._compute().ctx
  724. expected = {(1, 0): ['background-color: yellow'],
  725. (1, 1): ['background-color: yellow'],
  726. (0, 1): [''], (0, 0): ['']}
  727. assert result == expected
  728. result = getattr(df.style, attr)(axis=1)._compute().ctx
  729. expected = {(0, 1): ['background-color: yellow'],
  730. (1, 1): ['background-color: yellow'],
  731. (0, 0): [''], (1, 0): ['']}
  732. assert result == expected
  733. # separate since we can't negate the strs
  734. df['C'] = ['a', 'b']
  735. result = df.style.highlight_max()._compute().ctx
  736. expected = {(1, 1): ['background-color: yellow']}
  737. result = df.style.highlight_min()._compute().ctx
  738. expected = {(0, 0): ['background-color: yellow']}
  739. def test_export(self):
  740. f = lambda x: 'color: red' if x > 0 else 'color: blue'
  741. g = lambda x, y, z: 'color: {z}'.format(z=z) \
  742. if x > 0 else 'color: {z}'.format(z=z)
  743. style1 = self.styler
  744. style1.applymap(f)\
  745. .applymap(g, y='a', z='b')\
  746. .highlight_max()
  747. result = style1.export()
  748. style2 = self.df.style
  749. style2.use(result)
  750. assert style1._todo == style2._todo
  751. style2.render()
  752. def test_display_format(self):
  753. df = pd.DataFrame(np.random.random(size=(2, 2)))
  754. ctx = df.style.format("{:0.1f}")._translate()
  755. assert all(['display_value' in c for c in row]
  756. for row in ctx['body'])
  757. assert all([len(c['display_value']) <= 3 for c in row[1:]]
  758. for row in ctx['body'])
  759. assert len(ctx['body'][0][1]['display_value'].lstrip('-')) <= 3
  760. def test_display_format_raises(self):
  761. df = pd.DataFrame(np.random.randn(2, 2))
  762. with pytest.raises(TypeError):
  763. df.style.format(5)
  764. with pytest.raises(TypeError):
  765. df.style.format(True)
  766. def test_display_subset(self):
  767. df = pd.DataFrame([[.1234, .1234], [1.1234, 1.1234]],
  768. columns=['a', 'b'])
  769. ctx = df.style.format({"a": "{:0.1f}", "b": "{0:.2%}"},
  770. subset=pd.IndexSlice[0, :])._translate()
  771. expected = '0.1'
  772. assert ctx['body'][0][1]['display_value'] == expected
  773. assert ctx['body'][1][1]['display_value'] == '1.1234'
  774. assert ctx['body'][0][2]['display_value'] == '12.34%'
  775. raw_11 = '1.1234'
  776. ctx = df.style.format("{:0.1f}",
  777. subset=pd.IndexSlice[0, :])._translate()
  778. assert ctx['body'][0][1]['display_value'] == expected
  779. assert ctx['body'][1][1]['display_value'] == raw_11
  780. ctx = df.style.format("{:0.1f}",
  781. subset=pd.IndexSlice[0, :])._translate()
  782. assert ctx['body'][0][1]['display_value'] == expected
  783. assert ctx['body'][1][1]['display_value'] == raw_11
  784. ctx = df.style.format("{:0.1f}",
  785. subset=pd.IndexSlice['a'])._translate()
  786. assert ctx['body'][0][1]['display_value'] == expected
  787. assert ctx['body'][0][2]['display_value'] == '0.1234'
  788. ctx = df.style.format("{:0.1f}",
  789. subset=pd.IndexSlice[0, 'a'])._translate()
  790. assert ctx['body'][0][1]['display_value'] == expected
  791. assert ctx['body'][1][1]['display_value'] == raw_11
  792. ctx = df.style.format("{:0.1f}",
  793. subset=pd.IndexSlice[[0, 1], ['a']])._translate()
  794. assert ctx['body'][0][1]['display_value'] == expected
  795. assert ctx['body'][1][1]['display_value'] == '1.1'
  796. assert ctx['body'][0][2]['display_value'] == '0.1234'
  797. assert ctx['body'][1][2]['display_value'] == '1.1234'
  798. def test_display_dict(self):
  799. df = pd.DataFrame([[.1234, .1234], [1.1234, 1.1234]],
  800. columns=['a', 'b'])
  801. ctx = df.style.format({"a": "{:0.1f}", "b": "{0:.2%}"})._translate()
  802. assert ctx['body'][0][1]['display_value'] == '0.1'
  803. assert ctx['body'][0][2]['display_value'] == '12.34%'
  804. df['c'] = ['aaa', 'bbb']
  805. ctx = df.style.format({"a": "{:0.1f}", "c": str.upper})._translate()
  806. assert ctx['body'][0][1]['display_value'] == '0.1'
  807. assert ctx['body'][0][3]['display_value'] == 'AAA'
  808. def test_bad_apply_shape(self):
  809. df = pd.DataFrame([[1, 2], [3, 4]])
  810. with pytest.raises(ValueError):
  811. df.style._apply(lambda x: 'x', subset=pd.IndexSlice[[0, 1], :])
  812. with pytest.raises(ValueError):
  813. df.style._apply(lambda x: [''], subset=pd.IndexSlice[[0, 1], :])
  814. with pytest.raises(ValueError):
  815. df.style._apply(lambda x: ['', '', '', ''])
  816. with pytest.raises(ValueError):
  817. df.style._apply(lambda x: ['', '', ''], subset=1)
  818. with pytest.raises(ValueError):
  819. df.style._apply(lambda x: ['', '', ''], axis=1)
  820. def test_apply_bad_return(self):
  821. def f(x):
  822. return ''
  823. df = pd.DataFrame([[1, 2], [3, 4]])
  824. with pytest.raises(TypeError):
  825. df.style._apply(f, axis=None)
  826. def test_apply_bad_labels(self):
  827. def f(x):
  828. return pd.DataFrame(index=[1, 2], columns=['a', 'b'])
  829. df = pd.DataFrame([[1, 2], [3, 4]])
  830. with pytest.raises(ValueError):
  831. df.style._apply(f, axis=None)
  832. def test_get_level_lengths(self):
  833. index = pd.MultiIndex.from_product([['a', 'b'], [0, 1, 2]])
  834. expected = {(0, 0): 3, (0, 3): 3, (1, 0): 1, (1, 1): 1, (1, 2): 1,
  835. (1, 3): 1, (1, 4): 1, (1, 5): 1}
  836. result = _get_level_lengths(index)
  837. tm.assert_dict_equal(result, expected)
  838. def test_get_level_lengths_un_sorted(self):
  839. index = pd.MultiIndex.from_arrays([
  840. [1, 1, 2, 1],
  841. ['a', 'b', 'b', 'd']
  842. ])
  843. expected = {(0, 0): 2, (0, 2): 1, (0, 3): 1,
  844. (1, 0): 1, (1, 1): 1, (1, 2): 1, (1, 3): 1}
  845. result = _get_level_lengths(index)
  846. tm.assert_dict_equal(result, expected)
  847. def test_mi_sparse(self):
  848. df = pd.DataFrame({'A': [1, 2]},
  849. index=pd.MultiIndex.from_arrays([['a', 'a'],
  850. [0, 1]]))
  851. result = df.style._translate()
  852. body_0 = result['body'][0][0]
  853. expected_0 = {
  854. "value": "a", "display_value": "a", "is_visible": True,
  855. "type": "th", "attributes": ["rowspan=2"],
  856. "class": "row_heading level0 row0", "id": "level0_row0"
  857. }
  858. tm.assert_dict_equal(body_0, expected_0)
  859. body_1 = result['body'][0][1]
  860. expected_1 = {
  861. "value": 0, "display_value": 0, "is_visible": True,
  862. "type": "th", "class": "row_heading level1 row0",
  863. "id": "level1_row0"
  864. }
  865. tm.assert_dict_equal(body_1, expected_1)
  866. body_10 = result['body'][1][0]
  867. expected_10 = {
  868. "value": 'a', "display_value": 'a', "is_visible": False,
  869. "type": "th", "class": "row_heading level0 row1",
  870. "id": "level0_row1"
  871. }
  872. tm.assert_dict_equal(body_10, expected_10)
  873. head = result['head'][0]
  874. expected = [
  875. {'type': 'th', 'class': 'blank', 'value': '',
  876. 'is_visible': True, "display_value": ''},
  877. {'type': 'th', 'class': 'blank level0', 'value': '',
  878. 'is_visible': True, 'display_value': ''},
  879. {'type': 'th', 'class': 'col_heading level0 col0', 'value': 'A',
  880. 'is_visible': True, 'display_value': 'A'}]
  881. assert head == expected
  882. def test_mi_sparse_disabled(self):
  883. with pd.option_context('display.multi_sparse', False):
  884. df = pd.DataFrame({'A': [1, 2]},
  885. index=pd.MultiIndex.from_arrays([['a', 'a'],
  886. [0, 1]]))
  887. result = df.style._translate()
  888. body = result['body']
  889. for row in body:
  890. assert 'attributes' not in row[0]
  891. def test_mi_sparse_index_names(self):
  892. df = pd.DataFrame({'A': [1, 2]}, index=pd.MultiIndex.from_arrays(
  893. [['a', 'a'], [0, 1]],
  894. names=['idx_level_0', 'idx_level_1'])
  895. )
  896. result = df.style._translate()
  897. head = result['head'][1]
  898. expected = [{
  899. 'class': 'index_name level0', 'value': 'idx_level_0',
  900. 'type': 'th'},
  901. {'class': 'index_name level1', 'value': 'idx_level_1',
  902. 'type': 'th'},
  903. {'class': 'blank', 'value': '', 'type': 'th'}]
  904. assert head == expected
  905. def test_mi_sparse_column_names(self):
  906. df = pd.DataFrame(
  907. np.arange(16).reshape(4, 4),
  908. index=pd.MultiIndex.from_arrays(
  909. [['a', 'a', 'b', 'a'], [0, 1, 1, 2]],
  910. names=['idx_level_0', 'idx_level_1']),
  911. columns=pd.MultiIndex.from_arrays(
  912. [['C1', 'C1', 'C2', 'C2'], [1, 0, 1, 0]],
  913. names=['col_0', 'col_1']
  914. )
  915. )
  916. result = df.style._translate()
  917. head = result['head'][1]
  918. expected = [
  919. {'class': 'blank', 'value': '', 'display_value': '',
  920. 'type': 'th', 'is_visible': True},
  921. {'class': 'index_name level1', 'value': 'col_1',
  922. 'display_value': 'col_1', 'is_visible': True, 'type': 'th'},
  923. {'class': 'col_heading level1 col0',
  924. 'display_value': 1,
  925. 'is_visible': True,
  926. 'type': 'th',
  927. 'value': 1},
  928. {'class': 'col_heading level1 col1',
  929. 'display_value': 0,
  930. 'is_visible': True,
  931. 'type': 'th',
  932. 'value': 0},
  933. {'class': 'col_heading level1 col2',
  934. 'display_value': 1,
  935. 'is_visible': True,
  936. 'type': 'th',
  937. 'value': 1},
  938. {'class': 'col_heading level1 col3',
  939. 'display_value': 0,
  940. 'is_visible': True,
  941. 'type': 'th',
  942. 'value': 0},
  943. ]
  944. assert head == expected
  945. def test_hide_single_index(self):
  946. # GH 14194
  947. # single unnamed index
  948. ctx = self.df.style._translate()
  949. assert ctx['body'][0][0]['is_visible']
  950. assert ctx['head'][0][0]['is_visible']
  951. ctx2 = self.df.style.hide_index()._translate()
  952. assert not ctx2['body'][0][0]['is_visible']
  953. assert not ctx2['head'][0][0]['is_visible']
  954. # single named index
  955. ctx3 = self.df.set_index('A').style._translate()
  956. assert ctx3['body'][0][0]['is_visible']
  957. assert len(ctx3['head']) == 2 # 2 header levels
  958. assert ctx3['head'][0][0]['is_visible']
  959. ctx4 = self.df.set_index('A').style.hide_index()._translate()
  960. assert not ctx4['body'][0][0]['is_visible']
  961. assert len(ctx4['head']) == 1 # only 1 header levels
  962. assert not ctx4['head'][0][0]['is_visible']
  963. def test_hide_multiindex(self):
  964. # GH 14194
  965. df = pd.DataFrame({'A': [1, 2]}, index=pd.MultiIndex.from_arrays(
  966. [['a', 'a'], [0, 1]],
  967. names=['idx_level_0', 'idx_level_1'])
  968. )
  969. ctx1 = df.style._translate()
  970. # tests for 'a' and '0'
  971. assert ctx1['body'][0][0]['is_visible']
  972. assert ctx1['body'][0][1]['is_visible']
  973. # check for blank header rows
  974. assert ctx1['head'][0][0]['is_visible']
  975. assert ctx1['head'][0][1]['is_visible']
  976. ctx2 = df.style.hide_index()._translate()
  977. # tests for 'a' and '0'
  978. assert not ctx2['body'][0][0]['is_visible']
  979. assert not ctx2['body'][0][1]['is_visible']
  980. # check for blank header rows
  981. assert not ctx2['head'][0][0]['is_visible']
  982. assert not ctx2['head'][0][1]['is_visible']
  983. def test_hide_columns_single_level(self):
  984. # GH 14194
  985. # test hiding single column
  986. ctx = self.df.style._translate()
  987. assert ctx['head'][0][1]['is_visible']
  988. assert ctx['head'][0][1]['display_value'] == 'A'
  989. assert ctx['head'][0][2]['is_visible']
  990. assert ctx['head'][0][2]['display_value'] == 'B'
  991. assert ctx['body'][0][1]['is_visible'] # col A, row 1
  992. assert ctx['body'][1][2]['is_visible'] # col B, row 1
  993. ctx = self.df.style.hide_columns('A')._translate()
  994. assert not ctx['head'][0][1]['is_visible']
  995. assert not ctx['body'][0][1]['is_visible'] # col A, row 1
  996. assert ctx['body'][1][2]['is_visible'] # col B, row 1
  997. # test hiding mulitiple columns
  998. ctx = self.df.style.hide_columns(['A', 'B'])._translate()
  999. assert not ctx['head'][0][1]['is_visible']
  1000. assert not ctx['head'][0][2]['is_visible']
  1001. assert not ctx['body'][0][1]['is_visible'] # col A, row 1
  1002. assert not ctx['body'][1][2]['is_visible'] # col B, row 1
  1003. def test_hide_columns_mult_levels(self):
  1004. # GH 14194
  1005. # setup dataframe with multiple column levels and indices
  1006. i1 = pd.MultiIndex.from_arrays([['a', 'a'], [0, 1]],
  1007. names=['idx_level_0',
  1008. 'idx_level_1'])
  1009. i2 = pd.MultiIndex.from_arrays([['b', 'b'], [0, 1]],
  1010. names=['col_level_0',
  1011. 'col_level_1'])
  1012. df = pd.DataFrame([[1, 2], [3, 4]], index=i1, columns=i2)
  1013. ctx = df.style._translate()
  1014. # column headers
  1015. assert ctx['head'][0][2]['is_visible']
  1016. assert ctx['head'][1][2]['is_visible']
  1017. assert ctx['head'][1][3]['display_value'] == 1
  1018. # indices
  1019. assert ctx['body'][0][0]['is_visible']
  1020. # data
  1021. assert ctx['body'][1][2]['is_visible']
  1022. assert ctx['body'][1][2]['display_value'] == 3
  1023. assert ctx['body'][1][3]['is_visible']
  1024. assert ctx['body'][1][3]['display_value'] == 4
  1025. # hide top column level, which hides both columns
  1026. ctx = df.style.hide_columns('b')._translate()
  1027. assert not ctx['head'][0][2]['is_visible'] # b
  1028. assert not ctx['head'][1][2]['is_visible'] # 0
  1029. assert not ctx['body'][1][2]['is_visible'] # 3
  1030. assert ctx['body'][0][0]['is_visible'] # index
  1031. # hide first column only
  1032. ctx = df.style.hide_columns([('b', 0)])._translate()
  1033. assert ctx['head'][0][2]['is_visible'] # b
  1034. assert not ctx['head'][1][2]['is_visible'] # 0
  1035. assert not ctx['body'][1][2]['is_visible'] # 3
  1036. assert ctx['body'][1][3]['is_visible']
  1037. assert ctx['body'][1][3]['display_value'] == 4
  1038. # hide second column and index
  1039. ctx = df.style.hide_columns([('b', 1)]).hide_index()._translate()
  1040. assert not ctx['body'][0][0]['is_visible'] # index
  1041. assert ctx['head'][0][2]['is_visible'] # b
  1042. assert ctx['head'][1][2]['is_visible'] # 0
  1043. assert not ctx['head'][1][3]['is_visible'] # 1
  1044. assert not ctx['body'][1][3]['is_visible'] # 4
  1045. assert ctx['body'][1][2]['is_visible']
  1046. assert ctx['body'][1][2]['display_value'] == 3
  1047. def test_pipe(self):
  1048. def set_caption_from_template(styler, a, b):
  1049. return styler.set_caption(
  1050. 'Dataframe with a = {a} and b = {b}'.format(a=a, b=b))
  1051. styler = self.df.style.pipe(set_caption_from_template, 'A', b='B')
  1052. assert 'Dataframe with a = A and b = B' in styler.render()
  1053. # Test with an argument that is a (callable, keyword_name) pair.
  1054. def f(a, b, styler):
  1055. return (a, b, styler)
  1056. styler = self.df.style
  1057. result = styler.pipe((f, 'styler'), a=1, b=2)
  1058. assert result == (1, 2, styler)
  1059. @td.skip_if_no_mpl
  1060. class TestStylerMatplotlibDep(object):
  1061. def test_background_gradient(self):
  1062. df = pd.DataFrame([[1, 2], [2, 4]], columns=['A', 'B'])
  1063. for c_map in [None, 'YlOrRd']:
  1064. result = df.style.background_gradient(cmap=c_map)._compute().ctx
  1065. assert all("#" in x[0] for x in result.values())
  1066. assert result[(0, 0)] == result[(0, 1)]
  1067. assert result[(1, 0)] == result[(1, 1)]
  1068. result = df.style.background_gradient(
  1069. subset=pd.IndexSlice[1, 'A'])._compute().ctx
  1070. assert result[(1, 0)] == ['background-color: #fff7fb',
  1071. 'color: #000000']
  1072. @pytest.mark.parametrize(
  1073. 'c_map,expected', [
  1074. (None, {
  1075. (0, 0): ['background-color: #440154', 'color: #f1f1f1'],
  1076. (1, 0): ['background-color: #fde725', 'color: #000000']}),
  1077. ('YlOrRd', {
  1078. (0, 0): ['background-color: #ffffcc', 'color: #000000'],
  1079. (1, 0): ['background-color: #800026', 'color: #f1f1f1']})])
  1080. def test_text_color_threshold(self, c_map, expected):
  1081. df = pd.DataFrame([1, 2], columns=['A'])
  1082. result = df.style.background_gradient(cmap=c_map)._compute().ctx
  1083. assert result == expected
  1084. @pytest.mark.parametrize("text_color_threshold", [1.1, '1', -1, [2, 2]])
  1085. def test_text_color_threshold_raises(self, text_color_threshold):
  1086. df = pd.DataFrame([[1, 2], [2, 4]], columns=['A', 'B'])
  1087. msg = "`text_color_threshold` must be a value from 0 to 1."
  1088. with pytest.raises(ValueError, match=msg):
  1089. df.style.background_gradient(
  1090. text_color_threshold=text_color_threshold)._compute()
  1091. @td.skip_if_no_mpl
  1092. def test_background_gradient_axis(self):
  1093. df = pd.DataFrame([[1, 2], [2, 4]], columns=['A', 'B'])
  1094. low = ['background-color: #f7fbff', 'color: #000000']
  1095. high = ['background-color: #08306b', 'color: #f1f1f1']
  1096. mid = ['background-color: #abd0e6', 'color: #000000']
  1097. result = df.style.background_gradient(cmap='Blues',
  1098. axis=0)._compute().ctx
  1099. assert result[(0, 0)] == low
  1100. assert result[(0, 1)] == low
  1101. assert result[(1, 0)] == high
  1102. assert result[(1, 1)] == high
  1103. result = df.style.background_gradient(cmap='Blues',
  1104. axis=1)._compute().ctx
  1105. assert result[(0, 0)] == low
  1106. assert result[(0, 1)] == high
  1107. assert result[(1, 0)] == low
  1108. assert result[(1, 1)] == high
  1109. result = df.style.background_gradient(cmap='Blues',
  1110. axis=None)._compute().ctx
  1111. assert result[(0, 0)] == low
  1112. assert result[(0, 1)] == mid
  1113. assert result[(1, 0)] == mid
  1114. assert result[(1, 1)] == high
  1115. def test_block_names():
  1116. # catch accidental removal of a block
  1117. expected = {
  1118. 'before_style', 'style', 'table_styles', 'before_cellstyle',
  1119. 'cellstyle', 'before_table', 'table', 'caption', 'thead', 'tbody',
  1120. 'after_table', 'before_head_rows', 'head_tr', 'after_head_rows',
  1121. 'before_rows', 'tr', 'after_rows',
  1122. }
  1123. result = set(Styler.template.blocks)
  1124. assert result == expected
  1125. def test_from_custom_template(tmpdir):
  1126. p = tmpdir.mkdir("templates").join("myhtml.tpl")
  1127. p.write(textwrap.dedent("""\
  1128. {% extends "html.tpl" %}
  1129. {% block table %}
  1130. <h1>{{ table_title|default("My Table") }}</h1>
  1131. {{ super() }}
  1132. {% endblock table %}"""))
  1133. result = Styler.from_custom_template(str(tmpdir.join('templates')),
  1134. 'myhtml.tpl')
  1135. assert issubclass(result, Styler)
  1136. assert result.env is not Styler.env
  1137. assert result.template is not Styler.template
  1138. styler = result(pd.DataFrame({"A": [1, 2]}))
  1139. assert styler.render()