Parkinson’s disease in the spinal cord: an exploratory study to establish T2*w, MTR and diffusion-weighted imaging metric values
import plotly.express as px
from plotly.offline import plot
from IPython.core.display import HTML
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
import statsmodels.formula.api as smf
from statsmodels.formula.api import ols
from statsmodels.stats.anova import anova_lm
import base64
import plotly.io as pio
pio.renderers.default = "plotly_mimetype"
# Initialize the figure with subplots
fig = make_subplots(
rows=6, cols=3,
vertical_spacing=0.05, # Adjust vertical spacing (between rows)
horizontal_spacing=0.16, # Adjust horizontal spacing (between columns)
)
# Color palettes
palette_boxplots = ['steelblue', '#F0B0B0', 'lightcoral', '#B4464F']
palette_points = ['#00517F', '#B4464F', '#B4464F', '#5E000E']
# Initialize dictionnaries to store dataframes for white matter, gray matter and WM/GM ratio
df_WM = {}
df_GM = {}
df_WMGM = {}
# Dataframes for metrics in the white matter :
metrics_in_WM = ['FA', 'MD', 'AD', 'RD', 'ODI', 'FISO', 'FICVF', 'MTR']
for metric in metrics_in_WM:
df = pd.read_csv(f'../../data/parkinsons-spinalcord-mri-metrics/data/{metric}.csv')
df_WM[metric] = df[(df['Label'] == 'white matter') & (df['SpinalLevel'] == '2:05')]
# Dataframes for metrics in the gray matter :
metrics_in_GM = ['ODI', 'FISO', 'FICVF']
for metric in metrics_in_GM:
df = pd.read_csv(f'../../data/parkinsons-spinalcord-mri-metrics/data/{metric}.csv')
df_GM[metric] = df[(df['Label'] == 'gray matter') & (df['SpinalLevel'] == '2:05')]
# Dataframes for metrics in the WM/GM ratio:
df = pd.read_csv(f'../../data/parkinsons-spinalcord-mri-metrics/data/T2star.csv')
df_WM['T2star'] = df[(df['Label'] == 'white matter') & (df['SpinalLevel'] == '2:05')]
df_GM['T2star'] = df[(df['Label'] == 'gray matter') & (df['SpinalLevel'] == '2:05')]
df_WMGM['T2star'] = df_WM['T2star'].copy()
df_WMGM['T2star']['WA'] = df_WM['T2star']['WA']/df_GM['T2star']['WA'].values
# Function to add boxplots for each subplot
def add_boxplot_for_subplot(data, row, col):
#print(f'------------ data used for boxplot in row {row}, col {col} -------------- \n : {data}')
groups = ['CTRL', 'early', 'mid', 'adv']
# OLS analysis
data['UPDRS_class_bis'] = pd.Categorical(data['UPDRS_class_bis'], ordered=True)
ols_model = smf.ols(formula='WA ~ C(UPDRS_class_bis) + Age', data=data) # The "C()" here means that UPDRS_class_bis is a categorical variable
ols_results = ols_model.fit()
#print(f'OLS results for WM MTR in C2-C5: {ols_results.summary()}')
# Perform ANOVA test
anova_results = anova_lm(ols_results, typ=2) # Type II ANOVA
#print(f'ANOVA results : {anova_results}')
# Adjusted R2
adjR2 = ols_results.rsquared_adj
# UPDRSIII_class_bis p-value
pvalue_UPDRS_class_bis = anova_results.loc['C(UPDRS_class_bis)', 'PR(>F)']
# Age p-value
pvalue_age = anova_results.loc['Age', 'PR(>F)']
for i, group in enumerate(groups):
box_color = palette_boxplots[i % len(palette_boxplots)] # Ensure we don't run out of colors
points_color = palette_points[i % len(palette_points)] # Cycle through jitter colors
fig.add_trace(go.Box(
# Filter data for each group
x=data['UPDRS_class_bis'][data['UPDRS_class_bis'] == group],
y=data['WA'][data['UPDRS_class_bis'] == group],
# Show all points
boxpoints='all',
jitter=0.7, # Jitter the points for better visibility
whiskerwidth=0.8, # Width of the whiskers
fillcolor=box_color, # Color of the box
marker_size=2.5, # Marker size for points
marker_color=points_color, # Color of the points
marker_opacity=0.8, # Opacity of the points
line_width=1, # Border width of the box
line_color="black" , # Border color of the box
pointpos=0, # Center the points with the box
), row=row, col=col)
# Determine if the p-value is significant (whether to add an asterisk on the plot or not)
red_asterisk = '<span style="color:red; font-size:15">*</span>' if pvalue_UPDRS_class_bis < 0.05 else ""
black_asterisk = '<span style="color:black; font-size:15">*</span>' if pvalue_age < 0.05 else ""
# Add annotations for p-values
fig.add_annotation(
x=0.99, # Position of the annotation in x
y=0.98, # Position of the annotation in y
text=f"p-Group: {pvalue_UPDRS_class_bis:.4f}{red_asterisk}<br>p-Age: {pvalue_age:.4f}{black_asterisk}",
showarrow=False,
font=dict(size=11, family="Arial", color="black"),
bgcolor="white", # Background color of the annotation
align="right",
row=row,
col=col,
xref="x domain",
yref="y domain"
)
# Add boxplots
# White matter plots
add_boxplot_for_subplot(df_WM['FA'], 2, 2)
add_boxplot_for_subplot(df_WM['MD'], 3, 2)
add_boxplot_for_subplot(df_WM['AD'], 4, 2)
add_boxplot_for_subplot(df_WM['RD'], 5, 2)
add_boxplot_for_subplot(df_WM['ODI'], 2, 3)
add_boxplot_for_subplot(df_WM['FISO'], 3, 3)
add_boxplot_for_subplot(df_WM['FICVF'], 4, 3)
add_boxplot_for_subplot(df_WM['MTR'], 5, 3)
# Gray matter plots
add_boxplot_for_subplot(df_GM['ODI'], 4, 1)
add_boxplot_for_subplot(df_GM['FISO'], 5, 1)
add_boxplot_for_subplot(df_GM['FICVF'], 6, 1)
# WM/GM plots
add_boxplot_for_subplot(df_WMGM['T2star'], 6, 3)
# Update layout
fig.update_layout(
margin=dict(
l=111,
r=95,
t=40,
b=100,
),
width=800,
height=990,
showlegend=False,
yaxis5_title="FA",
yaxis5_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis5_title_standoff=1,
yaxis8_title="MD",
yaxis8_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis8_title_standoff=1,
yaxis11_title="AD",
yaxis11_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis11_title_standoff=1,
yaxis14_title="RD",
yaxis14_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis14_title_standoff=1,
yaxis6_title="ODI",
yaxis6_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis6_title_standoff=1,
yaxis9_title="FISO",
yaxis9_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis9_title_standoff=1,
yaxis12_title="FICVF",
yaxis12_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis12_title_standoff=1,
yaxis15_title='MTR',
yaxis15_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis15_title_standoff=1,
yaxis10_title="ODI",
yaxis10_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis10_title_standoff=1,
yaxis13_title="FISO",
yaxis13_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis13_title_standoff=1,
yaxis16_title='FICVF',
yaxis16_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis16_title_standoff=1,
yaxis18_title="T2*w ratio",
yaxis18_title_font=dict(size=20, family="Arial", color="black", weight='bold'),
yaxis18_title_standoff=1,
)
# Load static background image (from the "templates_for_figures" folder) and encode as base64
with open("../templates_for_figures/figure4_template.png", "rb") as image_file:
encoded_image = base64.b64encode(image_file.read()).decode()
fig.update_layout(
images=[dict(
source="data:image/png;base64," + encoded_image, # Add static background image
x=-0.24,
y=0.995,
xanchor="left",
yanchor="top",
sizex=1.4,
sizey=2.5,
layer="below",
)],
)
# Set the y-axis range for each subplot
fig.update_yaxes(range=[0.3, 0.9], row=2, col=2) # FA (WM)
fig.update_yaxes(range=[0.0002, 0.0016], row=3, col=2) # MD (WM)
fig.update_yaxes(range=[0.0002, 0.0026], row=4, col=2) # AD (WM)
fig.update_yaxes(range=[0.00025, 0.001], row=5, col=2) # RD (WM)
fig.update_yaxes(range=[0, 0.5], row=2, col=3) # ODI (WM)
fig.update_yaxes(range=[0.1, 0.7], row=3, col=3) # FISO (WM)
fig.update_yaxes(range=[0.4, 1.3], row=4, col=3) # FICVF (WM)
fig.update_yaxes(range=[35, 55], row=5, col=3) # MTR (WM)
fig.update_yaxes(range=[0, 0.5], row=4, col=1) # ODI (GM)
fig.update_yaxes(range=[0.1, 0.7], row=5, col=1) # FISO (GM)
fig.update_yaxes(range=[0.4, 1.3], row=6, col=1) # FICVF (GM)
fig.update_yaxes(range=[0.8, 1.1], row=6, col=3) # T2* (WM/GM ratio)
# List of subplot positions for x-axis updates
xaxis_subplots = [(2, 2), (3, 2), (4, 2), (5, 2), (2, 3), (3, 3), (4, 3), (5, 3), (4, 1), (5, 1), (6, 1), (6, 3)]
# Update x-axis labels and font
for row, col in xaxis_subplots:
fig.update_xaxes(
ticktext=['HC', 'Early', 'Mid', 'Adv'],
tickvals=['CTRL', 'early', 'mid', 'adv'],
tickfont=dict(size=13, weight='bold', family="Arial"),
row=row,
col=col
)
fig.show()
Loading...