From ca9c0d546833e25d08f080059e71e89bb153d7a2 Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Mon, 28 Nov 2016 16:22:50 +0100 Subject: [PATCH 1/2] ENH: Added more options for formats.style.bar You can now have the bar be centered on zero or midpoint value (in addition to the already existing way of having the min value at the left side of the cell) Fixed line too long `git diff upstream/master | flake8 --diff now passes` Change the tests to match new float formats. Added documentation on the new df.style.bar options for align and Colors in the documentation. Fix versionadded ENH: Added more options for formats.style.bar You can now have the bar be centered on zero or midpoint value (in addition to the already existing way of having the min value at the left side of the cell) Fixed line too long `git diff upstream/master | flake8 --diff now passes` Change the tests to match new float formats. Added documentation on the new df.style.bar options for align and Colors in the documentation. Fix versionadded Check for bad align value and raise. Wrote test for it too Added a simple example before the parametric one Added a whatsnew note Replaced 'self.assertEqual(left, rigth)' by 'assert left == right' like @TomAugspurger asked rebased --- doc/source/style.ipynb | 223 +++++++++++++------------- doc/source/whatsnew/v0.20.0.txt | 2 + pandas/io/formats/style.py | 158 ++++++++++++++++-- pandas/tests/io/formats/test_style.py | 116 +++++++++++++- 4 files changed, 376 insertions(+), 123 deletions(-) diff --git a/doc/source/style.ipynb b/doc/source/style.ipynb index 2cacbb19d81bb..427b18b988aef 100644 --- a/doc/source/style.ipynb +++ b/doc/source/style.ipynb @@ -2,7 +2,9 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "collapsed": true + }, "source": [ "# Styling\n", "\n", @@ -87,9 +89,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style" @@ -107,9 +107,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.highlight_null().render().split('\\n')[:10]" @@ -160,9 +158,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "s = df.style.applymap(color_negative_red)\n", @@ -208,9 +204,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.apply(highlight_max)" @@ -234,9 +228,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.\\\n", @@ -290,9 +282,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.apply(highlight_max, color='darkorange', axis=None)" @@ -340,9 +330,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.apply(highlight_max, subset=['B', 'C', 'D'])" @@ -358,9 +346,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.applymap(color_negative_red,\n", @@ -393,9 +379,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.format(\"{:.2%}\")" @@ -411,9 +395,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.format({'B': \"{:0<4.0f}\", 'D': '{:+.2f}'})" @@ -429,9 +411,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.format({\"B\": lambda x: \"±{:.2f}\".format(abs(x))})" @@ -454,9 +434,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.highlight_null(null_color='red')" @@ -472,9 +450,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "import seaborn as sns\n", @@ -495,9 +471,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Uses the full color range\n", @@ -507,9 +481,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Compress the color range\n", @@ -523,67 +495,128 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can include \"bar charts\" in your DataFrame." + "There's also `.highlight_min` and `.highlight_max`." ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "df.style.bar(subset=['A', 'B'], color='#d65f5f')" + "df.style.highlight_max(axis=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "There's also `.highlight_min` and `.highlight_max`." + "Use `Styler.set_properties` when the style doesn't actually depend on the values." ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "df.style.highlight_max(axis=0)" + "df.style.set_properties(**{'background-color': 'black',\n", + " 'color': 'lawngreen',\n", + " 'border-color': 'white'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Bar charts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can include \"bar charts\" in your DataFrame." ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "df.style.highlight_min(axis=0)" + "df.style.bar(subset=['A', 'B'], color='#d65f5f')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Use `Styler.set_properties` when the style doesn't actually depend on the values." + "New in version 0.20.0 is the ability to customize further the bar chart: You can now have the `df.style.bar` be centered on zero or midpoint value (in addition to the already existing way of having the min value at the left side of the cell), and you can pass a list of `[color_negative, color_positive]`.\n", + "\n", + "Here's how you can change the above with the new `align='mid'` option:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ - "df.style.set_properties(**{'background-color': 'black',\n", - " 'color': 'lawngreen',\n", - " 'border-color': 'white'})" + "df.style.bar(subset=['A', 'B'], align='mid', color=['#d65f5f', '#5fba7d'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following example aims to give a highlight of the behavior of the new align options:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "from IPython.display import HTML\n", + "\n", + "# Test series\n", + "test1 = pd.Series([-100,-60,-30,-20], name='All Negative')\n", + "test2 = pd.Series([10,20,50,100], name='All Positive')\n", + "test3 = pd.Series([-10,-5,0,90], name='Both Pos and Neg')\n", + "\n", + "head = \"\"\"\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\"\"\"\n", + "\n", + "aligns = ['left','zero','mid']\n", + "for align in aligns:\n", + " row = \"\".format(align)\n", + " for serie in [test1,test2,test3]:\n", + " s = serie.copy()\n", + " s.name=''\n", + " row += \"\".format(s.to_frame().style.bar(align=align, \n", + " color=['#d65f5f', '#5fba7d'], \n", + " width=100).render()) #testn['width']\n", + " row += ''\n", + " head += row\n", + " \n", + "head+= \"\"\"\n", + "\n", + "
AlignAll NegativeAll PositiveBoth Neg and Pos
{}{}
\"\"\"\n", + " \n", + "\n", + "HTML(head)" ] }, { @@ -603,9 +636,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df2 = -df\n", @@ -616,9 +647,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "style2 = df2.style\n", @@ -671,9 +700,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "with pd.option_context('display.precision', 2):\n", @@ -693,9 +720,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style\\\n", @@ -728,9 +753,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "df.style.set_caption('Colormaps, with a caption.')\\\n", @@ -756,9 +779,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.display import HTML\n", @@ -854,9 +875,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.html import widgets\n", @@ -892,9 +911,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "np.random.seed(25)\n", @@ -993,9 +1010,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "%mkdir templates" @@ -1012,9 +1027,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "%%file templates/myhtml.tpl\n", @@ -1065,9 +1078,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "MyStyler(df)" @@ -1083,9 +1094,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "HTML(MyStyler(df).render(table_title=\"Extending Example\"))" @@ -1101,9 +1110,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "EasyStyler = Styler.from_custom_template(\"templates\", \"myhtml.tpl\")\n", @@ -1120,9 +1127,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "with open(\"template_structure.html\") as f:\n", diff --git a/doc/source/whatsnew/v0.20.0.txt b/doc/source/whatsnew/v0.20.0.txt index 6e4756c3c5245..4a95e580af9fd 100644 --- a/doc/source/whatsnew/v0.20.0.txt +++ b/doc/source/whatsnew/v0.20.0.txt @@ -524,6 +524,8 @@ Other Enhancements - ``parallel_coordinates()`` has gained a ``sort_labels`` keyword arg that sorts class labels and the colours assigned to them (:issue:`15908`) - Options added to allow one to turn on/off using ``bottleneck`` and ``numexpr``, see :ref:`here ` (:issue:`16157`) +- ``DataFrame.style.bar()`` now accepts two more options to further customize the bar chart. Bar alignment is set with ``align='left'|'mid'|'zero'``, the default is "left", which is backward compatible; You can now pass a list of ``color=[color_negative, color_positive]``. (:issue:`14757`) + .. _ISO 8601 duration: https://en.wikipedia.org/wiki/ISO_8601#Durations diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 83062e7d764cd..5b5a90ebb279e 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -23,6 +23,7 @@ import numpy as np import pandas as pd +from pandas.api.types import is_list_like from pandas.compat import range from pandas.core.config import get_option from pandas.core.generic import _shared_docs @@ -868,39 +869,172 @@ def set_properties(self, subset=None, **kwargs): return self.applymap(f, subset=subset) @staticmethod - def _bar(s, color, width): + def _bar_left(s, color, width, base): + """ + The minimum value is aligned at the left of the cell + Parameters + ---------- + color: 2-tuple/list, of [``color_negative``, ``color_positive``] + width: float + A number between 0 or 100. The largest value will cover ``width`` + percent of the cell's width + base: str + The base css format of the cell, e.g.: + ``base = 'width: 10em; height: 80%;'`` + Returns + ------- + self : Styler + """ normed = width * (s - s.min()) / (s.max() - s.min()) - - base = 'width: 10em; height: 80%;' - attrs = (base + 'background: linear-gradient(90deg,{c} {w}%, ' + zero_normed = width * (0 - s.min()) / (s.max() - s.min()) + attrs = (base + 'background: linear-gradient(90deg,{c} {w:.1f}%, ' 'transparent 0%)') - return [attrs.format(c=color, w=x) if x != 0 else base for x in normed] - def bar(self, subset=None, axis=0, color='#d65f5f', width=100): + return [base if x == 0 else attrs.format(c=color[0], w=x) + if x < zero_normed + else attrs.format(c=color[1], w=x) if x >= zero_normed + else base for x in normed] + + @staticmethod + def _bar_center_zero(s, color, width, base): + """ + Creates a bar chart where the zero is centered in the cell + Parameters + ---------- + color: 2-tuple/list, of [``color_negative``, ``color_positive``] + width: float + A number between 0 or 100. The largest value will cover ``width`` + percent of the cell's width + base: str + The base css format of the cell, e.g.: + ``base = 'width: 10em; height: 80%;'`` + Returns + ------- + self : Styler + """ + + # Either the min or the max should reach the edge + # (50%, centered on zero) + m = max(abs(s.min()), abs(s.max())) + + normed = s * 50 * width / (100.0 * m) + + attrs_neg = (base + 'background: linear-gradient(90deg, transparent 0%' + ', transparent {w:.1f}%, {c} {w:.1f}%, ' + '{c} 50%, transparent 50%)') + + attrs_pos = (base + 'background: linear-gradient(90deg, transparent 0%' + ', transparent 50%, {c} 50%, {c} {w:.1f}%, ' + 'transparent {w:.1f}%)') + + return [attrs_pos.format(c=color[1], w=(50 + x)) if x >= 0 + else attrs_neg.format(c=color[0], w=(50 + x)) + for x in normed] + + @staticmethod + def _bar_center_mid(s, color, width, base): + """ + Creates a bar chart where the midpoint is centered in the cell + Parameters + ---------- + color: 2-tuple/list, of [``color_negative``, ``color_positive``] + width: float + A number between 0 or 100. The largest value will cover ``width`` + percent of the cell's width + base: str + The base css format of the cell, e.g.: + ``base = 'width: 10em; height: 80%;'`` + Returns + ------- + self : Styler + """ + + if s.min() >= 0: + # In this case, we place the zero at the left, and the max() should + # be at width + zero = 0.0 + slope = width / s.max() + elif s.max() <= 0: + # In this case, we place the zero at the right, and the min() + # should be at 100-width + zero = 100.0 + slope = width / -s.min() + else: + slope = width / (s.max() - s.min()) + zero = (100.0 + width) / 2.0 - slope * s.max() + + normed = zero + slope * s + + attrs_neg = (base + 'background: linear-gradient(90deg, transparent 0%' + ', transparent {w:.1f}%, {c} {w:.1f}%, ' + '{c} {zero:.1f}%, transparent {zero:.1f}%)') + + attrs_pos = (base + 'background: linear-gradient(90deg, transparent 0%' + ', transparent {zero:.1f}%, {c} {zero:.1f}%, ' + '{c} {w:.1f}%, transparent {w:.1f}%)') + + return [attrs_pos.format(c=color[1], zero=zero, w=x) if x > zero + else attrs_neg.format(c=color[0], zero=zero, w=x) + for x in normed] + + def bar(self, subset=None, align='left', axis=0, + color='#d65f5f', width=100): """ Color the background ``color`` proptional to the values in each column. Excludes non-numeric data by default. - .. versionadded:: 0.17.1 - Parameters ---------- subset: IndexSlice, default None a valid slice for ``data`` to limit the style application to axis: int - color: str + color: str or 2-tuple/list + If a str is passed, the color is the same for both + negative and positive numbers. If 2-tuple/list is used, the + first element is the color_negative and the second is the + color_positive (eg: ['#d65f5f', '#5fba7d']) width: float A number between 0 or 100. The largest value will cover ``width`` percent of the cell's width - + align : {'left', 'zero',' mid'} + .. versionadded:: 0.20.0 + - 'left' : the min value starts at the left of the cell + - 'zero' : a value of zero is located at the center of the cell + - 'mid' : the center of the cell is at (max-min)/2, or + if values are all negative (positive) the zero is aligned + at the right (left) of the cell Returns ------- self : Styler """ subset = _maybe_numeric_slice(self.data, subset) subset = _non_reducing_slice(subset) - self.apply(self._bar, subset=subset, axis=axis, color=color, - width=width) + + base = 'width: 10em; height: 80%;' + + if not(is_list_like(color)): + color = [color, color] + elif len(color) == 1: + color = [color[0], color[0]] + elif len(color) > 2: + msg = ("Must pass `color` as string or a list-like" + " of length 2: [`color_negative`, `color_positive`]\n" + "(eg: color=['#d65f5f', '#5fba7d'])") + raise ValueError(msg) + + if align == 'left': + self.apply(self._bar_left, subset=subset, axis=axis, color=color, + width=width, base=base) + elif align == 'zero': + self.apply(self._bar_center_zero, subset=subset, axis=axis, + color=color, width=width, base=base) + elif align == 'mid': + self.apply(self._bar_center_mid, subset=subset, axis=axis, + color=color, width=width, base=base) + else: + msg = ("`align` must be one of {'left', 'zero',' mid'}") + raise ValueError(msg) + return self def highlight_max(self, subset=None, color='yellow', axis=0): diff --git a/pandas/tests/io/formats/test_style.py b/pandas/tests/io/formats/test_style.py index f421c0f8e6d69..9219ac1c9c26b 100644 --- a/pandas/tests/io/formats/test_style.py +++ b/pandas/tests/io/formats/test_style.py @@ -266,7 +266,7 @@ def test_empty(self): {'props': [['', '']], 'selector': 'row1_col0'}] assert result == expected - def test_bar(self): + def test_bar_align_left(self): df = pd.DataFrame({'A': [0, 1, 2]}) result = df.style.bar()._compute().ctx expected = { @@ -299,7 +299,7 @@ def test_bar(self): result = df.style.bar(color='red', width=50)._compute().ctx assert result == expected - def test_bar_0points(self): + def test_bar_align_left_0points(self): df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) result = df.style.bar()._compute().ctx expected = {(0, 0): ['width: 10em', ' height: 80%'], @@ -349,6 +349,118 @@ def test_bar_0points(self): ', transparent 0%)']} assert result == expected + def test_bar_align_mid_pos_and_neg(self): + df = pd.DataFrame({'A': [-10, 0, 20, 90]}) + + result = df.style.bar(align='mid', color=[ + '#d65f5f', '#5fba7d'])._compute().ctx + + expected = {(0, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 0.0%, #d65f5f 0.0%, ' + '#d65f5f 10.0%, transparent 10.0%)'], + (1, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 10.0%, ' + '#d65f5f 10.0%, #d65f5f 10.0%, ' + 'transparent 10.0%)'], + (2, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 10.0%, #5fba7d 10.0%' + ', #5fba7d 30.0%, transparent 30.0%)'], + (3, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 10.0%, ' + '#5fba7d 10.0%, #5fba7d 100.0%, ' + 'transparent 100.0%)']} + + self.assertEqual(result, expected) + + def test_bar_align_mid_all_pos(self): + df = pd.DataFrame({'A': [10, 20, 50, 100]}) + + result = df.style.bar(align='mid', color=[ + '#d65f5f', '#5fba7d'])._compute().ctx + + expected = {(0, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 0.0%, #5fba7d 0.0%, ' + '#5fba7d 10.0%, transparent 10.0%)'], + (1, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 0.0%, #5fba7d 0.0%, ' + '#5fba7d 20.0%, transparent 20.0%)'], + (2, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 0.0%, #5fba7d 0.0%, ' + '#5fba7d 50.0%, transparent 50.0%)'], + (3, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 0.0%, #5fba7d 0.0%, ' + '#5fba7d 100.0%, transparent 100.0%)']} + + self.assertEqual(result, expected) + + def test_bar_align_mid_all_neg(self): + df = pd.DataFrame({'A': [-100, -60, -30, -20]}) + + result = df.style.bar(align='mid', color=[ + '#d65f5f', '#5fba7d'])._compute().ctx + + expected = {(0, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 0.0%, ' + '#d65f5f 0.0%, #d65f5f 100.0%, ' + 'transparent 100.0%)'], + (1, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 40.0%, ' + '#d65f5f 40.0%, #d65f5f 100.0%, ' + 'transparent 100.0%)'], + (2, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 70.0%, ' + '#d65f5f 70.0%, #d65f5f 100.0%, ' + 'transparent 100.0%)'], + (3, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 80.0%, ' + '#d65f5f 80.0%, #d65f5f 100.0%, ' + 'transparent 100.0%)']} + assert result == expected + + def test_bar_align_zero_pos_and_neg(self): + # See https://github.com/pandas-dev/pandas/pull/14757 + df = pd.DataFrame({'A': [-10, 0, 20, 90]}) + + result = df.style.bar(align='zero', color=[ + '#d65f5f', '#5fba7d'], width=90)._compute().ctx + + expected = {(0, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 45.0%, ' + '#d65f5f 45.0%, #d65f5f 50%, ' + 'transparent 50%)'], + (1, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 50%, ' + '#5fba7d 50%, #5fba7d 50.0%, ' + 'transparent 50.0%)'], + (2, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 50%, #5fba7d 50%, ' + '#5fba7d 60.0%, transparent 60.0%)'], + (3, 0): ['width: 10em', ' height: 80%', + 'background: linear-gradient(90deg, ' + 'transparent 0%, transparent 50%, #5fba7d 50%, ' + '#5fba7d 95.0%, transparent 95.0%)']} + assert result == expected + + def test_bar_bad_align_raises(self): + df = pd.DataFrame({'A': [-100, -60, -30, -20]}) + with pytest.raises(ValueError): + df.style.bar(align='poorly', color=['#d65f5f', '#5fba7d']) + def test_highlight_null(self, null_color='red'): df = pd.DataFrame({'A': [0, np.nan]}) result = df.style.highlight_null()._compute().ctx From c7d41fbd90300ff806e250d9a39c0b38e0ab37dd Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 1 May 2017 22:56:28 +0200 Subject: [PATCH 2/2] small doc fixes --- pandas/io/formats/style.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 5b5a90ebb279e..f1ff2966dca48 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -977,8 +977,8 @@ def _bar_center_mid(s, color, width, base): else attrs_neg.format(c=color[0], zero=zero, w=x) for x in normed] - def bar(self, subset=None, align='left', axis=0, - color='#d65f5f', width=100): + def bar(self, subset=None, axis=0, color='#d65f5f', width=100, + align='left'): """ Color the background ``color`` proptional to the values in each column. Excludes non-numeric data by default. @@ -996,13 +996,15 @@ def bar(self, subset=None, align='left', axis=0, width: float A number between 0 or 100. The largest value will cover ``width`` percent of the cell's width - align : {'left', 'zero',' mid'} - .. versionadded:: 0.20.0 + align : {'left', 'zero',' mid'}, default 'left' - 'left' : the min value starts at the left of the cell - 'zero' : a value of zero is located at the center of the cell - 'mid' : the center of the cell is at (max-min)/2, or - if values are all negative (positive) the zero is aligned - at the right (left) of the cell + if values are all negative (positive) the zero is aligned + at the right (left) of the cell + + .. versionadded:: 0.20.0 + Returns ------- self : Styler