Updating width between bars in go.Bar()

I am creating barcharts sliced by two categorical variables. The space between the bar groupings is much wider than the space from the bar groupings to the edges of the plot [fig1].

I understand it’s possible to increase the space from bar grouping to edge of plot [fig2], but is there a way to rather reduce this gap between bar groupings instead? This would provide more visual consistency between charts of a different number of groupings.

[^fig1, narrow gap]


[^fig2, wide gap]

alternatively, is there any way to manually set the bar width, regardless of how many groupings, so that there is consistency in subplots?

TIA!

I think this might be related to this:

Concerning manual bar_width:

I remember responding to quite some topics concerning bar with, a quick forum search:
https://community.plotly.com/search?q=bar_width%20%40aimped

1 Like

Got it, so it has to do with plotly creating space for bars on the back-end side of the equation. Noted.

Just looking above at my two figs it’s interesting that it doesn’t do that for “conversion”, but does for all other graphs [see fig1]. Perhaps I can supply code and you’d be willing to take a look?

TIA!

@plotmaster422 sure

Here is the code. Any workarounds for normalizing the width between bars would be greatly appreciated!


def bar_cat_overview__slice_by_categorical(df, categoricals, sliceable, dep, max_cols=4, height=700, bargap=1):

    print(f"Categorical variables are {categoricals}, dependent variable is [{dep}]")

    df = df[categoricals].merge(df[dep], how='outer', left_index=True, right_index=True)
    
    max_rows=ceil(float(len(df.columns))/max_cols)
    
    fig = make_subplots(
        rows=max_rows,
        cols=max_cols,
        subplot_titles=df.drop([sliceable], axis=1).columns).update_layout(
        {"title":f"bars of features, slice by {sliceable}"})
    
    col_cyc = cycle(iter(range(1,max_cols+1)))
    color_list = cycle(iter(px.colors.qualitative.T10[:len(df[sliceable].unique())]))
    
    row=1
    ctr=0

    for j in df.drop([sliceable], axis=1).columns:
        col = next(col_cyc)
        for attr1 in df[j].unique():
            if ctr < 1:
                for attr2 in df[sliceable].unique():
                    fig.add_bar(
                        x = [attr1],
                        y = [df[j][(df[j]==attr1) & (df[sliceable]==attr2)].count()],
                        row=row,
                        col=col,
                        marker={"color": next(color_list)},         
                        name=str(attr2),
                        showlegend=True
                        )
            else:
                for attr2 in df[sliceable].unique():
                    fig.add_bar(
                        x = [attr1],
                        y = [df[j][(df[j]==attr1) & (df[sliceable]==attr2)].count()],
                        row=row,
                        col=col,
                        marker={"color": next(color_list)},         
                        name=str(attr2),
                        showlegend=False
                        )
            ctr+=1

        if col % max_cols == 0:
            row+=1
        col+=1
    return fig.update_layout({"height": height,
                              "barcornerradius": 1,
                              "bargap": bargap,
                              "barmode":"group",
                              "legend": {"title": sliceable}
                             })
        
bar_cat_overview__slice_by_categorical(digital_og, categoricals=categoricals, sliceable="CampaignType", dep="Conversion", max_cols=3, height=800, 
                                       bargap=0.8
                                      )

could you post a reproducible example?

Here you go [apologies for the long function, subplots are dynamic based on df size]:

import plotly.express as px
import plotly.graph_objs as go
from plotly.subplots import make_subplots
from plotly.offline import *

from math import ceil
from itertools import cycle

def bar_cat_overview__slice_by_categorical(df, categoricals, sliceable, max_cols=4, height=700, bargap=1):
    
    print(f"Categorical variables are {categoricals}")

    max_rows=ceil(float(len(df.columns))/max_cols)
    
    fig = make_subplots(
        rows=max_rows,
        cols=max_cols,
        subplot_titles=df.drop([sliceable], axis=1).columns).update_layout(
        {"title":f"bars of features, slice by {sliceable}"})
    
    col_cyc = cycle(iter(range(1,max_cols+1)))
    color_list = cycle(iter(px.colors.qualitative.T10[:len(df[sliceable].unique())]))
    
    row=1
    ctr=0

    for j in df.drop([sliceable], axis=1).columns:
        col = next(col_cyc)
        for attr1 in df[j].unique():
            if ctr < 1:
                for attr2 in df[sliceable].unique():
                    fig.add_bar(
                        x = [attr1],
                        y = [df[j][(df[j]==attr1) & (df[sliceable]==attr2)].count()],
                        row=row,
                        col=col,
                        marker={"color": next(color_list)},         
                        name=str(attr2),
                        showlegend=True
                        )
            else:
                for attr2 in df[sliceable].unique():
                    fig.add_bar(
                        x = [attr1],
                        y = [df[j][(df[j]==attr1) & (df[sliceable]==attr2)].count()],
                        row=row,
                        col=col,
                        marker={"color": next(color_list)},         
                        name=str(attr2),
                        showlegend=False
                        )
            ctr+=1

        if col % max_cols == 0:
            row+=1
        col+=1
    return fig.update_layout({"height": height,
                              "barcornerradius": 1,
                              "bargap": bargap,
                              "barmode":"group",
                              "legend": {"title": sliceable}
                             })
        
fig = bar_cat_overview__slice_by_categorical(df, categoricals=categoricals, sliceable="A", max_cols=3, height=400, 
                                       bargap=0.8
                                      )
fig

copy paste this dataframe and hit go:

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "A": ["awareness", "retention", "retention", "retention", "awareness", "converison"],
    "B": ["social", "email", "referral", "notification", "PPC", "SEO"],
    "C": ["male", "female", "male", "female", "male", "female"],
    "D": ["confidential", "confidential", "confidential", "confidential", "confidential", "confidential"],
    "E" : [0, 0, 0, 0, 0, 1],

})

categoricals = ["A", "B", "C", "D"]

df

see how the widths of the bar are far too narrow? I would like the bar widths to be consistent.

TIA!

Yes, I see the problem. As I mentioned already, I do not think there is a built in way to do what you are after. Unfortunately I do not have the time to come up with a fully customized plot for your data, but maybe someone else here on the forums does :slight_smile:

no worries, these are more EA graphs than something front-end ready. Thanks for the response!

1 Like