Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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')
like image 459
Asher11 Avatar asked May 09 '26 19:05

Asher11


1 Answers

Update: This answer lead to a pull request pandas-dev/pandas#14757 that got accepted in Pandas v0.20.0. The documentation can be found on Pandas website at style.html#Bar-charts


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

like image 120
Julien Marrec Avatar answered May 12 '26 10:05

Julien Marrec



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!