Skip to content

Commit 2694732

Browse files
committed
ENH: Styler.bar: add support for vmin/vmax #21526
1 parent 926f218 commit 2694732

File tree

3 files changed

+96
-7
lines changed

3 files changed

+96
-7
lines changed

doc/source/whatsnew/v0.24.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ Other
428428
- :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`)
429429
- Require at least 0.28.2 version of ``cython`` to support read-only memoryviews (:issue:`21688`)
430430
- :meth: `~pandas.io.formats.style.Styler.background_gradient` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None`` (:issue:`15204`)
431-
- :meth: `~pandas.io.formats.style.Styler.bar` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None``
431+
- :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`)
432432
-
433433
-
434434
-

pandas/io/formats/style.py

+15-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
@@ -1064,6 +1064,15 @@ def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
10641064
at the right (left) of the cell
10651065
10661066
.. versionadded:: 0.20.0
1067+
vmin: float, optional
1068+
minimum bar value, defining the left hand limit
1069+
of the bar drawing range, lower values are clipped to ``vmin``.
1070+
When None (default): the minimum value of the data will be used.
1071+
vmax: float, optional
1072+
maximum bar value, defining the right hand limit
1073+
of the bar drawing range, higher values are clipped to ``vmax``.
1074+
When None (default): the maximum value of the data will be used.
1075+
10671076
10681077
Returns
10691078
-------
@@ -1084,7 +1093,7 @@ def bar(self, subset=None, axis=0, color='#d65f5f', width=100,
10841093
subset = _maybe_numeric_slice(self.data, subset)
10851094
subset = _non_reducing_slice(subset)
10861095
self.apply(self._bar, subset=subset, axis=axis,
1087-
align=align, colors=color, width=width)
1096+
align=align, colors=color, width=width, vmin=vmin, vmax=vmax)
10881097

10891098
return self
10901099

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)