Skip to content

Commit 2b9c57f

Browse files
Licht-Talanbato
authored andcommitted
ENH: Add Styler.where (pandas-dev#17474)
1 parent 623fa57 commit 2b9c57f

File tree

4 files changed

+102
-0
lines changed

4 files changed

+102
-0
lines changed

doc/source/api.rst

+1
Original file line numberDiff line numberDiff line change
@@ -2062,6 +2062,7 @@ Style Application
20622062

20632063
Styler.apply
20642064
Styler.applymap
2065+
Styler.where
20652066
Styler.format
20662067
Styler.set_precision
20672068
Styler.set_table_styles

doc/source/whatsnew/v0.21.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ Other Enhancements
112112
- `read_*` methods can now infer compression from non-string paths, such as ``pathlib.Path`` objects (:issue:`17206`).
113113
- :func:`pd.read_sas()` now recognizes much more of the most frequently used date (datetime) formats in SAS7BDAT files (:issue:`15871`).
114114
- :func:`DataFrame.items` and :func:`Series.items` is now present in both Python 2 and 3 and is lazy in all cases (:issue:`13918`, :issue:`17213`)
115+
- :func:`Styler.where` has been implemented. It is as a convenience for :func:`Styler.applymap` and enables simple DataFrame styling on the Jupyter notebook (:issue:`17474`).
115116

116117

117118

pandas/io/formats/style.py

+42
Original file line numberDiff line numberDiff line change
@@ -618,11 +618,53 @@ def applymap(self, func, subset=None, **kwargs):
618618
-------
619619
self : Styler
620620
621+
See Also
622+
--------
623+
Styler.where
624+
621625
"""
622626
self._todo.append((lambda instance: getattr(instance, '_applymap'),
623627
(func, subset), kwargs))
624628
return self
625629

630+
def where(self, cond, value, other=None, subset=None, **kwargs):
631+
"""
632+
Apply a function elementwise, updating the HTML
633+
representation with a style which is selected in
634+
accordance with the return value of a function.
635+
636+
.. versionadded:: 0.21.0
637+
638+
Parameters
639+
----------
640+
cond : callable
641+
``cond`` should take a scalar and return a boolean
642+
value : str
643+
applied when ``cond`` returns true
644+
other : str
645+
applied when ``cond`` returns false
646+
subset : IndexSlice
647+
a valid indexer to limit ``data`` to *before* applying the
648+
function. Consider using a pandas.IndexSlice
649+
kwargs : dict
650+
pass along to ``cond``
651+
652+
Returns
653+
-------
654+
self : Styler
655+
656+
See Also
657+
--------
658+
Styler.applymap
659+
660+
"""
661+
662+
if other is None:
663+
other = ''
664+
665+
return self.applymap(lambda val: value if cond(val) else other,
666+
subset=subset, **kwargs)
667+
626668
def set_precision(self, precision):
627669
"""
628670
Set the precision used to render.

pandas/tests/io/formats/test_style.py

+58
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,64 @@ def f(x):
265265
col in self.df.loc[slice_].columns)
266266
assert result == expected
267267

268+
def test_where_with_one_style(self):
269+
# GH 17474
270+
def f(x):
271+
return x > 0.5
272+
273+
style1 = 'foo: bar'
274+
275+
result = self.df.style.where(f, style1)._compute().ctx
276+
expected = dict(((r, c),
277+
[style1 if f(self.df.loc[row, col]) else ''])
278+
for r, row in enumerate(self.df.index)
279+
for c, col in enumerate(self.df.columns))
280+
assert result == expected
281+
282+
def test_where_subset(self):
283+
# GH 17474
284+
def f(x):
285+
return x > 0.5
286+
287+
style1 = 'foo: bar'
288+
style2 = 'baz: foo'
289+
290+
slices = [pd.IndexSlice[:], pd.IndexSlice[:, ['A']],
291+
pd.IndexSlice[[1], :], pd.IndexSlice[[1], ['A']],
292+
pd.IndexSlice[:2, ['A', 'B']]]
293+
294+
for slice_ in slices:
295+
result = self.df.style.where(f, style1, style2,
296+
subset=slice_)._compute().ctx
297+
expected = dict(((r, c),
298+
[style1 if f(self.df.loc[row, col]) else style2])
299+
for r, row in enumerate(self.df.index)
300+
for c, col in enumerate(self.df.columns)
301+
if row in self.df.loc[slice_].index and
302+
col in self.df.loc[slice_].columns)
303+
assert result == expected
304+
305+
def test_where_subset_compare_with_applymap(self):
306+
# GH 17474
307+
def f(x):
308+
return x > 0.5
309+
310+
style1 = 'foo: bar'
311+
style2 = 'baz: foo'
312+
313+
def g(x):
314+
return style1 if f(x) else style2
315+
316+
slices = [pd.IndexSlice[:], pd.IndexSlice[:, ['A']],
317+
pd.IndexSlice[[1], :], pd.IndexSlice[[1], ['A']],
318+
pd.IndexSlice[:2, ['A', 'B']]]
319+
320+
for slice_ in slices:
321+
result = self.df.style.where(f, style1, style2,
322+
subset=slice_)._compute().ctx
323+
expected = self.df.style.applymap(g, subset=slice_)._compute().ctx
324+
assert result == expected
325+
268326
def test_empty(self):
269327
df = pd.DataFrame({'A': [1, 0]})
270328
s = df.style

0 commit comments

Comments
 (0)