Black Lives Matter. Please consider donating to Black Girls Code today.
Dash HoloViews is now available! Check out the docs.

Conditional formatting of scatterplot markers

Problem:
I have a data set with x and y value pairs, plus lower_limit and upper_limit values for y.

I want to plot x vs. y in a plot.ly scatter plot, and colour the marker in green if lower_limityupper_limit, else in red.

I know that I could use 2 traces, or add a color column in the DataFrame. However, I’d like to generate these colour on the fly and use one trace only.

Example:
Consider this data set:

   x   y  lower_limit  upper_limit
0  1  13           10           15
1  2  13           15           20
2  3  17           15           20

The first marker (x=1, y=13) should be green, because lower_limityupper_limit (10 ≤ 13 ≤ 15), just like the third one.
However the second should be red, because y < lower_limit.

I then want to produce this graph:
enter image description here


MWE:

import pandas as pd
import plotly.graph_objs as go
import plotly.plotly as py
import plotly.offline as po

data = [
    [1, 13, 10, 15],
    [2, 13, 15, 20],
    [3, 17, 15, 20]
]

df = pd.DataFrame(
    data,
    columns=['x', 'y', 'lower_limit', 'upper_limit']
)

trace = go.Scatter(
    x=df['x'],
    y=df['y'],
    mode='markers',
    marker=dict(
        size=42,
        # I want the color to be green if 
        # lower_limit ≤ y ≤ upper_limit
        # else red
        color='green',
    )
)

po.plot([trace])

Hi @ebosi,

Here’s an approach using pandas to compute the inequalities and a colorscale to specify the colors.

import pandas as pd
import plotly.graph_objs as go
import plotly.plotly as py
import plotly.offline as po

data = [
    [1, 13, 10, 15],
    [2, 13, 15, 20],
    [3, 17, 15, 20]
]

df = pd.DataFrame(
    data,
    columns=['x', 'y', 'lower_limit', 'upper_limit']
)


trace = go.Scatter(
    x=df['x'],
    y=df['y'],
    mode='markers',
    marker=dict(
        size=42,
        # I want the color to be green if 
        # lower_limit ≤ y ≤ upper_limit
        # else red
        color=(
            (df.lower_limit < df.y) &
            (df.y < df.upper_limit)
        ).astype('int'),
        colorscale=[[0, 'red'], [1, 'green']]
    )
)

po.plot([trace])

Hope that helps!
-Jon

2 Likes

Sound brilliant, thank you!
(btw, you might want to add your answer on stackoverflow where I’ve also asked for advice)

if I wanted the color to be green if lower_limit ≤ y ≤ upper_limit/3 and yellow if upper_limit/3 ≤ y ≤ upper_limit/2 and red if upper_limit/2≤ y ≤ upper_limit. How would I achieve that?

Hi @Amir22 ,
The colorscale argument in dict for marker’s formatting (as in @jmmease 's code) requires values only the ordered range from 0 to 1. So you will need to mention your desired colors only in this range.

Below is my sample code:

import pandas as pd
import plotly.graph_objects as go

fig = go.Figure()

x_vals = pd.Series([1, 2, 3, 4, 5])
y_vals = pd.Series([3, 4, 5, 6, 7])

fig.add_trace(go.Scattergl(
    x=x_vals,
    y=y_vals,
    marker=dict(
        color=pd.Series(np.where(y_vals>4, 'alert',
                                 np.where(y_vals>3,
                                          'warning', 'normal'))
                       ).astype(str).map(
                                        {'alert':1, 'warning':0.5, 'normal':0}),
        colorscale=[[0, 'green'], [0.5, 'yellow'], [1, 'red']]
    )
))

fig