Skip to content

Commit 336f4bc

Browse files
committed
ENH: Styler.bar: add support for vmin/vmax #21526
1 parent 5f2dfc2 commit 336f4bc

File tree

3 files changed

+93
-7
lines changed

3 files changed

+93
-7
lines changed

doc/source/whatsnew/v0.24.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ Other
425425

426426
- :meth: `~pandas.io.formats.style.Styler.background_gradient` now takes a ``text_color_threshold`` parameter to automatically lighten the text color based on the luminance of the background color. This improves readability with dark background colors without the need to limit the background colormap range. (:issue:`21258`)
427427
- Require at least 0.28.2 version of ``cython`` to support read-only memoryviews (:issue:`21688`)
428-
- :meth: `~pandas.io.formats.style.Styler.bar` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None``
428+
- :meth: `~pandas.io.formats.style.Styler.bar` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None`` and setting clipping range with ``vmin`` and ``vmax`` (:issue:`21548` and :issue:`21526`)
429429
-
430430
-
431431
-

pandas/io/formats/style.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -984,12 +984,12 @@ def set_properties(self, subset=None, **kwargs):
984984
return self.applymap(f, subset=subset)
985985

986986
@staticmethod
987-
def _bar(s, align, color='#d65f5f', width=100):
987+
def _bar(s, align, color='#d65f5f', width=100, vmin=None, vmax=None):
988988
"""Draw bar chart in dataframe cells"""
989989

990990
# Get input value range.
991-
smin = s.values.min()
992-
smax = s.values.max()
991+
smin = s.values.min() if vmin is None else vmin
992+
smax = s.values.max() if vmax is None else vmax
993993
if align == 'mid':
994994
smin = min(0, smin)
995995
smax = max(0, smax)
@@ -1011,7 +1011,7 @@ def css_bar(start, end, color):
10111011
s=start, c=color
10121012
)
10131013
css += '{c} {e:.1f}%, transparent {e:.1f}%)'.format(
1014-
e=end, c=color,
1014+
e=min(end, width), c=color,
10151015
)
10161016
return css
10171017

@@ -1030,7 +1030,7 @@ def css(x):
10301030
)
10311031

