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)