Asher11 Asher11 - 12 days ago 4
CSS Question

Conditional bipolar inside table bar chart - pandas - css

for my html representation of a pandas dataframe (python), I'd like to create something that resembles as much as possible the image below (created with Excel), that is, given a sequence of numbers, create INSIDE A TABLE, some horizontal bar charts that become green if the value is greater than zero, red if they are below zero and with the "zero" point in the axis which dynamically rescales according to the data provided. the closest I got to was by using pandas native instruments is given by the code below (http://pandas.pydata.org/pandas-docs/stable/style.html)
enter image description here

#done in Jupyter
from pandas import DataFrame

df = pd.DataFrame([-0.02, 0.03, 0.04, -0.05], columns=['A'])

more_than_zero = df.loc[df.loc[:,'A'] >= 0].index.values.tolist()
less_than_zero = df.loc[df.loc[:,'A'] < 0].index.values.tolist()

df.style.bar(subset=pd.IndexSlice[more_than_zero,'A'], color='#d65f5f')
#df.style.bar(subset=pd.IndexSlice[less_than_zero, 'B'], color='#7fff00')

Answer

There's no way to do this out of the box using pandas right now (I'll try to implement that soon), but for now here's a monkey-patching solution:

def _bar_center_zero(self, s, color_positive, color_negative, width):

    # 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 * m)

    base = 'width: 10em; height: 80%;'

    attrs_neg = (base+ 'background: linear-gradient(90deg, transparent 0%, transparent {w}%, {c} {w}%, '
                 '{c} 50%, transparent 50%)')

    attrs_pos = (base+ 'background: linear-gradient(90deg, transparent 0%, transparent 50%, {c} 50%, {c} {w}%, '
                 'transparent {w}%)')

    return [attrs_pos.format(c=color_positive,  w=(50+x)) if x > 0
                else attrs_neg.format(c=color_negative, w=(50+x)) 
                    for x in normed]

def bar_excel(self, subset=None, axis=0, color_positive='#5FBA7D', 
                  color_negative='#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_positive: str
    color_negative: str
    width: float
        A number between 0 or 100. The largest value will cover ``width``
        percent of the cell's width
    Returns
    -------
    self : Styler
    """
    #subset = _maybe_numeric_slice(self.data, subset)
    #subset = _non_reducing_slice(subset)

    self.apply(self._bar_center_zero, axis=axis, subset=subset, 
               color_positive=color_positive, color_negative=color_negative,
               width=width)
    return self

Monkey-patch this to the Styler class:

pd.formats.style.Styler._bar_center_zero = _bar_center_zero
pd.formats.style.Styler.bar_excel = bar_excel

Now you can use it:

df = pd.DataFrame([-0.02, 0.03, 0.04, -0.05], columns=['A'])

df.style.bar_excel(color_positive='#5FBA7D', color_negative='#d65f5f')

Pandas bar chart sparkline like Excel


Update:

I since then created a pull request on GitHub where I implemented this as well as the option of centering 'dynamically' (align='mid').

The resulting bar you can get by specifying options align and depending on the type of your data are represented below:

enter image description here