Multiple Boxplots with corresponding Line Charts (using subplots)

Hi all - my goal is to generate multiple boxplots based on parameters a user selects using Dash and Plotly.

I have the initial part down of creating boxplots dynamically, and adding a new subplot based on the primary dimension, and within each subplot a new Box based on the secondary dimension. My final goal is to overlay a simple line over each box representing a reference point (which will also be configurable).

Currently, my approach is to generate alternate X-axis and use go.Scatter() to generate lines (i’m referencing both of these posts):

My goal is very similar - do the same thing but on a group of subplots. My solution is very close, but only works for the first Subplot, and doesn’t work for the remaining 2 subplots… it blocks out the Box and only displays the line! If I remove, the boxplots show just fine.

  fig = make_subplots(rows=1, cols=len(p_data), subplot_titles=p_data)
for s in range(0,len(p_data)):

        for w in range(0,len(s_data)):
            y0 = df1[(df1[primary_config] == p_data[s]) & (df1[secondary_config] == s_data[w])]['Avg $/mt']
            x0 = df_s[(df_s[primary_config] == p_data[s]) & (df_s[secondary_config] == s_data[w])]['Avg $/mt']
            fig.add_trace(go.Box(xaxis='x1', y=y0, name=s_data[w],line=dict(color=colors[s]), fillcolor=colors[s]), row=1, col=s+1)
            # fig.add_trace(go.Scatter(x=[s_data[w]]*len(x0), y=x0, name=s_data[w], mode='markers', marker=dict(symbol='diamond', color='black')), row=1, col=s+1)
            

    fig.update_layout(showlegend=False)
    fig.layout.xaxis4 = go.layout.XAxis(range=[0, 4], overlaying='x', showticklabels=False)
    fig.add_trace(go.Scatter(x = [.25, .75], y = [800, 800], mode='lines', xaxis='x4', showlegend=False))


    fig.layout.xaxis5 = go.layout.XAxis(range=[0, 4], overlaying='x2', showticklabels=False)
    fig.add_trace(go.Scatter(x = [.25, .75], y = [700, 700], mode='lines', xaxis='x5', showlegend=False))
    
    fig.layout.xaxis6 = go.layout.XAxis(range=[0, 4], overlaying='x3', showticklabels=False)
    fig.add_trace(go.Scatter(x = [.25, .75], y = [700, 700], mode='lines', xaxis='x6', showlegend=False))

Hi @cmurph255 ! Thank you for including such a detailed explanation of what you want, the screenshots really help. However, without data is hard to replicate the results of the code you provided.

I think the problem is in the creation of x1, x2 and x3 axis. If you look at your code in detail, you will notice that inside the second loop you only create x axes named x1, and no axes named x2 or x3.

for w in range(0,len(s_data)):
            y0 = df1[(df1[primary_config] == p_data[s]) & (df1[secondary_config] == s_data[w])]['Avg $/mt']
            x0 = df_s[(df_s[primary_config] == p_data[s]) & (df_s[secondary_config] == s_data[w])]['Avg $/mt']
           # The name of xaxis is always 'x1'
            fig.add_trace(go.Box(xaxis='x1', y=y0, name=s_data[w],line=dict(color=colors[s]), fillcolor=colors[s]), row=1, col=s+1)
           

I think it should be:

fig.add_trace(go.Box(xaxis='x' + str(w+1), y=y0, name=s_data[w],line=dict(color=colors[s]), fillcolor=colors[s]), row=1, col=s+1)

In case that doesn’t work, there’s an alternative way. Your case is different from the post examples because you have subplots and not only one grouped plot. In that case, you could do something similar to the Adding Traces To Subplots example of the docs.

Instead of:

    fig.layout.xaxis4 = go.layout.XAxis(range=[0, 4], overlaying='x', showticklabels=False)
    fig.add_trace(go.Scatter(x = [.25, .75], y = [800, 800], mode='lines', xaxis='x4', showlegend=False))


    fig.layout.xaxis5 = go.layout.XAxis(range=[0, 4], overlaying='x2', showticklabels=False)
    fig.add_trace(go.Scatter(x = [.25, .75], y = [700, 700], mode='lines', xaxis='x5', showlegend=False))
    
    fig.layout.xaxis6 = go.layout.XAxis(range=[0, 4], overlaying='x3', showticklabels=False)
    fig.add_trace(go.Scatter(x = [.25, .75], y = [700, 700], mode='lines', xaxis='x6', showlegend=False))

You could try:

fig.add_trace(
    go.Scatter(x = [.25, .75], y = [800, 800], mode='lines', showlegend=False),
    row=1, col=1
    )
fig.add_trace(
    go.Scatter(x = [.25, .75], y = [700, 700], mode='lines', showlegend=False),
    row=1, col=2
    )
fig.add_trace(
    go.Scatter(x = [.25, .75], y = [700, 700], mode='lines', showlegend=False),
    row=1, col=3
    )

I hope this helps! :four_leaf_clover:

1 Like

Hi Celia, thank you for the quick response!! I’ve tried both suggestions, unfortunately no luck.

With the second suggestion, I think the issue is it’s not recognizing the secondary Xaxis, and just plotting them under new x-values 0.25 and 0.75. Is there a way to force it to align over “weeks”? (I’ve used this method successfully in Matplotlib) :

The first suggestion makes total sense as all 3 original plots shared the same, however I still get the same results where only the first subplot shows both Box and Line correctly, while the 2nd and 3rd only show the Line. I think the 2nd and 3rd Box plots are still there, but are just being hidden by the new Line? Is there a way to make the Line have a transparent plot background, which maybe allows the Box plots to show?

Hi! I’m not sure I can help you more without a MRE. Could you share one?

Hi Celia - I’ve had some success explicitly defining the layout and axis… It seems to work!

layout_f = {}
    for s in range(0,len(p_data)):
        for w in range(0,len(s_data)):
            y0 = df1[(df1[primary_config] == p_data[s]) & (df1[secondary_config] == s_data[w])]['Avg $/mt']
            x0 = df_s[(df_s[primary_config] == p_data[s]) & (df_s[secondary_config] == s_data[w])]['Avg $/mt']
            # fig.add_trace(go.Box(xaxis='x' + str(w+1), y=y0, name=s_data[w],line=dict(color=colors[s]), fillcolor=colors[s]), row=1, col=s+1)
            fig.add_trace(go.Box(xaxis='x' + str(w+1), yaxis='y' + str(w+1), y=y0, name=s_data[w],line=dict(color=colors[s]), fillcolor=colors[s]), row=1, col=s+1)
            # fig.add_trace(go.Scatter(x=[s_data[w]]*len(x0), y=x0, name=s_data[w], mode='markers', marker=dict(symbol='diamond', color='black')), row=1, col=s+1)
        
        layout_f['xaxis'+ str(s+4)] = {
                'range': [0,4],
                'overlaying':'x' + str(s+1),
                'showticklabels':False
            }

        fig.add_trace(go.Scatter(x = [.25, .75], y = [800, 800], mode='lines', xaxis='x' + str(s+4), yaxis='y' + str(s+1), showlegend=False))

    fig.layout.update(layout_f)
    fig.update_layout(showlegend=False)