Search code examples
pythonplotlyconfidence-intervalerrorbar

Drawing custom error bars when using plotly subplots


This question is closely related to an earlier one that I posted. I would like to draw confidence intervals for each bar within subplots of a figure, using the information from two columns in my data frame describing the upper and lower limit of each confidence interval. I tried to use the solution from that earlier post, but it does not seem to be applicable when one wants to use different colors and/or different rows in order to draw subplots for the figure.

For example, the following code does not produce the right confidence intervals. For instance, the CI of the 3rd bar in the second row should go from 11 to 5:

import pandas as pd
import plotly.express as px


df = pd.DataFrame(
    {"x": [0, 1, 2, 3, 0, 1, 2, 3],
     "y": [6, 10, 2, 5, 8, 9, 10, 11],
     "ci_upper": [8, 11, 2.5, 4, 9, 10, 11, 12],
     "ci_lower": [5, 9, 1.5, 3, 7, 6, 5, 10],
     "state": ['foo','foo','foo','foo','bar','bar','bar','bar'],
     "color": ['0','0','1','1','0','0','1','1']}
)


fig = px.bar(df, x="x", y="y",facet_row='state',color='color').update_traces(
    error_y={
        "type": "data",
        "symmetric": False,
        "array": df["ci_upper"] - df["y"],
        "arrayminus": df["y"] - df["ci_lower"],
    }
)


fig.update_yaxes(dtick=1)
fig.show(renderer='png')

enter image description here


Solution

    • it's the same technique but solution needs to consider it's multiple traces (4 in this example)
    • encoded in hovertemplate of each trace are the facet and color. Extract these and filter data down to appropriate rows
    • then build instruction for error bars as with simpler condition
    import pandas as pd
    import plotly.express as px
    
    
    df = pd.DataFrame(
        {
            "x": [0, 1, 2, 3, 0, 1, 2, 3],
            "y": [6, 10, 2, 5, 8, 9, 10, 11],
            "ci_upper": [8, 11, 2.5, 4, 9, 10, 11, 12],
            "ci_lower": [5, 9, 1.5, 3, 7, 6, 5, 10],
            "state": ["foo", "foo", "foo", "foo", "bar", "bar", "bar", "bar"],
            "color": ["0", "0", "1", "1", "0", "0", "1", "1"],
        }
    )
    
    
    fig = px.bar(df, x="x", y="y", facet_row="state", color="color")
    fig.update_yaxes(dtick=1)
    
    def error_facet(t):
        # filter data frame based on contents of hovertemplate
        d = df.query(
            " and ".join(
                [
                    f"{q.split('=')[0]}==\"{q.split('=')[1]}\""
                    for q in t.hovertemplate.split("<br>")[0:2]
                ]
            )
        )
        t.update(
            {
                "error_y": {
                    "type": "data",
                    "symmetric": False,
                    "array": d["ci_upper"] - d["y"],
                    "arrayminus": d["y"] - d["ci_lower"],
                }
            }
        )
    fig.for_each_trace(error_facet)
    
    
    fig
    

    enter image description here