Skip to content

Commit 6b4e2c2

Browse files
committed
ENH: Styler.bar: add support for vmin/vmax #21526
1 parent 9f7e99a commit 6b4e2c2

File tree

3 files changed

+105
-9
lines changed

3 files changed

+105
-9
lines changed

doc/source/whatsnew/v0.24.0.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -725,10 +725,10 @@ Build Changes
725725
Other
726726
^^^^^
727727

728-
- :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`)
728+
- :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`)
729729
- Require at least 0.28.2 version of ``cython`` to support read-only memoryviews (:issue:`21688`)
730-
- :meth: `~pandas.io.formats.style.Styler.background_gradient` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None`` (:issue:`15204`)
731-
- :meth: `~pandas.io.formats.style.Styler.bar` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None``
730+
- :meth:`~pandas.io.formats.style.Styler.background_gradient` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None`` (:issue:`15204`)
731+
- :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`)
732732
-
733733
-
734734
-

pandas/io/formats/style.py

+22-6
Original file line numberDiff line numberDiff line change
@@ -993,12 +993,12 @@ def set_properties(self, subset=None, **kwargs):
993993
return self.applymap(f, subset=subset)
994994

995995
@staticmethod
996-
def _bar(s, align, colors, width=100):
996+
def _bar(s, align, colors, width=100, vmin=None, vmax=None):
997997
"""Draw bar chart in dataframe cells"""
998998

999999
# Get input value range.
1000-
smin = s.values.min()
1001-
smax = s.values.max()
1000+
smin = s.values.min() if vmin is None else vmin
1001+
smax = s.values.max() if vmax is None else vmax
10021002
if align == 'mid':
10031003
smin = min(0, smin)
10041004
smax = max(0, smax)
@@ -1020,7 +1020,7 @@ def css_bar(start, end, color):
10201020
s=start, c=color
10211021
)
10221022
css += '{c} {e:.1f}%, transparent {e:.1f}%)'.format(
1023-
e=end, c=color,
1023+
e=min(end, width), c=color,
10241024
)
10251025
return css
10261026

@@ -1039,7 +1039,7 @@ def css(x):
10391039
)
10401040

10411041
def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
1042-
align='left'):
1042+
align='left', vmin=None, vmax=None):
10431043
"""
10441044
Draw bar chart in the cell backgrounds.
10451045
@@ -1065,6 +1065,21 @@ def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
10651065
10661066
.. versionadded:: 0.20.0
10671067
1068+
vmin: float, optional
1069+
minimum bar value, defining the left hand limit
1070+
of the bar drawing range, lower values are clipped to ``vmin``.
1071+
When None (default): the minimum value of the data will be used.
1072+
1073+
.. versionadded:: 0.24.0
1074+
1075+
vmax: float, optional
1076+
maximum bar value, defining the right hand limit
1077+
of the bar drawing range, higher values are clipped to ``vmax``.
1078+
When None (default): the maximum value of the data will be used.
1079+
1080+
.. versionadded:: 0.24.0
1081+
1082+
10681083
Returns
10691084
-------
10701085
self : Styler
@@ -1084,7 +1099,8 @@ def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
10841099
subset = _maybe_numeric_slice(self.data, subset)
10851100
subset = _non_reducing_slice(subset)
10861101
self.apply(self._bar, subset=subset, axis=axis,
1087-
align=align, colors=color, width=width)
1102+
align=align, colors=color, width=width,
1103+
vmin=vmin, vmax=vmax)
10881104

10891105
return self
10901106

pandas/tests/io/formats/test_style.py

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

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

0 commit comments

Comments
 (0)