Marginal distribution plots without express?

Hi all,

I am creating multi-trace histograms one bar trace at a time because I need control that plotly.express’ histograms don’t give me (like changing traces’ visibility depending on values per bin).

However now I can’t find a way to add marginal distribution plots to my histogram. Looks like it’s a plotly.express-only thing!

Is that right? Hopefully I’m missing something…

Hey @hmijail welcome to the community.

Would you mind creating a minimal example for that? Are you using plotly.graph_objects to generate your figure?

I guess you are referring to this:

Sorry, creating an example of … what? the thing that can’t be done? :sweat_smile:

What I am doing is basically this:

fig = go.Figure()
bins = ...

for l in mydata:
  visibility = ...
  fig.add_bar(
        x=bins,
        y=l,
        visible=visibility,
    )
fig.show()

I don’t see any Figure.add_* mentioning marginals, so that’s where I’m blocked.

… and yes, as you see I’m using Figure from plotly.graph_objects.

While all the examples in the link you posted use plotly.express.

TL;DR You can re-create any figure created with px using go, it takes some effort, though

Some explanation:

As far as I know, figures created by plotly.express can be created by plotly.graph_objects as express uses graph_objects under the hood. You might see express as a more user- friendly version of graph_objects which does grouping with pandas or creating subplots for you.

That said, there is no function which adds marginals because they are just traces arranged in subplots.

To get an idea of what I’m referring to, just take this example:

import plotly.express as px
df = px.data.iris()
fig = px.histogram(df, x="sepal_length", color="species", marginal="box", height=700)
fig.show()

if you convert the figure into a dictionary and print the content, you’ll find the traces which have been created by px for you.

f = fig.to_dict()
print(f['data'])

Output:

