Scatter plot with conditional error bars

Adding p-values and statistical annotations in Plotly appears more complicated than it should be, I think.

Although the following codes produces what it should, I would greatly appreciate if the developers @nicolaskruchten could provide an easier way to accomplish it.

Here’s the graph with the data from https://www.dropbox.com/s/t4j96etfw5t02qv/example_2.xlsx?dl=0

and the code

import plotly.graph_objects as go
import plotly.express as px
import pandas as pd

df = pd.read_excel("/Users/Jakob/Dropbox/Public/example_2.xlsx",
                   sheet_name='sheet_1', header=0, skiprows=range(1,4), usecols = "A,B,E", na_values=['NA']
                  )


# Data Extraction
# Group and calculate the mean and sem for distance moved during dark and light period
mean_period=df.groupby(['genotype','period']).mean()
sem_period=df.groupby(['genotype','period']).sem()

# Extract mean from the distance moved during the dark period
dark_mean_A=mean_period['Distance moved'].A['Dark']
dark_mean_B=mean_period['Distance moved'].B['Dark']
dark_mean_C=mean_period['Distance moved'].C['Dark']

# Extract mean from the distance moved during the light period
light_mean_A=mean_period['Distance moved'].A['Light']
light_mean_B=mean_period['Distance moved'].B['Light']
light_mean_C=mean_period['Distance moved'].C['Light']


# Extract sem from the distance moved during the dark period
dark_sem_A=sem_period['Distance moved'].A['Dark']
dark_sem_B=sem_period['Distance moved'].B['Dark']
dark_sem_C=sem_period['Distance moved'].C['Dark']

# Extract sem from the distance moved during the light period
light_sem_A=sem_period['Distance moved'].A['Light']
light_sem_B=sem_period['Distance moved'].B['Light']
light_sem_C=sem_period['Distance moved'].C['Light']


# Scatter plot
fig = px.strip(df, x='genotype', y='Distance moved', color="period",
               color_discrete_sequence=['rgba(0,0,0,0.4)', 'rgba(255,255,0,0.4)']).update_traces(
                                                                       jitter = 1,
                                                                       opacity=0.8,
                                                                       marker_size=10,
                                                                       marker_line_width=1,
                                                                       marker_line_color='rgba(0,0,0,0.8)',
                                                                       #marker_color='rgba(0,0,0,0.8)',
                                                                       showlegend=False)

# Bar graphs with error bars
fig.add_bar(
    name='Dark',
    marker_color='rgba(0,0,0,0.5)', marker_line_color='rgba(0,0,0,0.8)', marker_line_width=1, opacity=0.8,
    x=['A', 'B', 'C'],
    y=[dark_mean_A, dark_mean_B, dark_mean_C],
    error_y=dict(type='data', array=[dark_sem_A, dark_sem_B, dark_sem_C],
                color='rgba(0,0,0,1)', thickness=1.5, width=10)
)

fig.add_bar(
    name='Light',
    marker_color='rgba(255,255,0,0.5)', marker_line_color='rgba(0,0,0,0.8)', marker_line_width=1, opacity=0.8,
    x=['A', 'B', 'C'],
    y=[light_mean_A, light_mean_B, light_mean_C],    
    error_y=dict(type='data', array=[light_sem_A, light_sem_B, light_sem_C],
                color='rgba(0,0,0,1)', thickness=1.5, width=10)
)

# Sample numbers
# Add n numbers
fig.add_trace(go.Scatter(
    x=['B', 'A', 'C'],
    y=[100, 100, 100],
    mode="text",
    name="n numbers",
    text=['n = 30', 'n = 32', 'n = 32'],
    textposition="top center",
    textfont=dict(color='rgba(0,0,0,1)', size=13),
    hoverlabel=dict(bgcolor='white'),
    showlegend=False
))

# Brackets for p-values
# Dark bar 1 to dark bar 2 p-value bracket
x_coords = [0.10, 0.10, 0.428, 0.428]
y_coords = [(dark_mean_A+dark_sem_A)+100, (dark_mean_B+dark_sem_B)+300,
            (dark_mean_B+dark_sem_B)+300, (dark_mean_B+dark_sem_B)+100]
for i in range(1,len(x_coords)):
    fig.add_shape(
        type="line",
        xref="paper",
        x0=x_coords[i-1], 
        y0=y_coords[i-1], 
        x1=x_coords[i], 
        y1=y_coords[i],
        line=dict(color='rgba(0,0,0,1)', width=1.5), opacity=1
    )
    
# Dark bar 1 to dark bar 3 p-value bracket
x_coords = [0.10, 0.10, 0.7674, 0.7674]
y_coords = [(dark_mean_A+dark_sem_A)+700, (dark_mean_A+dark_sem_A)+2000,
            (dark_mean_A+dark_sem_A)+2000, (dark_mean_A+dark_sem_A)+1800]
for i in range(1,len(x_coords)):
    fig.add_shape(
        type="line",
        xref="paper",
        x0=x_coords[i-1], 
        y0=y_coords[i-1], 
        x1=x_coords[i], 
        y1=y_coords[i],
        line=dict(color='rgba(0,0,0,1)', width=1.5), opacity=1
    )
    
