@@ -669,6 +669,7 @@ def get_delays(
669669 df : pd .DataFrame ,
670670 features : list ,
671671 stim_time : list | tuple ,
672+ conditions : list ,
672673 nstd : float = 3 ,
673674 npoints : int = 3 ,
674675 maxdelay : float = 0.5 ,
@@ -697,6 +698,9 @@ def get_delays(
697698 Keys in `df`, used to compute delays.
698699 stim_time : 2-elements list or tuple
699700 Stimulation onset and offset.
701+ conditions : list
702+ List of conditions. Pairwise consecutive conditions will be used to compute
703+ p-values. It is required to ensure the ordering.
700704 nstd : float, optionnal
701705 Multiplier of standard deviation for threshold definition. Default is 3.
702706 npoints : int, optional
@@ -709,12 +713,26 @@ def get_delays(
709713 -------
710714 df_delays : pandas.DataFrame
711715 List of delays for each animals and each trials. NaN if no delay found.
716+ pvalues : dict
717+ pvalues associated with each features, comparing pairs of consecutive groups.
712718
713719 """
720+
721+ # check if significance test will be performed
722+ if len (conditions ) < 2 :
723+ flag_pvalue = False
724+ else :
725+ flag_pvalue = True
726+
727+ # make sure conditions is a list
728+ if not isinstance (conditions , list ):
729+ conditions = list (conditions )
730+
714731 # group by trials and conditions
715732 df_group = df .groupby (["condition" , "trialID" ])
716733
717734 df_delays_feature = []
735+ pvalues = {} # prepare list of pvalues comparing the consecutive conditions
718736 for feature in features :
719737 dfs = [] # initialize output
720738 for name , df_trial in df_group :
@@ -819,11 +837,25 @@ def get_delays(
819837
820838 df_delay = pd .DataFrame (dfs ) # convert to DataFrame
821839 df_delay ["feature" ] = feature # add corresponding feature
840+
841+ # compute pvalues for consecutive conditions
842+ if flag_pvalue :
843+ pvalue = []
844+ for idx_condition in range (len (conditions ) - 1 ):
845+ cond1 = conditions [idx_condition ]
846+ cond2 = conditions [idx_condition + 1 ]
847+ values1 = df_delay .loc [df_delay ["condition" ] == cond1 , "delay" ].dropna ()
848+ values2 = df_delay .loc [df_delay ["condition" ] == cond2 , "delay" ].dropna ()
849+ pvalue .append (stats .ttest_ind (values1 , values2 ).pvalue )
850+ else :
851+ pvalue = None
852+
822853 df_delays_feature .append (df_delay )
854+ pvalues [feature ] = pvalue
823855
824856 df_delays = pd .concat (df_delays_feature ).reset_index ().drop (columns = "index" )
825857
826- return df_delays
858+ return df_delays , pvalues
827859
828860
829861def get_responsiveness_from_delays (df_delays : pd .DataFrame ) -> pd .DataFrame :
@@ -872,7 +904,11 @@ def pvalue_to_stars(pvalue):
872904 return "**"
873905 elif pvalue <= 0.05 :
874906 return "*"
875- return "ns"
907+ elif pvalue > 0.05 :
908+ return "ns"
909+ else :
910+ print ("[Warning] pvalue computation errored." )
911+ return "error"
876912
877913
878914def add_stars_to_lines (pvalue , stim_time , ax , ** kwargs ):
@@ -1219,7 +1255,7 @@ def nice_plot_metrics(
12191255 x : str = "condition" ,
12201256 y : str = "" ,
12211257 conditions_order : list = [],
1222- pvalue : float = 0 ,
1258+ pvalues : list | float = 0 ,
12231259 title = "" ,
12241260 ax : plt .Axes | None = None ,
12251261 kwargs_plot : dict = {},
@@ -1238,8 +1274,9 @@ def nice_plot_metrics(
12381274 Keys in `df`.
12391275 conditions_order : list
12401276 Order in which metrics will be plotted.
1241- pvalue : float
1242- p-value to plot stars. If 0, no stars will be plotted.
1277+ pvalues : List
1278+ List of p-values for consecutive conditions to plot stars. If 0, no stars will
1279+ be plotted.
12431280 title : str
12441281 Axes title.
12451282 ax : matplotlib Axes
@@ -1280,22 +1317,21 @@ def nice_plot_metrics(
12801317 )
12811318
12821319 # add significance
1283- if pvalue :
1320+ if pvalues :
12841321 # get bar + errorbar value, sorting as sorted in the plot
12851322 maxvals = (df .groupby (x )[y ].mean () + df .groupby (x )[y ].sem ())[
12861323 conditions_order
12871324 ].values
1288- c = 0
1289- for pval in pvalue :
1325+
1326+ for c , pvalue in enumerate ( pvalues ) :
12901327 ax = add_stars_to_bars (
12911328 [c , c + 1 ],
12921329 max (maxvals [c : c + 2 ]),
1293- pval ,
1330+ pvalue ,
12941331 ax = ax ,
12951332 line_kws = {"color" : "k" },
12961333 text_kws = {"fontsize" : "large" },
12971334 )
1298- c += 1
12991335
13001336 # remove axes labels
13011337 ax .set_xlabel ("" )
@@ -1317,11 +1353,12 @@ def nice_plot_metrics(
13171353
13181354def nice_plot_bars (
13191355 df : pd .DataFrame ,
1320- x = "" ,
1321- y = "" ,
1322- hue = "" ,
1323- xlabels = dict ,
1324- ylabel = "" ,
1356+ x : str = "" ,
1357+ y : str = "" ,
1358+ hue : str = "" ,
1359+ pvalues : dict | None = None ,
1360+ xlabels : dict = {},
1361+ ylabel : str = "" ,
13251362 ax : plt .Axes | None = None ,
13261363 kwargs_plot : dict = {},
13271364) -> plt .Axes :
@@ -1333,8 +1370,11 @@ def nice_plot_bars(
13331370 df : pandas.DataFrame
13341371 x, y, hue : str
13351372 Keys in `df`.
1373+ pvalues : dict
1374+ Mapping a `x` to a list of pvalues for consecutive `hue`.
13361375 xlabels : dict
13371376 Mapping names found in 'x' in `df` to another values.
1377+ ylabel : str
13381378 ax : matplotlib Axes
13391379
13401380 Returns
@@ -1372,6 +1412,40 @@ def nice_plot_bars(
13721412 ** kwplot ,
13731413 )
13741414
1415+ # add significance
1416+ if pvalues :
1417+ # get offset for each bar at same x (from seaborn)
1418+ n_levels = len (df [hue ].unique ()) # number of hue
1419+ width = 0.8 # 0.8 is default width in sns.barplot()
1420+ each_width = width / n_levels
1421+ offsets = np .linspace (0 , width - each_width , n_levels )
1422+ offsets -= offsets .mean ()
1423+
1424+ for xline_center , xp in enumerate (df [x ].unique ()):
1425+ # select data
1426+ dfpval = df .loc [df [x ] == xp , :]
1427+ pvalue = pvalues [xp ]
1428+
1429+ if not pvalue :
1430+ continue
1431+
1432+ # get bar + errorbar value, sorting as sorted in the plot
1433+ maxvals = (
1434+ dfpval .groupby (hue )[y ].mean () + dfpval .groupby (hue )[y ].sem ()
1435+ ).values
1436+
1437+ for c , pval in enumerate (pvalue ):
1438+ xline_0 = xline_center + offsets [c ]
1439+ xline_1 = xline_center + offsets [c + 1 ]
1440+ ax = add_stars_to_bars (
1441+ [xline_0 , xline_1 ],
1442+ max (maxvals [c : c + 2 ]),
1443+ pval ,
1444+ ax = ax ,
1445+ line_kws = {"color" : "k" },
1446+ text_kws = {"fontsize" : "large" },
1447+ )
1448+
13751449 # add/remove axes labels
13761450 ax .set_xlabel ("" )
13771451 ax .set_ylabel (ylabel )
@@ -1537,6 +1611,7 @@ def process_features(df_features: pd.DataFrame, conditions: dict, cfg):
15371611 df_metrics : pd.DataFrame
15381612 pvalues_metrics : dict
15391613 df_response : pd.DataFrame
1614+ pvalues_delays : dict
15401615
15411616 """
15421617
@@ -1552,18 +1627,19 @@ def process_features(df_features: pd.DataFrame, conditions: dict, cfg):
15521627 )
15531628
15541629 # delays before motion onset for each feature
1555- df_delays = get_delays (
1630+ df_delays , pvalues_delays = get_delays (
15561631 df_features ,
15571632 cfg .features ,
15581633 cfg .stim_time ,
1634+ conditions .keys (),
15591635 nstd = cfg .nstd ,
15601636 npoints = cfg .npoints ,
15611637 maxdelay = cfg .maxdelay ,
15621638 )
15631639 df_delays ["delay" ] = df_delays ["delay" ] * 1000 # convert to ms
15641640 df_response = get_responsiveness_from_delays (df_delays )
15651641
1566- return pvalues_stim , df_metrics , pvalues_metrics , df_response
1642+ return pvalues_stim , df_metrics , pvalues_metrics , df_response , pvalues_delays
15671643
15681644
15691645def plot_all_figures (
@@ -1572,6 +1648,7 @@ def plot_all_figures(
15721648 df_metrics : pd .DataFrame ,
15731649 pvalues_metrics : dict ,
15741650 df_response : pd .DataFrame ,
1651+ pvalues_delays : dict ,
15751652 plot_options : dict ,
15761653 conditions : dict ,
15771654 cfg ,
@@ -1651,7 +1728,7 @@ def plot_all_figures(
16511728 x = "condition" ,
16521729 y = f"{ feature } -{ metric } " ,
16531730 conditions_order = conditions .keys (),
1654- pvalue = pvalues_metrics [feature ][metric ],
1731+ pvalues = pvalues_metrics [feature ][metric ],
16551732 title = metric ,
16561733 ax = ax ,
16571734 kwargs_plot = kwargs_plot ,
@@ -1679,19 +1756,14 @@ def plot_all_figures(
16791756 # - Delays
16801757 print ("Plotting delays..." , end = "" , flush = True )
16811758 fig_delay , axd = plt .subplots (figsize = kwargs_plot ["figsize" ])
1682- df_response_plt = df_response [
1683- df_response ["condition" ].isin (plot_options ["plot_delay_list" ])
1684- & ~ df_response ["feature" ].isin (cfg .features_off )
1685- ]
1686-
1687- if df_response_plt .empty :
1688- print ("[Warning] Delays are empty, check 'plot_delay_list' in 'plot_options'." )
1759+ df_response_plt = df_response [~ df_response ["feature" ].isin (cfg .features_off )]
16891760
16901761 nice_plot_bars (
16911762 df_response_plt ,
16921763 x = "feature" ,
16931764 y = "delay" ,
16941765 hue = "condition" ,
1766+ pvalues = pvalues_delays ,
16951767 xlabels = cfg .features_labels ,
16961768 ylabel = "delay (ms)" ,
16971769 ax = axd ,
@@ -1818,8 +1890,8 @@ def process_directory(
18181890 df_features = pd .concat (df_align_list ).reset_index (drop = True )
18191891
18201892 # derive metrics, pvalues, delays and response
1821- pvalues_stim , df_metrics , pvalues_metrics , df_response = process_features (
1822- df_features , conditions , cfg
1893+ pvalues_stim , df_metrics , pvalues_metrics , df_response , pvalues_delays = (
1894+ process_features ( df_features , conditions , cfg )
18231895 )
18241896
18251897 # --- Plot everything
@@ -1829,6 +1901,7 @@ def process_directory(
18291901 df_metrics ,
18301902 pvalues_metrics ,
18311903 df_response ,
1904+ pvalues_delays ,
18321905 plot_options ,
18331906 conditions ,
18341907 cfg ,
0 commit comments