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()