10321032
def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
1033-
align='left'):
1033+
align='left', vmin=None, vmax=None):
10341034
"""
10351035
Draw bars in the cell backgrounds, with a width proportional to
10361036
the values
@@ -1055,6 +1055,12 @@ def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
10551055
- 'mid' : the center of the cell is at (max-min)/2, or
10561056
if values are all negative (positive) the zero is aligned
10571057
at the right (left) of the cell
1058+
vmin: float or None: minimum bar value, defining the left hand limit
1059+
of the bar drawing range, lower values are clipped to ``vmin``.
1060+
When None (default): the minimum value of the data will be used.
1061+
vmax: float or None: maximum bar value, defining the right hand limit
1062+
of the bar drawing range, higher values are clipped to ``vmax``.
1063+
When None (default): the maximum value of the data will be used.
10581064
10591065
.. versionadded:: 0.20.0
10601066
@@ -1077,7 +1083,7 @@ def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
10771083
subset = _maybe_numeric_slice(self.data, subset)
10781084
subset = _non_reducing_slice(subset)
10791085
self.apply(self._bar, subset=subset, axis=axis,
1080-
align=align, color=color, width=width)
1086+
align=align, color=color, width=width, vmin=vmin, vmax=vmax)
10811087

10821088
return self
10831089

pandas/tests/io/formats/test_style.py

+80
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,86 @@ def test_bar_align_mid_axis_none(self):
572572
}
573573
assert result == expected
574574

575+
def test_bar_align_mid_vmin(self):
576+
df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
577+
result = df.style.bar(align='mid', axis=None, vmin=-6)._compute().ctx
578+
expected = {
579+
(0, 0): ['width: 10em', ' height: 80%'],
580+
(1, 0): ['width: 10em', ' height: 80%',
581+
'background: linear-gradient(90deg, '
582+
'transparent 60.0%, #d65f5f 60.0%, '
583+
'#d65f5f 70.0%, transparent 70.0%)'],
584+
(0, 1): ['width: 10em', ' height: 80%',
585+
'background: linear-gradient(90deg, '
586+
'transparent 40.0%, #d65f5f 40.0%, '
587+
'#d65f5f 60.0%, transparent 60.0%)'],
588+
(1, 1): ['width: 10em', ' height: 80%',
589+
'background: linear-gradient(90deg, '
590+
'transparent 60.0%, #d65f5f 60.0%, '
591+
'#d65f5f 100.0%, transparent 100.0%)']
592+
}
593+
assert result == expected
594+
595+
def test_bar_align_mid_vmax(self):
596+
df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
597+
result = df.style.bar(align='mid', axis=None, vmax=8)._compute().ctx
598+
expected = {
599+
(0, 0): ['width: 10em', ' height: 80%'],
600+
(1, 0): ['width: 10em', ' height: 80%',
601+
'background: linear-gradient(90deg, '
602+
'transparent 20.0%, #d65f5f 20.0%, '
603+
'#d65f5f 30.0%, transparent 30.0%)'],
604+
(0, 1): ['width: 10em', ' height: 80%',
605+
'background: linear-gradient(90deg,'
606+
'#d65f5f 20.0%, transparent 20.0%)'],
607+
(1, 1): ['width: 10em', ' height: 80%',
608+
'background: linear-gradient(90deg, '
609+
'transparent 20.0%, #d65f5f 20.0%, '
610+
'#d65f5f 60.0%, transparent 60.0%)']
611+
}
612+
assert result == expected
613+
614+
def test_bar_align_mid_vmin_vmax_wide(self):
615+
df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
616+
result = df.style.bar(align='mid', axis=None,
617+
vmin=-3, vmax=7)._compute().ctx
618+
expected = {
619+
(0, 0): ['width: 10em', ' height: 80%'],
620+
(1, 0): ['width: 10em', ' height: 80%',
621+
'background: linear-gradient(90deg, '
622+
'transparent 30.0%, #d65f5f 30.0%, '
623+
'#d65f5f 40.0%, transparent 40.0%)'],
624+
(0, 1): ['width: 10em', ' height: 80%',
625+
'background: linear-gradient(90deg, '
626+
'transparent 10.0%, #d65f5f 10.0%, '
627+
'#d65f5f 30.0%, transparent 30.0%)'],
628+
(1, 1): ['width: 10em', ' height: 80%',
629+
'background: linear-gradient(90deg, '
630+
'transparent 30.0%, #d65f5f 30.0%, '
631+
'#d65f5f 70.0%, transparent 70.0%)']
632+
}
633+
assert result == expected
634+
635+
def test_bar_align_mid_vmin_vmax_clipping(self):
636+
df = pd.DataFrame({'A': [0, 1], 'B': [-2, 4]})
637+
result = df.style.bar(align='mid', axis=None,
638+
vmin=-1, vmax=3)._compute().ctx
639+
expected = {
640+
(0, 0): ['width: 10em', ' height: 80%'],
641+
(1, 0): ['width: 10em', ' height: 80%',
642+
'background: linear-gradient(90deg, '
643+
'transparent 25.0%, #d65f5f 25.0%, '
644+
'#d65f5f 50.0%, transparent 50.0%)'],
645+
(0, 1): ['width: 10em', ' height: 80%',
646+
'background: linear-gradient(90deg,'
647+
'#d65f5f 25.0%, transparent 25.0%)'],
648+
(1, 1): ['width: 10em', ' height: 80%',
649+
'background: linear-gradient(90deg, '
650+
'transparent 25.0%, #d65f5f 25.0%, '
651+
'#d65f5f 100.0%, transparent 100.0%)']
652+
}
653+
assert result == expected
654+
575655
def test_bar_bad_align_raises(self):
576656
df = pd.DataFrame({'A': [-100, -60, -30, -20]})
577657
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)