Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python dash table conditional formatting color scale

I want to color a column by value with color scale according to value high to small like this

https://i.imgur.com/GZpIXu1.png

currently, I create the dash table in a function and send it by loop for each column;

def make_table_in_div(df, column_name):
    pv = pd.pivot_table(df, index=[column_name], values=['val1'], aggfunc=['mean', 'count']).reset_index()
    pv.columns = [column_name, 'val1', 'count']
    print(column_name)
    div = html.Div([html.H1(column_name), dash_table.DataTable(
        columns=[{"name": i, "id": i} for i in pv.columns],
        data=pv.to_dict('records'),
    )], style={'height': 30, 'margin-right': 'auto', 'margin-left': 'auto', 'width': '800px'})  # 'width': '50%',
    return div

div = [make_table_in_div(df, column_name) for column_name in ['column_name']]
return div

the dash table looks as the flowing picture, and I want to colorize the value column enter image description here

like image 332
Ran Cohen Avatar asked Aug 12 '20 08:08

Ran Cohen


2 Answers

Thanks to The answer of Kristian Haga. - It's working great.

I'd like to summarize the options for future users with the same question and me. When we want to run it on multiple columns, there are two options:

  1. The original function will colorize all the columns with same scale (min and max) so if I run with multiple columns (from the example: values and count) its returns the table style colorized based on the range of min and max out of all columns (from the example: 0.193,109).
    discrete_background_color_bins(df, columns=['value','count']) https://i.imgur.com/bDrd8q1.png

    def discrete_background_color_bins(df, n_bins=7, columns='all'):
    
     bounds = [i * (1.0 / n_bins) for i in range(n_bins+1)]
     if columns == 'all':
         if 'id' in df:
             df_numeric_columns = df.select_dtypes('number').drop(['id'], axis=1)
         else:
             df_numeric_columns = df.select_dtypes('number')
     else:
         df_numeric_columns = df[columns]
     df_max = df_numeric_columns.max().max()
     df_min = df_numeric_columns.min().min()
     ranges = [
         ((df_max - df_min) * i) + df_min
         for i in bounds
     ]
     styles = []
     legend = []
     for i in range(1, len(bounds)):
         min_bound = ranges[i - 1]
         max_bound = ranges[i]
         backgroundColor = colorlover.scales[str(n_bins+4)]['div']['RdYlGn'][2:-2][i - 1]
         color = 'black'
    
         for column in df_numeric_columns:
             styles.append({
                 'if': {
                     'filter_query': (
                         '{{{column}}} >= {min_bound}' +
                         (' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
                     ).format(column=column, min_bound=min_bound, max_bound=max_bound),
                     'column_id': column
                 },
                 'backgroundColor': backgroundColor,
                 'color': color
             })
         legend.append(
             html.Div(style={'display': 'inline-block', 'width': '60px'}, children=[
                 html.Div(
                     style={
                         'backgroundColor': backgroundColor,
                         'borderLeft': '1px rgb(50, 50, 50) solid',
                         'height': '10px'
                     }
                 ),
                 html.Small(round(min_bound, 2), style={'paddingLeft': '2px'})
             ])
         )
    
     return (styles, html.Div(legend, style={'padding': '5px 0 5px 0'}))
    
  2. If we want to colorize each column separately based on its min and max, enter image description here We will use the function below:
    (very similar but running on the columns first)

     def discrete_background_color_bins(df, n_bins=7, columns='all'):
    
         bounds = [i * (1.0 / n_bins) for i in range(n_bins+1)]
         if columns == 'all':
             if 'id' in df:
                 df_numeric_columns = df.select_dtypes('number').drop(['id'], axis=1)
             else:
                 df_numeric_columns = df.select_dtypes('number')
         else:
             df_numeric_columns = df[columns]
         df_max = df_numeric_columns.max().max()
         df_min = df_numeric_columns.min().min()
         ranges = [
             ((df_max - df_min) * i) + df_min
             for i in bounds
         ]
         styles = []
         legend = []
         for i in range(1, len(bounds)):
             min_bound = ranges[i - 1]
             max_bound = ranges[i]
             backgroundColor = colorlover.scales[str(n_bins+4)]['div']['RdYlGn'][2:-2][i - 1]
             color = 'black'
    
             for column in df_numeric_columns:
                 styles.append({
                     'if': {
                         'filter_query': (
                             '{{{column}}} >= {min_bound}' +
                             (' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
                         ).format(column=column, min_bound=min_bound, max_bound=max_bound),
                         'column_id': column
                     },
                     'backgroundColor': backgroundColor,
                     'color': color
                 })
             legend.append(
                 html.Div(style={'display': 'inline-block', 'width': '60px'}, children=[
                     html.Div(
                         style={
                             'backgroundColor': backgroundColor,
                             'borderLeft': '1px rgb(50, 50, 50) solid',
                             'height': '10px'
                         }
                     ),
                     html.Small(round(min_bound, 2), style={'paddingLeft': '2px'})
                 ])
             )
    
         return (styles, html.Div(legend, style={'padding': '5px 0 5px 0'}))
    
like image 80
Ran Cohen Avatar answered Oct 18 '22 15:10

Ran Cohen


This is possible. You should check out this link: https://dash.plotly.com/datatable/conditional-formatting specifically the part 'Highlighting with a colorscale on a single column'

I have written a quick example for you:

import dash
import dash_table
import pandas as pd
import dash_html_components as html
import colorlover
from jupyter_dash import JupyterDash

# Dash Application
df = pd.DataFrame(list(zip(
    [5,6,7,8,9,10,11,12,13,14],
    [0.328, 0.323, 0.193, 0.231, 0.216, 0.284, 0.250, 0.258, 0.394, 0.455],
    [67, 99, 109, 104, 88, 74, 32, 31, 33, 22]
)), columns=['column_name', 'value', 'count'])

app = JupyterDash(__name__)

# Function for styling table, defined below
cols = ['value']
(styles, legend) = discrete_background_color_bins(df, columns = cols)

app.layout = html.Div([
    legend,
    dash_table.DataTable(
        id = 'table',
        columns = [{"name": i, "id": i} for i in df.columns],
        data = df.to_dict('records'),
        style_data_conditional = styles
    )
])

app.run_server(mode='inline')

enter image description here

This function returns the a list of styles for each row for the specified columns using the given colorscale.

To get the colorscale you need to install colorlover with pip install colorlover

Additional colorscales can be found here: https://github.com/plotly/colorlover

# Function for styling the table
def discrete_background_color_bins(df, n_bins=7, columns='all'):

    bounds = [i * (1.0 / n_bins) for i in range(n_bins+1)]
    if columns == 'all':
        if 'id' in df:
            df_numeric_columns = df.select_dtypes('number').drop(['id'], axis=1)
        else:
            df_numeric_columns = df.select_dtypes('number')
    else:
        df_numeric_columns = df[columns]
    df_max = df_numeric_columns.max().max()
    df_min = df_numeric_columns.min().min()
    ranges = [
        ((df_max - df_min) * i) + df_min
        for i in bounds
    ]
    styles = []
    legend = []
    for i in range(1, len(bounds)):
        min_bound = ranges[i - 1]
        max_bound = ranges[i]
        backgroundColor = colorlover.scales[str(n_bins+4)]['div']['RdYlGn'][2:-2][i - 1]
        color = 'black'

        for column in df_numeric_columns:
            styles.append({
                'if': {
                    'filter_query': (
                        '{{{column}}} >= {min_bound}' +
                        (' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
                    ).format(column=column, min_bound=min_bound, max_bound=max_bound),
                    'column_id': column
                },
                'backgroundColor': backgroundColor,
                'color': color
            })
        legend.append(
            html.Div(style={'display': 'inline-block', 'width': '60px'}, children=[
                html.Div(
                    style={
                        'backgroundColor': backgroundColor,
                        'borderLeft': '1px rgb(50, 50, 50) solid',
                        'height': '10px'
                    }
                ),
                html.Small(round(min_bound, 2), style={'paddingLeft': '2px'})
            ])
        )

    return (styles, html.Div(legend, style={'padding': '5px 0 5px 0'}))
like image 29
Kristian Haga Avatar answered Oct 18 '22 15:10

Kristian Haga