[{'alignmentgroup': 'True',
  'bingroup': 'x',
  'hovertemplate': 'species=setosa<br>sepal_length=%{x}<br>count=%{y}<extra></extra>',
  'legendgroup': 'setosa',
  'marker': {'color': '#636efa', 'pattern': {'shape': ''}},
  'name': 'setosa',
  'offsetgroup': 'setosa',
  'orientation': 'v',
  'showlegend': True,
  'x': array([5.1, 4.9, 4.7, 4.6, 5. , 5.4, 4.6, 5. , 4.4, 4.9, 5.4, 4.8, 4.8,
         4.3, 5.8, 5.7, 5.4, 5.1, 5.7, 5.1, 5.4, 5.1, 4.6, 5.1, 4.8, 5. ,
         5. , 5.2, 5.2, 4.7, 4.8, 5.4, 5.2, 5.5, 4.9, 5. , 5.5, 4.9, 4.4,
         5.1, 5. , 4.5, 4.4, 5. , 5.1, 4.8, 5.1, 4.6, 5.3, 5. ]),
  'xaxis': 'x',
  'yaxis': 'y',
  'type': 'histogram'},
 {'alignmentgroup': 'True',
  'hovertemplate': 'species=setosa<br>sepal_length=%{x}<extra></extra>',
  'legendgroup': 'setosa',
  'marker': {'color': '#636efa'},
  'name': 'setosa',
  'notched': True,
  'offsetgroup': 'setosa',
  'showlegend': False,
  'x': array([5.1, 4.9, 4.7, 4.6, 5. , 5.4, 4.6, 5. , 4.4, 4.9, 5.4, 4.8, 4.8,
         4.3, 5.8, 5.7, 5.4, 5.1, 5.7, 5.1, 5.4, 5.1, 4.6, 5.1, 4.8, 5. ,
         5. , 5.2, 5.2, 4.7, 4.8, 5.4, 5.2, 5.5, 4.9, 5. , 5.5, 4.9, 4.4,
         5.1, 5. , 4.5, 4.4, 5. , 5.1, 4.8, 5.1, 4.6, 5.3, 5. ]),
  'xaxis': 'x2',
  'yaxis': 'y2',
  'type': 'box'},
 {'alignmentgroup': 'True',
  'bingroup': 'x',
  'hovertemplate': 'species=versicolor<br>sepal_length=%{x}<br>count=%{y}<extra></extra>',
  'legendgroup': 'versicolor',
  'marker': {'color': '#EF553B', 'pattern': {'shape': ''}},
  'name': 'versicolor',
  'offsetgroup': 'versicolor',
  'orientation': 'v',
  'showlegend': True,
  'x': array([7. , 6.4, 6.9, 5.5, 6.5, 5.7, 6.3, 4.9, 6.6, 5.2, 5. , 5.9, 6. ,
         6.1, 5.6, 6.7, 5.6, 5.8, 6.2, 5.6, 5.9, 6.1, 6.3, 6.1, 6.4, 6.6,
         6.8, 6.7, 6. , 5.7, 5.5, 5.5, 5.8, 6. , 5.4, 6. , 6.7, 6.3, 5.6,
         5.5, 5.5, 6.1, 5.8, 5. , 5.6, 5.7, 5.7, 6.2, 5.1, 5.7]),
  'xaxis': 'x',
  'yaxis': 'y',
  'type': 'histogram'},
 {'alignmentgroup': 'True',
  'hovertemplate': 'species=versicolor<br>sepal_length=%{x}<extra></extra>',
  'legendgroup': 'versicolor',
  'marker': {'color': '#EF553B'},
  'name': 'versicolor',
  'notched': True,
  'offsetgroup': 'versicolor',
  'showlegend': False,
  'x': array([7. , 6.4, 6.9, 5.5, 6.5, 5.7, 6.3, 4.9, 6.6, 5.2, 5. , 5.9, 6. ,
         6.1, 5.6, 6.7, 5.6, 5.8, 6.2, 5.6, 5.9, 6.1, 6.3, 6.1, 6.4, 6.6,
         6.8, 6.7, 6. , 5.7, 5.5, 5.5, 5.8, 6. , 5.4, 6. , 6.7, 6.3, 5.6,
         5.5, 5.5, 6.1, 5.8, 5. , 5.6, 5.7, 5.7, 6.2, 5.1, 5.7]),
  'xaxis': 'x2',
  'yaxis': 'y2',
  'type': 'box'},
 {'alignmentgroup': 'True',
  'bingroup': 'x',
  'hovertemplate': 'species=virginica<br>sepal_length=%{x}<br>count=%{y}<extra></extra>',
  'legendgroup': 'virginica',
  'marker': {'color': '#00cc96', 'pattern': {'shape': ''}},
  'name': 'virginica',
  'offsetgroup': 'virginica',
  'orientation': 'v',
  'showlegend': True,
  'x': array([6.3, 5.8, 7.1, 6.3, 6.5, 7.6, 4.9, 7.3, 6.7, 7.2, 6.5, 6.4, 6.8,
         5.7, 5.8, 6.4, 6.5, 7.7, 7.7, 6. , 6.9, 5.6, 7.7, 6.3, 6.7, 7.2,
         6.2, 6.1, 6.4, 7.2, 7.4, 7.9, 6.4, 6.3, 6.1, 7.7, 6.3, 6.4, 6. ,
         6.9, 6.7, 6.9, 5.8, 6.8, 6.7, 6.7, 6.3, 6.5, 6.2, 5.9]),
  'xaxis': 'x',
  'yaxis': 'y',
  'type': 'histogram'},
 {'alignmentgroup': 'True',
  'hovertemplate': 'species=virginica<br>sepal_length=%{x}<extra></extra>',
  'legendgroup': 'virginica',
  'marker': {'color': '#00cc96'},
  'name': 'virginica',
  'notched': True,
  'offsetgroup': 'virginica',
  'showlegend': False,
  'x': array([6.3, 5.8, 7.1, 6.3, 6.5, 7.6, 4.9, 7.3, 6.7, 7.2, 6.5, 6.4, 6.8,
         5.7, 5.8, 6.4, 6.5, 7.7, 7.7, 6. , 6.9, 5.6, 7.7, 6.3, 6.7, 7.2,
         6.2, 6.1, 6.4, 7.2, 7.4, 7.9, 6.4, 6.3, 6.1, 7.7, 6.3, 6.4, 6. ,
         6.9, 6.7, 6.9, 5.8, 6.8, 6.7, 6.7, 6.3, 6.5, 6.2, 5.9]),
  'xaxis': 'x2',
  'yaxis': 'y2',
  'type': 'box'}]

Aha, that’s interesting… looks like I can also use fig.update_*() to change parts of the dictionary - or feed it to a blank figure to recreate it piecewise.

I’ll see what balance I can find between fast-but-hacky vs reliable. Thank you!

1 Like