Skip to content

Commit acfe1de

Browse files
v2024.11.28
- compute pvalues for delays for consecutive conditions - display stars for delays - added some typing annotations
1 parent e1114c0 commit acfe1de

File tree

3 files changed

+102
-33
lines changed

3 files changed

+102
-33
lines changed

features_from_dlc/features_from_dlc.py

Lines changed: 100 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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

829861
def 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

878914
def 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

13181354
def 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

15691645
def 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,

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "features-from-dlc"
3-
version = "2024.11.27"
3+
version = "2024.11.28"
44
authors = [{ name = "Guillaume Le Goc", email = "g.legoc@posteo.org" }]
55
description = "Behavioral quantification from DeepLabCut tracking"
66
readme = "README.md"

scripts/ffd_quantify.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Specify each entry, reading carefully what they do, then run the script with the 'ffd'
55
conda environment activated.
66
7-
Works with features_from_dlc v2024.11.27
7+
Works with features_from_dlc v2024.11.28
88
99
"""
1010

@@ -59,10 +59,6 @@
5959
plot_animal=False, # whether to plot mean and sem per animal
6060
plot_animal_monochrome=True, # whether to plot mean per animal in the same color
6161
plot_condition_off=None, # conditions NOT to be plotted, list or None
62-
plot_delay_list=[
63-
"condition2",
64-
"condition3",
65-
], # delays will be plotted only for those
6662
style_file=style_file, # full path to the config_plot.toml file
6763
)
6864

0 commit comments

Comments
 (0)