3

I was wondering if Plotly can start support for nested X /multiple X axis? For instance if one is using standard “tip” data, adding a capability as implemented in (fivecents plot, JMP or Origin ) would be beneficial. i.e.

import plotly.express as px
df = px.data.tips()
%load_ext autoreload
%autoreload 2
%matplotlib inline
import fivecentplots as fcp
import pandas as pd
import numpy as np
import os, sys, pdb
osjoin = os.path.join
db = pdb.set_trace
fcp.boxplot(df=df, y=‘tip’, groups=[‘time’, ‘sex’, ‘day’], legend=‘smoker’)

enter image description here

would generate: nested X-axis bar-chart

If this capability already exist - please add a comment.

Hamzah
  • 8,175
  • 3
  • 19
  • 43
P Kuca
  • 31
  • 2
  • I am not sure if Plotly supports a multidimensional xaxis for more than 2 dimensions. any plotly experts want to chime in? – Derek O Oct 10 '22 at 23:14
  • Yes, @DerekO, to the best of my knowledge this functionality is not supported by plotly. – Hamzah Oct 11 '22 at 09:48
  • I recommend posting this question in Plotly community forum here: https://community.plotly.com/ – Hamzah Oct 11 '22 at 09:49
  • Thanks Derek & Hamzah, this was done some time back: https://community.plotly.com/t/box-plot-with-nested-x-axis/55800 – P Kuca Oct 11 '22 at 18:34
  • I believe that using a groupby and creating a separate trace for each boxplot, you could achieve something similar to what is being shown, but you would have to create the multidimensional x-axis labels yourself probably using annotations and admittedly, this would be a ton of work. – Derek O Oct 11 '22 at 19:39

1 Answers1

3

It's doable, but takes a lot more steps than the method you're currently using. One solution to achieving this with plotly is by creating subplots with multidimensional axis, one for Lunch and one for Dinner, with zero space in between so it looks like one single plot.

import plotly.express as px
import pandas as pd
import numpy as np
from pandas.api.types import CategoricalDtype

df = px.data.tips()

#set order for days
days = CategoricalDtype(
    ['Thur', 'Fri', 'Sat', 'Sun'],
    ordered=True
    )

df['day'] = df['day'].astype(days)

#sort df
df.sort_values(['time', 'sex', 'day'], inplace=True)

#create framework
fig = make_subplots(rows=1,
            cols=2,
            shared_yaxes=True,
            horizontal_spacing=0,
            column_widths=[7/11, 4/11])

#create "Dinner" boxplots
fig.add_trace(go.Box(x=[df['sex'][df['time']=='Dinner'].tolist(), df['day'][df['time']=='Dinner'].tolist()],
             y=df['tip'][df['time']=='Dinner'],
             boxpoints=False,
             pointpos=0,
             line=dict(color='gray',
                   width=1),
             fillcolor='white',
             showlegend=False),
          row=1,
          col=1)
#add "Dinner" smokers
fig.add_trace(go.Scatter(x=[df['sex'][(df['time']=='Dinner') & (df['smoker']=='Yes')].tolist(), df['day'][(df['time']=='Dinner') & (df['smoker']=='Yes')].tolist()],
             y=df['tip'][(df['time']=='Dinner') & (df['smoker']=='Yes')],
             mode='markers',
             marker=dict(color='red',
                     symbol='circle-open',
                     size=10),
             name='Yes'
             ),
          row=1,
          col=1)

#add "Dinner" non-smokers
fig.add_trace(go.Scatter(x=[df['sex'][(df['time']=='Dinner') & (df['smoker']=='No')].tolist(), df['day'][(df['time']=='Dinner') & (df['smoker']=='No')].tolist()],
             y=df['tip'][(df['time']=='Dinner') & (df['smoker']=='No')],
             mode='markers',
             marker=dict(color='green',
                     symbol='cross-thin-open',
                     size=10),
             name='No'
             ),
          row=1,
          col=1)

df_mean = df[['sex', 'day', 'tip']][df['time']=='Dinner'].groupby(['sex', 'day']).mean().reset_index().dropna()

#add "Dinner" mean line
fig.add_trace(go.Scatter(x=[df_mean['sex'].tolist(), df_mean['day'].tolist()],
             y=df_mean['tip'].tolist(),
             showlegend=False,
             marker=dict(color='black')
             ),
          row=1,
          col=1)

#create "Lunch" boxplots
fig.add_trace(go.Box(x=[df['sex'][df['time']=='Lunch'].tolist(), df['day'][df['time']=='Lunch'].tolist()],
             y=df['tip'][df['time']=='Lunch'],
             boxpoints=False,
             pointpos=0,
             line=dict(color='gray',
                   width=1),
             fillcolor='white',
             showlegend=False),
          row=1,
          col=2)
#add "Lunch" smokers
fig.add_trace(go.Scatter(x=[df['sex'][(df['time']=='Lunch') & (df['smoker']=='Yes')].tolist(), df['day'][(df['time']=='Lunch') & (df['smoker']=='Yes')].tolist()],
             y=df['tip'][(df['time']=='Lunch') & (df['smoker']=='Yes')],
             mode='markers',
             marker=dict(color='red',
                     symbol='circle-open',
                     size=10),
             showlegend=False
             ),
          row=1,
          col=2)
#add "Lunch" non-smokers
fig.add_trace(go.Scatter(x=[df['sex'][(df['time']=='Lunch') & (df['smoker']=='No')].tolist(), df['day'][(df['time']=='Lunch') & (df['smoker']=='No')].tolist()],
             y=df['tip'][(df['time']=='Lunch') & (df['smoker']=='No')],
             mode='markers',
             marker=dict(color='green',
                     symbol='cross-thin-open',
                     size=10),
             showlegend=False
             ),
          row=1,
          col=2)

df_mean = df[['sex', 'day', 'tip']][df['time']=='Lunch'].groupby(['sex', 'day']).mean().reset_index().dropna()

#add "Lunch" mean line
fig.add_trace(go.Scatter(x=[df_mean['sex'].tolist(), df_mean['day'].tolist()],
             y=df_mean['tip'].tolist(),
             showlegend=False,
             marker=dict(color='black')
             ),
          row=1,
          col=2)

fig.update_xaxes(title='Dinner', col=1)
fig.update_xaxes(title='Lunch', col=2)
fig.update_yaxes(title='tip', col=1)
fig.update_layout(legend_title='Smoker')
fig.show()

Figure

amance
  • 883
  • 4
  • 14
  • Thanks for the response Amance, this is far more complex than 12 lines of code. I think that the person who coded 5C plots (https://endangeredoxen.github.io/fivecentplots/0.5.1/) made it quite user friendly. I only wish that he added support for plotly as he did for Bokeh. – P Kuca Oct 14 '22 at 19:46
  • @PKuca Agreed! Happy to help, and welcome to Stack Overflow. If this answer or any other one solved your issue, please mark it as accepted. – amance Oct 17 '22 at 15:35