# Dark bar 2 to bar 3 p-value bracket
x_coords = [0.437, 0.437, 0.7674, 0.7674]
y_coords = [(dark_mean_B+dark_sem_B)+100, (dark_mean_B+dark_sem_B)+300,
            (dark_mean_B+dark_sem_B)+300, (dark_mean_B+dark_sem_C)+100]
for i in range(1,len(x_coords)):
    fig.add_shape(
        type="line",
        xref="paper",
        x0=x_coords[i-1], 
        y0=y_coords[i-1], 
        x1=x_coords[i], 
        y1=y_coords[i],
        line=dict(color='rgba(0,0,0,1)', width=1.5), opacity=1
    )
    
    
# Light bar 1 to light bar 2 p-value bracket
x_coords = [0.233, 0.233, 0.560, 0.560]
y_coords = [(light_mean_B+light_sem_B)+2500, (light_mean_B+light_sem_B)+2700,
            (light_mean_B+light_sem_B)+2700, (light_mean_B+light_sem_B)+2200]
for i in range(1,len(x_coords)):
    fig.add_shape(
        type="line",
        xref="paper",
        x0=x_coords[i-1], 
        y0=y_coords[i-1], 
        x1=x_coords[i], 
        y1=y_coords[i],
        line=dict(color='rgba(0,0,0,1)', width=1.5), opacity=1
    )
    
# Light bar 2 to light bar 3 p-value bracket
x_coords = [0.574, 0.574, 0.9, 0.9]
y_coords = [(light_mean_B+light_sem_B)+2500, (light_mean_B+light_sem_B)+2700,
            (light_mean_B+light_sem_B)+2700, (light_mean_C+light_sem_C)+100]
for i in range(1,len(x_coords)):
    fig.add_shape(
        type="line",
        xref="paper",
        x0=x_coords[i-1], 
        y0=y_coords[i-1], 
        x1=x_coords[i], 
        y1=y_coords[i],
        line=dict(color='rgba(0,0,0,1)', width=1.5), opacity=1
    )
    
# Light bar 1 to light bar 3 p-value bracket
x_coords = [0.233, 0.233, 0.9, 0.9]
y_coords = [(light_mean_A+light_sem_A)+4100, (light_mean_A+light_sem_A)+4500,
            (light_mean_A+light_sem_A)+4500, (light_mean_C+light_sem_C)+2900]
for i in range(1,len(x_coords)):
    fig.add_shape(
        type="line",
        xref="paper",
        x0=x_coords[i-1], 
        y0=y_coords[i-1], 
        x1=x_coords[i], 
        y1=y_coords[i],
        line=dict(color='rgba(0,0,0,1)', width=1.5), opacity=1        
    )

    
# P-values
# Darl to Dark
# p-value: Dark bar 1 to dark bar 2
fig.add_annotation(text="p = 0.5111",
                   name="p-value",                                  
                   xref="paper", yref="paper",
                   x=0.23, y=0.640, showarrow=False,
                   font=dict(size=12, color="black")
                  )

# p-value: Dark bar 1 to dark bar 3
fig.add_annotation(text="p = 0.5898",
                   name="p-value",                                  
                   xref="paper", yref="paper",
                   x=0.48, y=0.845, showarrow=False,
                   font=dict(size=12, color="black")
                  )

# p-value: Dark bar 2 to dark bar 3
fig.add_annotation(text="p = 0.9910",
                   name="p-value",                                  
                   xref="paper", yref="paper",
                   x=0.62, y=0.640, showarrow=False,
                   font=dict(size=12, color="black")
                  )
# Light to Light
# p-value: Light bar 1 to light bar 2
fig.add_annotation(text="p = 0.4897",
                   name="p-value",                                  
                   xref="paper", yref="paper",
                   x=0.325, y=0.75, showarrow=False,
                   font=dict(size=12, color="black")
                  )

# p-value: Light bar 1 to light bar 3
fig.add_annotation(text="p = 0.5841",
                   name="p-value",                                  
                   xref="paper", yref="paper",
                   x=0.56, y=0.95, showarrow=False,
                   font=dict(size=12, color="black")
                  )

# p-value: Light bar 2 to light bar 3
fig.add_annotation(text="p = 0.9870",
                   name="p-value",                                  
                   xref="paper", yref="paper",
                   x=0.753, y=0.75, showarrow=False,
                   font=dict(size=12, color="black")
                  )




# Customization of layout and traces
fig.update_layout(template='simple_white', title='', height=600, width=1000, yaxis_title='Distance moved [mm]', barmode='group',
                  dragmode='drawrect', font_size=12, hoverlabel_namelength=-1, legend_title_text='',
                  bargroupgap=0,
                 )

fig.update_traces(hoverinfo="x+y")

# Set custom x-axis labels
fig.update_xaxes(title='', tickvals=["A", "B", "C"],
                 ticktext=["Elephant", "Dolphin", "Cat"],
                 ticks="", tickfont_size=14
                )

fig.update_yaxes(range=[0, 7500]
                )


print(df.groupby(['genotype', 'period']).mean())
print(df.groupby(['genotype', 'period']).sem())

fig.show()