Applying a custom color scale with Plotly Boxplots

Shahnewaz Khan
Jul 23, 2018 · 3 min read

I was recently asked by my boss to apply a color scale to a series of Plotly Box plots I was working on for a Dash application. The notion was to plot multiple box plots on the same graph and have each box plot be darker or lighter based on it’s distance from the overall median in the dataset.

The problem

The issue here is that Plotly only supports color scales for heatmaps, scatter plots & contour plots.

I will demonstrate the default behavior by plotting some generated data for Canucks players +/- per game all time as follows:

Note: Complete code at the end of the post

The dashed green line represents the NHL league average +/- per game at +2.0, what my boss wanted was to color each box plot darker or lighter based on how far away each players median +/- value was from the league average .

The solution

Given that I was already in a Python environment, I chose to use the colormap & colormap normalizer features from the Matplotlib Python module.

First step was to import Matplotlib:

import matplotlib.pyplot as plt
import matplotlib
import pandas as pd

Then determine the minand maxvalues in the dataset:

x_data = ['Henrik Sedin', 'Daniel Sedin',
'Brock Boeser', 'Elias Pettersen',
'Bo Horvat', 'Pavel Bure',]
# Generate random data
y0 = np.random.randn(50)+4
y1 = np.random.randn(50)+4
y2 = np.random.randn(50)+2
y3 = np.random.randn(50)+2
y4 = np.random.randn(50)+1
y5 = np.random.randn(50)+3
y_data = [y0,y1,y2,y3,y4,y5]df = pd.DataFrame(y_data)
vmin, vmax = df.min().min(), df.max().max()

Initialize the Matplotlib cmap & normalizer:

norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
cmap = matplotlib.cm.get_cmap('GnBu') # green to blue color way

Find the median of each y_data and use the normalizer to map it to an appropriate color based on the median value:

for xd, yd in zip(x_data, y_data):

median = np.median(yd) # find the median
color = 'rgb' + str(cmap(norm(median))[0:3]) # normalize

traces.append(go.Box(
y=yd,
name=xd,
boxpoints='all',
jitter=0.5,
whiskerwidth=0.2,
fillcolor=color, # add the box plot color
marker=dict(
size=2,
),
line=dict(width=1)
))

VIOLA!

The resulting plot will render a darker fillcolorthe further positive the players median +/- is from the league average and vice versa.

BONUS

Here is the complete code to get the result above:

from plotly import __version__
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
init_notebook_mode(connected=True)
import plotly.graph_objs as go
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
x_data = ['Henrik Sedin', 'Daniel Sedin',
'Brock Boeser', 'Elias Pettersen',
'Bo Horvat', 'Pavel Bure',]
y0 = np.random.randn(50)+4
y1 = np.random.randn(50)+4
y2 = np.random.randn(50)+2
y3 = np.random.randn(50)+2
y4 = np.random.randn(50)+1
y5 = np.random.randn(50)+3
y_data = [y0,y1,y2,y3,y4,y5]traces = []df = pd.DataFrame(y_data)
vmin, vmax = df.min().min(), df.max().max()
norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax)
cmap = matplotlib.cm.get_cmap('GnBu')
for xd, yd in zip(x_data, y_data):
median = np.median(yd)
color = 'rgb' + str(cmap(norm(median))[0:3])

traces.append(go.Box(
y=yd,
name=xd,
boxpoints='all',
jitter=0.5,
whiskerwidth=0.2,
fillcolor=color,
marker=dict(
size=2,
color='rgb(0, 0, 0)'
),
line=dict(width=1),
))
layout = go.Layout(
title='Canucks all time +/- per game',
yaxis=dict(
autorange=True,
showgrid=True,
zeroline=True,
dtick=5,
gridcolor='rgb(255, 255, 255)',
gridwidth=1,
zerolinecolor='rgb(255, 255, 255)',
zerolinewidth=2,
),
margin=dict(
l=40,
r=30,
b=80,
t=100,
),
paper_bgcolor='rgb(243, 243, 243)',
plot_bgcolor='rgb(243, 243, 243)',
showlegend=False,
shapes= [{
'type': 'line',
'x0': -1,
'y0': 2,
'x1': 6,
'y1': 2,
'line': {
'color': 'rgb(50, 171, 96)',
'width': 4,
'dash': 'dashdot'
}
}]
)
fig = go.Figure(data=traces, layout=layout)
iplot(fig)

Shahnewaz Khan

Written by

Data Science enthusiast & cloud infrastructure integration expert with a keen interest in all things information!

More From Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade