Search code examples
pythonplotlyplotly-dashdashboardplotly-python

How do I split a grouped bar chart into sub-groups?


I have this dataset-

              group             sub_group    value    date
0           Animal                  Cats       12     today
1           Animal                  Dogs       32     today
2           Animal                 Goats       38     today
3           Animal                  Fish        1     today
4            Plant                  Tree       48     today
5           Object                   Car       55     today
6           Object                Garage       61     today
7           Object            Instrument       57     today
8           Animal                  Cats       44     yesterday
9           Animal                  Dogs       12     yesterday
10          Animal                 Goats       18     yesterday
11          Animal                  Fish        9     yesterday
12           Plant                  Tree        8     yesterday
13          Object                   Car       12     yesterday
14          Object                Garage       37     yesterday
15          Object            Instrument       77     yesterday

I want to have two series in a barchart. I want to have one series for today and I want to have another series for yesterday. Within each series, I want the bars to be split up by their sub-groups. For example, there would be one bar called "Animal - today" and it would sum up to 83 and, within that bar, there would be cats, dogs, etc.

I want to make a chart that is very similar to chart shown under "Bar charts with Long Format Data" on the docs, except that I have two series.

This is what I tried-

fig = make_subplots(rows = 1, cols = 1)

fig.add_trace(go.Bar(
            y = df[df['date'] == 'today']['amount'],
            x = df[df['date'] == 'today']['group'],
            color = df[df['date'] == 'today']['sub_group']
        ),
    row = 1, col = 1
)

fig.add_trace(go.Bar(
            y = df[df['date'] == 'yesterday']['amount'],
            x = df[df['date'] == 'yesterday']['group'],
            color = df[df['date'] == 'yesterday']['sub_group']
        ),
    row = 1, col = 1
)

fig.show()
 

I added a bounty because I want to be able to add the chart as a trace in my subplot.


Solution

  • I think your code will draw the intended graph except for the color settings, but if you want to separate each stacked graph by color, you will need to do some tricks. There may be another way to do this, but create two express graphs by date and reuse that data. To create that x-axis, add a column with the code that makes the group column a category. the second group needs to be shifted on the x-axis, so I add +0.5 to the x-axis. Set the array ([0.25,1.25,2.25]) and string to center the x-axis scale on the created graph. Finally, duplicate legend items are made unique.

    # group id add
    df['gr_id'] = df['group'].astype('category').cat.codes
    
    import plotly.express as px
    import plotly.graph_objects as go
    
    fig_t = px.bar(df.query('date == "today"'), x="gr_id", y="amount", color='sub_group')
    fig_y = px.bar(df.query('date == "yesterday"'), x="gr_id", y="amount", color='sub_group')
    
    fig = go.Figure()
    
    for t in fig_t.data:
        fig.add_trace(go.Bar(t))
        
    for i,y in enumerate(fig_y.data):
        y['x'] = y['x'] + 0.5
        fig.add_trace(go.Bar(y))
    
    fig.update_layout(bargap=0.05, barmode='stack')
    fig.update_xaxes(tickvals=[0.25,1.25,2.25], ticktext=['Animal','Plant','Pbject'])
    
    names = set()
    fig.for_each_trace(
        lambda trace:
            trace.update(showlegend=False)
            if (trace.name in names) else names.add(trace.name))
    
    fig.update_layout(legend_tracegroupgap=0)
    fig.show()
    

    enter image description here

    EDIT: This is achieved by using the pre-loaded subplotting functionality built into express.

    import plotly.express as px
    
    px.bar(df, x='group', y='amount', color='sub_group', facet_col='date')
    

    enter image description here