How to show axis labels of all subplots when the labels are strings?

Problem summary

Whenever I try to create a plot with plotly express 5.18.0 containing a subplot and axes labels that are not numbers, I only get labels for the first subplot subsequent subplots show empty axis labels.

How can I ensure that all subplots show their respective axes labels, even if they contain strings?

Example data

N = 100
food = ["Dim sum", "Noodles", "Burger", "Pizza", "Pancake"]
drink = ["Beer", "Wine", "Soda", "Water", "Fruit juice", "Coffee", "Tea"]
df = pd.DataFrame(
    {
        "age": np.random.randint(8, 99, N),
        "favourite_food": np.random.choice(food, N, replace=True),
        "favourite_drink": np.random.choice(drink, N, replace=True),
        "max_running_speed": np.random.random(N)*20,
        "number_of_bicycles": np.random.randint(0, 5, N)
    }
)
df.age.replace({range(0, 19): "Kid", range(19, 100): "Adult"}, inplace=True)

Random 5 rows:

age favourite_food favourite_drink max_running_speed number_of_bicycles
0 Adult Dim sum Wine 8.57536 2
65 Kid Pizza Water 9.45698 1
57 Kid Pancake Beer 11.1445 0
84 Adult Dim sum Soda 8.80699 0
45 Adult Pizza Fruit juice 17.7258 4

Demonstration of problem

If I now create a figure with two subplots:

  • First subplot contains the distribution of the max. running speed (a number)
  • Second subplot contains the distribution of the number of bicycles (a number)

For convenience I use the facet_col argument in combination with the wide-form support of plotly express and the formatting updates I found in this related Q&A):

px.histogram(
    df,
    x=["max_running_speed", "number_of_bicycles"],
    facet_col="variable",
    color="age",
    barmode="group",
    histnorm="percent",
    text_auto=".2r",
).update_xaxes(matches=None, showticklabels=True).update_yaxes(matches=None, showticklabels=True)

enter image description here

All works as it should :white_check_mark:: I get separate ranges x- and y-axes and I get separate labels on the x-axes.

Now I do the same, but for the columns with text data:

px.histogram(
    df,
    x=["favourite_food", "favourite_drink"],
    facet_col="variable",
    color="age",
    barmode="group",
    histnorm="percent",
    text_auto=".2r",
).update_xaxes(matches=None, showticklabels=True).update_yaxes(matches=None, showticklabels=True)

enter image description here

Now thereโ€™s a problem :x:: The x-axis of the right plot does not show the names of the favourite drinks.

What Iโ€™ve tried

I checked the underlying data JSON object, as I noticed that when I hover over the bars of the right plot, the โ€œvalueโ€ field is empty:

enter image description here

But when I inspect the JSON object in the .data key of the figure, I see that x-values are present for both histograms:

enter image description here

HI @ba_tno, plotly.express uses pandas under the hood to create groups and other stuff. The plot depends therefore directly on how your dataframe looks like. This might be the issue here.

Iโ€™m not sure how exactly the dataframe should look like to achieve your objective , though. I fear, there is no โ€œmagicโ€ trick to generate this.

You could use plotly.graph_objects in combination with subplots and do the grouping in pandas manually.

Thanks for the suggestion, Iโ€™ve tried the manual way with graph_objects: it works, but is rather labour intensive (compared to the plotly.express approach)

from plotly.subplots import make_subplots
import plotly.graph_objects as go

# Set the index for age for easier selection
df.set_index("age", inplace=True)
# Create subplot
fig = make_subplots(rows=1, cols=2, subplot_titles=["Food", "Drink"])
# Build up with individual histograms
fig.add_trace(
    go.Histogram(
        histfunc="count",
        histnorm="percent",
        x=df.loc["Adult"].favourite_food,
        name="Adult",
        legendgroup="Adult",
    ),
    row=1,
    col=1,
)
fig.add_trace(
    go.Histogram(
        histfunc="count", histnorm="percent", x=df.loc["Kid"].favourite_food, name="Kid", legendgroup="Kid"
    ),
    row=1,
    col=1,
)
fig.add_trace(
    go.Histogram(
        histfunc="count",
        histnorm="percent",
        x=df.loc["Adult"].favourite_drink,
        name="Adult",
        legendgroup="Adult",
    ),
    row=1,
    col=2,
)
fig.add_trace(
    go.Histogram(
        histfunc="count", histnorm="percent", x=df.loc["Kid"].favourite_drink, name="Kid", legendgroup="Kid"
    ),
    row=1,
    col=2,
)
fig.show()

If I want to get the colours to match in both plots I would have to specify even more options, so this is not a very viable alternative to me. Is there any way to get this to work in plotly.express?

You are right, itโ€™s quite cumbersome. What one had to figure out, how the dataframe has to be structured for your visualization goals. But in the end: you already did it!? You could just convert it to a function if your df always looks like this.

Concerning the comment of matching colors:

My df does not always look like this, this is just an example. As it works withouth a problem with numeric axes I suspect itโ€™s an issue, therefore Iโ€™ve opened one: Axis labels are not shown for all subplots when using plotly express, facets and string labels ยท Issue #6806 ยท plotly/plotly.js (github.com)