Skip to content

Commit fd5460e

Browse files
authoredMar 21, 2025··
Merge pull request #100 from MannLabs/change_minrep_semantics
Fix filtering options in GUI
2 parents f11cb4e + 794a9dd commit fd5460e

12 files changed

+254
-130
lines changed
 

‎alphaquant/diffquant/condpair_analysis.py

+27-31
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def analyze_condpair(*,runconfig, condpair):
3737
c1_samples, c2_samples = aqutils.get_samples_used_from_samplemap_df(runconfig.samplemap_df, condpair[0], condpair[1])
3838

3939
try:
40-
df_c1, df_c2 = get_per_condition_dataframes(c1_samples, c2_samples, input_df_local,runconfig.minrep_both, runconfig.minrep_either, runconfig.minrep_c1, runconfig.minrep_c2)
40+
df_c1, df_c2 = get_per_condition_dataframes(c1_samples, c2_samples, input_df_local, min_valid_values=runconfig.min_valid_values, valid_values_filter_mode=runconfig.valid_values_filter_mode, min_valid_values_c1=runconfig.min_valid_values_c1, min_valid_values_c2=runconfig.min_valid_values_c2)
4141
except Exception as e:
4242
LOGGER.info(e)
4343
return
@@ -153,53 +153,49 @@ def write_out_normed_df(normed_df_1, normed_df_2, pep2prot, results_dir, condpai
153153
merged_df.to_csv(f"{results_dir}/{aqutils.get_condpairname(condpair)}.normed.tsv", sep = "\t")
154154

155155

156-
def get_per_condition_dataframes(samples_c1, samples_c2, unnormed_df, minrep_both =None, minrep_either = None, minrep_c1 = None, minrep_c2 = None):
156+
def get_per_condition_dataframes(samples_c1, samples_c2, unnormed_df, min_valid_values, valid_values_filter_mode, min_valid_values_c1, min_valid_values_c2):
157157

158158
min_samples = min(len(samples_c1), len(samples_c2))
159159

160160
if min_samples<2:
161161
raise Exception(f"condpair has not enough samples: c1:{len(samples_c1)} c2: {len(samples_c2)}, skipping")
162162

163-
if (minrep_either is not None) or ((minrep_c1 is not None) and (minrep_c2 is not None)): #minrep_both was set as default and should be overruled by minrep_either or minrep_c1 and minrep_c2
164-
minrep_both = None
165-
166-
if minrep_either is not None:
167-
minrep_either = np.min([get_minrep_for_cond(samples_c1, minrep_either), get_minrep_for_cond(samples_c2, minrep_either)])
168-
passes_minrep_c1 = unnormed_df.loc[:, samples_c1].notna().sum(axis=1) >= minrep_either
169-
passes_minrep_c2 = unnormed_df.loc[:, samples_c2].notna().sum(axis=1) >= minrep_either
170-
passes_minrep_either = passes_minrep_c1 | passes_minrep_c2
171-
unnormed_df = unnormed_df[passes_minrep_either]
163+
if valid_values_filter_mode == "either":
164+
min_valid_values = np.min([get_min_valid_values_for_cond(samples_c1, min_valid_values), get_min_valid_values_for_cond(samples_c2, min_valid_values)])
165+
passes_min_valid_values_c1 = unnormed_df.loc[:, samples_c1].notna().sum(axis=1) >= min_valid_values
166+
passes_min_valid_values_c2 = unnormed_df.loc[:, samples_c2].notna().sum(axis=1) >= min_valid_values
167+
passes_min_valid_values = passes_min_valid_values_c1 | passes_min_valid_values_c2
168+
unnormed_df = unnormed_df[passes_min_valid_values]
172169
df_c1 = unnormed_df.loc[:, samples_c1]
173170
df_c2 = unnormed_df.loc[:, samples_c2]
174171

172+
elif valid_values_filter_mode == "both":
173+
min_valid_values_c1 = get_min_valid_values_for_cond(samples_c1, min_valid_values)
174+
min_valid_values_c2 = get_min_valid_values_for_cond(samples_c2, min_valid_values)
175+
df_c1 = unnormed_df.loc[:, samples_c1].dropna(thresh=min_valid_values_c1, axis=0)
176+
df_c2 = unnormed_df.loc[:, samples_c2].dropna(thresh=min_valid_values_c2, axis=0)
177+
178+
elif valid_values_filter_mode == "per_condition":
179+
min_valid_values_c1 = get_min_valid_values_for_cond(samples_c1, min_valid_values_c1)
180+
min_valid_values_c2 = get_min_valid_values_for_cond(samples_c2, min_valid_values_c2)
181+
df_c1 = unnormed_df.loc[:, samples_c1].dropna(thresh=min_valid_values_c1, axis=0)
182+
df_c2 = unnormed_df.loc[:, samples_c2].dropna(thresh=min_valid_values_c2, axis=0)
183+
else:
184+
raise Exception(f"invalid value set for the variable valid_values_filter_mode: {valid_values_filter_mode}, please ensure that is set to: 'either', 'both' or 'per_condition'")
175185

176-
elif minrep_both is not None:
177-
minrep_c1 = minrep_both
178-
minrep_c2 = minrep_both
179-
180-
if (minrep_c1 is not None) and (minrep_c2 is not None):
181-
minrep_c1 = get_minrep_for_cond(samples_c1, minrep_c1)
182-
minrep_c2 = get_minrep_for_cond(samples_c2, minrep_c2)
183-
df_c1 = unnormed_df.loc[:, samples_c1].dropna(thresh=minrep_c1, axis=0)
184-
df_c2 = unnormed_df.loc[:, samples_c2].dropna(thresh=minrep_c2, axis=0)
185-
if (len(df_c1.index)<5) | (len(df_c2.index)<5):
186-
raise Exception(f"condpair has not enough data for processing c1: {len(df_c1.index)} c2: {len(df_c2.index)}, skipping")
187-
188-
if (minrep_both is None) and (minrep_either is None) and (minrep_c1 is None) and (minrep_c2 is None):
189-
raise Exception("no minrep set, please specify!")
190-
191-
186+
if (len(df_c1.index)<5) | (len(df_c2.index)<5):
187+
raise Exception(f"condpair has not enough data for processing c1: {len(df_c1.index)} c2: {len(df_c2.index)}, skipping")
192188

193189
return df_c1, df_c2
194190

195-
def get_minrep_for_cond(c_samples, minrep):
196-
if minrep is None: #in the case of None, no nans will be allowed
191+
def get_min_valid_values_for_cond(c_samples, min_valid_values):
192+
if min_valid_values is None: #in the case of None, no nans will be allowed
197193
return None
198194
num_samples = len(c_samples)
199-
if num_samples<minrep:
195+
if num_samples<min_valid_values:
200196
return num_samples
201197
else:
202-
return minrep
198+
return min_valid_values
203199

204200

205201

‎alphaquant/diffquant/diffutils.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ def get_samplenames_from_input_df(data):
6969

7070
# Cell
7171
import numpy as np
72-
def filter_df_to_minrep(quant_df_wideformat, samples_c1, samples_c2, minrep):
72+
def filter_df_to_min_valid_values(quant_df_wideformat, samples_c1, samples_c2, min_valid_values):
7373
"""filters dataframe in alphaquant format such that each column has a minimum number of replicates
7474
"""
7575
quant_df_wideformat = quant_df_wideformat.replace(0, np.nan)
76-
df_c1_minrep = quant_df_wideformat[samples_c1].dropna(thresh = minrep, axis = 0)
77-
df_c2_minrep = quant_df_wideformat[samples_c2].dropna(thresh = minrep, axis = 0)
78-
idxs_both = df_c1_minrep.index.intersection(df_c2_minrep.index)
76+
df_c1_min_valid_values = quant_df_wideformat[samples_c1].dropna(thresh = min_valid_values, axis = 0)
77+
df_c2_min_valid_values = quant_df_wideformat[samples_c2].dropna(thresh = min_valid_values, axis = 0)
78+
idxs_both = df_c1_min_valid_values.index.intersection(df_c2_min_valid_values.index)
7979
quant_df_reduced = quant_df_wideformat.iloc[idxs_both].reset_index()
8080
return quant_df_reduced
8181

‎alphaquant/ptm/ptmsite_mapping.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ def initialize_ptmsite_df(ptmsite_file, samplemap_file):
673673
ptmsite_df = pd.read_csv(ptmsite_file, sep = "\t")
674674
return ptmsite_df, samplemap_df
675675

676-
def detect_site_occupancy_change(cond1, cond2, ptmsite_df ,samplemap_df, minrep = 2, threshold_prob = 0.05):
676+
def detect_site_occupancy_change(cond1, cond2, ptmsite_df ,samplemap_df, min_valid_values = 2, threshold_prob = 0.05):
677677
"""
678678
uses a PTMsite df with headers "REFPROT", "gene","site", and headers for sample1, sample2, etc and determines
679679
whether a site appears/dissappears between conditions based on some probability threshold
@@ -708,7 +708,7 @@ def detect_site_occupancy_change(cond1, cond2, ptmsite_df ,samplemap_df, minrep
708708
numrep_c1 = len(cond1_vals)
709709
numrep_c2 = len(cond2_vals)
710710

711-
if(numrep_c1<minrep) | (numrep_c2 < minrep):
711+
if(numrep_c1<min_valid_values) | (numrep_c2 < min_valid_values):
712712
continue
713713

714714
cond1_prob = np.mean(cond1_vals)
@@ -741,7 +741,7 @@ def detect_site_occupancy_change(cond1, cond2, ptmsite_df ,samplemap_df, minrep
741741
import numpy as np
742742
import re
743743

744-
def check_site_occupancy_changes_all_diffresults(results_folder = os.path.join(".","results"), siteprobs_filename = "siteprobs.tsv",samplemap_file = "samples.map",condpairs_to_compare = [], threshold_prob = 0.05, minrep = 2):
744+
def check_site_occupancy_changes_all_diffresults(results_folder = os.path.join(".","results"), siteprobs_filename = "siteprobs.tsv",samplemap_file = "samples.map",condpairs_to_compare = [], threshold_prob = 0.05, min_valid_values = 2):
745745

746746
samplemap_df, _ = get_sample2cond_dataframe(samplemap_file)
747747
ptmsite_map = os.path.join(results_folder, siteprobs_filename)
@@ -765,7 +765,7 @@ def check_site_occupancy_changes_all_diffresults(results_folder = os.path.join("
765765
ptmsite_df_cpair = ptmsite_df_cpair.sort_index()
766766

767767
condpairname = utils.get_condpairname(condpair)
768-
df_occupancy = detect_site_occupancy_change(cond1, cond2, ptmsite_df_cpair, samplemap_df, minrep = minrep, threshold_prob = threshold_prob)
768+
df_occupancy = detect_site_occupancy_change(cond1, cond2, ptmsite_df_cpair, samplemap_df, min_valid_values = min_valid_values, threshold_prob = threshold_prob)
769769
df_occupancy.to_csv(os.path.join(results_folder, f"{condpairname}.ptm_occupancy_changes.tsv"), sep = "\t", index = None)
770770

771771

‎alphaquant/run_pipeline.py

+38-9
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ def run_pipeline(input_file: str,
4545
multicond_median_analysis: bool = False,
4646
condpairs_list: Optional[List[Tuple[str, str]]] = None,
4747
file_has_alphaquant_format: bool = False,
48-
minrep_both: int = 2,
49-
minrep_either: Optional[int] = None,
50-
minrep_c1: Optional[int] = None,
51-
minrep_c2: Optional[int] = None,
48+
min_valid_values: int = 2,
49+
valid_values_filter_mode: str = "either", #options: "either", "and", "per_condition"
50+
min_valid_values_c1: int = 0,
51+
min_valid_values_c2: int = 0,
5252
min_num_ions: int = 1,
5353
minpep: int = 1,
5454
organism: Optional[str] = None,
@@ -71,7 +71,12 @@ def run_pipeline(input_file: str,
7171
protein_subset_for_normalization_file: Optional[str] = None,
7272
protnorm_peptides: bool = True,
7373
peptides_to_exclude_file: Optional[str] = None,
74-
reset_progress_folder: bool = False) -> None:
74+
reset_progress_folder: bool = False,
75+
minrep_both: Optional[int] = None, #deprecated
76+
minrep_either: Optional[int] = None, #deprecated
77+
minrep_c1: Optional[int] = None, #deprecated
78+
minrep_c2: Optional[int] = None, #deprecated
79+
) -> None:
7580
"""Run differential analyses following the AlphaQuant pipeline. This function processes proteomics data through multiple steps including
7681
preprocessing, if applicable PTM site mapping, if applicable median condition creation, normalization, statistical testing, visualizations
7782
and writing of results tables.
@@ -86,10 +91,13 @@ def run_pipeline(input_file: str,
8691
multicond_median_analysis (bool): Whether to compare all conditions to a median condition. Defaults to False.
8792
condpairs_list (list): Specific condition pairs to compare. If None, performs all pairwise comparisons.
8893
file_has_alphaquant_format (bool): Whether the input file is already in AlphaQuant matrix format. Defaults to False.
89-
minrep_both (int): Minimum replicate count required in both conditions. Defaults to 2.
90-
minrep_either (int): Minimum replicate count required in either condition.
91-
minrep_c1 (int): Minimum replicate count required in condition 1.
92-
minrep_c2 (int): Minimum replicate count required in condition 2.
94+
min_valid_values (int): Minimum number of valid values required across conditions. Defaults to 2.
95+
valid_values_filter_mode (str): Strategy for filtering based on valid values. Options:
96+
- "either": Include features that have at least 'min_valid_values' valid values in at least one condition.
97+
- "both": Include only features that have at least 'min_valid_values' valid values in all conditions.
98+
- "per_condition": Include only features that have at least 'min_valid_values_c1' valid values in condition 1 and 'min_valid_values_c2' valid values in condition 2.
99+
min_valid_values_c1 (int): Minimum number of valid values required specifically in condition 1.
100+
min_valid_values_c2 (int): Minimum number of valid values required specifically in condition 2.
93101
min_num_ions (int): Minimum number of ions required per peptide. Defaults to 1.
94102
minpep (int): Minimum number of peptides required per protein. Defaults to 1.
95103
organism (str): Organism name for PTM mapping (e.g., 'human', 'mouse'). Required if perform_ptm_mapping is True.
@@ -115,6 +123,27 @@ def run_pipeline(input_file: str,
115123
reset_progress_folder (bool): Clear and recreate the progress folder. Defaults to False.
116124
"""
117125
LOGGER.info("Starting AlphaQuant")
126+
127+
#########################################################
128+
# TODO: this backwards compatibility can be removed beginning of 2026
129+
# to ensure backwards compatibility: in case the minrep paramters are set, we need to convert them to the min_valid_values and valid_values_filter_mode parameters
130+
if minrep_both is not None:
131+
min_valid_values = minrep_both
132+
valid_values_filter_mode = "both"
133+
LOGGER.warning("you set the parameter 'minrep_both', which is deprecated. Please use 'min_valid_values' and 'valid_values_filter_mode' instead.")
134+
if minrep_either is not None:
135+
min_valid_values = minrep_either
136+
valid_values_filter_mode = "either"
137+
LOGGER.warning("you set the parameter 'minrep_either', which is deprecated. Please use 'min_valid_values' and 'valid_values_filter_mode' instead.")
138+
if minrep_c1 is not None and minrep_c2 is not None:
139+
min_valid_values_c1 = minrep_c1
140+
min_valid_values_c2 = minrep_c2
141+
valid_values_filter_mode = "per_condition"
142+
LOGGER.warning("you set the parameter 'minrep_c1' and 'minrep_c2', which is deprecated. Please use 'min_valid_values_c1' and 'min_valid_values_c2' instead.")
143+
#########################################################
144+
145+
146+
118147
input_file_original = input_file
119148
check_input_consistency(input_file_original, samplemap_file, samplemap_df)
120149
create_progress_folder_if_applicable(input_file_original, reset_progress_folder)

‎alphaquant/ui/dashboad_parts_plots_basic.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,12 @@ def _extract_condpairs(self):
176176
self.condpairname_select.options = ["No conditions"]
177177
return
178178

179-
pattern = os.path.join(self.results_dir, "*_VS_*.results.tsv")
179+
# Ensure directory path ends with separator for Windows compatibility
180+
dir_path = self.results_dir
181+
if not dir_path.endswith(os.sep):
182+
dir_path += os.sep
183+
184+
pattern = os.path.join(dir_path, "*_VS_*.results.tsv")
180185
files = glob.glob(pattern)
181186

182187
for f in files:

‎alphaquant/ui/dashboard_parts_run_pipeline.py

+166-72
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,8 @@ def _make_widgets(self):
269269
name='Modification type:',
270270
placeholder='e.g., [Phospho (STY)] for Spectronaut',
271271
width=300,
272-
description=gui_textfields.Descriptions.tooltips['ptm_settings']
272+
description=gui_textfields.Descriptions.tooltips['ptm_settings'],
273+
visible=False # Hidden by default
273274
)
274275
self.input_type = pn.widgets.TextInput(
275276
name='Input type:',
@@ -281,10 +282,11 @@ def _make_widgets(self):
281282
options=['human', 'mouse'],
282283
value='human',
283284
width=300,
284-
description='Select the organism your samples come from'
285+
description='Select the organism your samples come from',
286+
visible=False # Hidden by default
285287
)
286-
self.filtering_options = pn.widgets.Select(
287-
name='Filtering Options:',
288+
self.valid_values_filter_mode = pn.widgets.Select(
289+
name='Filtering options for min. valid values:',
288290
options=[
289291
'min. valid values in condition1 OR condition2',
290292
'min. valid values in condition1 AND condition2',
@@ -295,33 +297,34 @@ def _make_widgets(self):
295297
description=gui_textfields.Descriptions.tooltips['filtering_options']
296298
)
297299

298-
self.minrep_either = pn.widgets.IntInput(
299-
name='Min replicates (either condition):',
300+
self.min_valid_values_OR = pn.widgets.IntInput(
301+
name='Min valid values (either condition):',
300302
value=2,
301303
start=0,
302304
width=300,
303305
description='Minimum number of valid values required in at least one of the conditions'
304306
)
305307

306-
self.minrep_both = pn.widgets.IntInput(
307-
name='Min replicates (both conditions):',
308+
self.min_valid_values_AND = pn.widgets.IntInput(
309+
name='Min valid values (both conditions):',
308310
value=2,
309311
start=1,
310312
width=300,
311-
description='Minimum number of valid values required in both conditions'
313+
description='Minimum number of valid values required in both conditions',
314+
visible=False
312315
)
313316

314-
self.minrep_c1 = pn.widgets.IntInput(
315-
name='Min replicates (condition 1):',
317+
self.min_valid_values_c1 = pn.widgets.IntInput(
318+
name='Min valid values (condition 1):',
316319
value=2,
317320
start=0,
318321
width=300,
319322
description='Minimum number of valid values required in condition 1',
320323
visible=False
321324
)
322325

323-
self.minrep_c2 = pn.widgets.IntInput(
324-
name='Min replicates (condition 2):',
326+
self.min_valid_values_c2 = pn.widgets.IntInput(
327+
name='Min valid values (condition 2):',
325328
value=2,
326329
start=0,
327330
width=300,
@@ -395,45 +398,50 @@ def _make_widgets(self):
395398
)
396399

397400
self.switches = {
398-
'use_ml': pn.widgets.Switch(
401+
'use_ml': pn.widgets.Checkbox(
399402
name='Enable machine learning',
400-
value=True
403+
value=True,
404+
width=300
401405
),
402-
'take_median_ion': pn.widgets.Switch(
406+
'take_median_ion': pn.widgets.Checkbox(
403407
name='Use median-centered ions',
404-
value=True
408+
value=True,
409+
width=300
405410
),
406-
'perform_ptm_mapping': pn.widgets.Switch(
407-
name='Enable PTM mapping',
408-
value=False
411+
'perform_ptm_mapping': pn.widgets.Checkbox(
412+
name='Perform PTM site mapping',
413+
value=False,
414+
width=300
409415
),
410-
'perform_phospho_inference': pn.widgets.Switch(
416+
'perform_phospho_inference': pn.widgets.Checkbox(
411417
name='Enable phospho inference',
412-
value=False
418+
value=False,
419+
width=300
413420
),
414-
'outlier_correction': pn.widgets.Switch(
421+
'outlier_correction': pn.widgets.Checkbox(
415422
name='Enable outlier correction',
416-
value=True
423+
value=True,
424+
width=300
417425
),
418-
'normalize': pn.widgets.Switch(
426+
'normalize': pn.widgets.Checkbox(
419427
name='Enable normalization',
420-
value=True
421-
),
422-
'use_iontree_if_possible': pn.widgets.Switch(
423-
name='Use ion tree when possible',
424-
value=True
428+
value=True,
429+
width=300
425430
),
426-
'write_out_results_tree': pn.widgets.Switch(
431+
'write_out_results_tree': pn.widgets.Checkbox(
427432
name='Write results tree',
428-
value=True
433+
value=True,
434+
width=300
429435
),
430-
'use_multiprocessing': pn.widgets.Switch(
436+
'use_multiprocessing': pn.widgets.Checkbox(
431437
name='Enable multiprocessing',
432-
value=False
438+
value=False,
439+
width=300
433440
),
434-
'runtime_plots': pn.widgets.Switch(
441+
'runtime_plots': pn.widgets.Checkbox(
435442
name='Generate runtime plots',
436-
value=True
443+
value=True,
444+
width=300
437445
),
438446
}
439447

@@ -444,7 +452,6 @@ def _make_widgets(self):
444452
'perform_phospho_inference': pn.pane.Markdown('Infer phosphorylation sites from the data'),
445453
'outlier_correction': pn.pane.Markdown('Automatically detect and correct outliers in the data'),
446454
'normalize': pn.pane.Markdown('Normalize data to account for technical variations'),
447-
'use_iontree_if_possible': pn.pane.Markdown('Use hierarchical ion structure when available'),
448455
'write_out_results_tree': pn.pane.Markdown('Save detailed results in a tree structure'),
449456
'use_multiprocessing': pn.pane.Markdown('Use multiple CPU cores to speed up processing (may use more memory)'),
450457
'runtime_plots': pn.pane.Markdown('Create plots during analysis to visualize the process'),
@@ -494,10 +501,9 @@ def _make_widgets(self):
494501
)
495502
self.samplemap_fileupload.param.watch(self._update_samplemap_table, 'value')
496503
self.samplemap_table.param.watch(self._add_conditions_for_assignment, 'value')
497-
self.minrep_either.param.watch(self._update_minrep_both, 'value')
498504
self.run_pipeline_button.param.watch(self._run_pipeline, 'clicks')
499505
self.analysis_type.param.watch(self._update_analysis_type_visibility, 'value')
500-
self.filtering_options.param.watch(self._toggle_filtering_options, 'value')
506+
self.valid_values_filter_mode.param.watch(self._toggle_filtering_options, 'value')
501507
self.path_output_folder.param.watch(self._update_results_dir, 'value')
502508
self.path_analysis_file.param.watch(self._update_analysis_file, 'value')
503509
self.samplemap_fileupload.param.watch(self._update_samplemap, 'value')
@@ -506,20 +512,57 @@ def _make_widgets(self):
506512
self.assign_cond_pairs.param.watch(self._update_run_button_state, 'value')
507513
self.analysis_type.param.watch(self._update_run_button_state, 'value')
508514

515+
# Add a watcher for the PTM mapping checkbox to show/hide other fields
516+
self.switches['perform_ptm_mapping'].param.watch(self._toggle_ptm_fields, 'value')
509517

510518
def create(self):
511519
"""
512520
Build and return the main layout for the pipeline widget.
513521
"""
514522

515-
ptm_section = pn.Row(
516-
pn.Column(self.modification_type, self.organism)
523+
# Create the PTM section with the checkbox at the top
524+
ptm_section = pn.Column(
525+
self.switches['perform_ptm_mapping'],
526+
pn.pane.Markdown(
527+
"<small><i>" + self.switch_descriptions['perform_ptm_mapping'].object + "</i></small>",
528+
margin=(0, 0, 10, 20)
529+
),
530+
self.modification_type,
531+
self.organism,
532+
margin=(5, 5, 5, 5)
517533
)
518534

519535
filtering_section = pn.Row(
520-
pn.Column(self.filtering_options, self.minrep_either)
536+
pn.Column(
537+
self.valid_values_filter_mode,
538+
self.min_valid_values_OR,
539+
self.min_valid_values_AND,
540+
self.min_valid_values_c1,
541+
self.min_valid_values_c2
542+
)
521543
)
522544

545+
# Create a function to build the checkbox items
546+
def create_checkbox_with_description(key, checkbox):
547+
# Skip the PTM mapping checkbox since it's now in the PTM settings card
548+
if key == 'perform_ptm_mapping':
549+
return None
550+
551+
return pn.Column(
552+
checkbox,
553+
pn.pane.Markdown(
554+
"<small><i>" + self.switch_descriptions[key].object + "</i></small>",
555+
margin=(0, 0, 10, 20)
556+
),
557+
margin=(0, 0, 15, 0),
558+
width=350
559+
)
560+
561+
# Create the checkbox items, filtering out None values
562+
checkbox_items = [create_checkbox_with_description(key, switch)
563+
for key, switch in self.switches.items()]
564+
checkbox_items = [item for item in checkbox_items if item is not None]
565+
523566
advanced_settings_card = pn.Card(
524567
pn.Column(
525568
"### Threshold Settings",
@@ -528,13 +571,7 @@ def create(self):
528571
self.cluster_threshold_pval,
529572
pn.layout.Divider(),
530573
"### Analysis Options",
531-
pn.Column(*[
532-
pn.Row(
533-
switch,
534-
self.switch_descriptions[key],
535-
align='center'
536-
) for key, switch in self.switches.items()
537-
]),
574+
*checkbox_items,
538575
),
539576
title='Advanced Configuration',
540577
collapsed=True,
@@ -543,6 +580,16 @@ def create(self):
543580
width=400
544581
)
545582

583+
# Create PTM settings card with fixed width
584+
ptm_settings_card = pn.Card(
585+
ptm_section,
586+
title='PTM Settings',
587+
collapsed=True,
588+
margin=(5, 5, 5, 5),
589+
sizing_mode='fixed',
590+
width=400
591+
)
592+
546593
# Create samples and conditions layout
547594
samples_conditions_layout = pn.Column(
548595
self.sample_mapping_mode_container,
@@ -562,16 +609,6 @@ def create(self):
562609
self.medianref_message,
563610
)
564611

565-
# Create PTM settings card with fixed width
566-
ptm_settings_card = pn.Card(
567-
ptm_section,
568-
title='PTM Settings',
569-
collapsed=True,
570-
margin=(5, 5, 5, 5),
571-
sizing_mode='fixed',
572-
width=400
573-
)
574-
575612
main_col = pn.Column(
576613
"### Input Files",
577614
self.path_analysis_file,
@@ -729,8 +766,7 @@ def _run_pipeline(self, *events):
729766
for pair in self.assign_cond_pairs.value
730767
]
731768

732-
# Log samplemap status right before passing to pipeline
733-
print(f"Samplemap right before pipeline run: {'Present with ' + str(len(self.samplemap_table.value)) + ' rows' if self.samplemap_table.value is not None else 'None'}")
769+
734770

735771
# Collect all configuration parameters
736772
pipeline_params = {
@@ -748,6 +784,20 @@ def _run_pipeline(self, *events):
748784
'volcano_fdr': self.volcano_fdr.value,
749785
'volcano_fcthresh': self.volcano_fcthresh.value,
750786
'multicond_median_analysis': is_median_analysis,
787+
"valid_values_filter_mode": self._translate_filter_mode_for_backend(),
788+
"min_valid_values": self._get_min_valid_values(),
789+
"min_valid_values_c1": self.min_valid_values_c1.value if self.valid_values_filter_mode.value == 'set min. valid values per condition' else None,
790+
"min_valid_values_c2": self.min_valid_values_c2.value if self.valid_values_filter_mode.value == 'set min. valid values per condition' else None,
791+
# Add the switch values to the pipeline parameters
792+
'use_ml': self.switches['use_ml'].value,
793+
'take_median_ion': self.switches['take_median_ion'].value,
794+
'perform_ptm_mapping': self.switches['perform_ptm_mapping'].value,
795+
'perform_phospho_inference': self.switches['perform_phospho_inference'].value,
796+
'outlier_correction': self.switches['outlier_correction'].value,
797+
'normalize': self.switches['normalize'].value,
798+
'write_out_results_tree': self.switches['write_out_results_tree'].value,
799+
'use_multiprocessing': self.switches['use_multiprocessing'].value,
800+
'runtime_plots': self.switches['runtime_plots'].value,
751801
}
752802

753803
# Log key parameters
@@ -1108,9 +1158,6 @@ def _check_condition_progress(self):
11081158
import traceback
11091159
traceback.print_exc()
11101160

1111-
def _update_minrep_both(self, *events):
1112-
"""Set minrep_both to 0 when minrep_either is changed."""
1113-
self.minrep_both.value = 0
11141161

11151162
def _update_results_dir(self, event):
11161163
"""Update central state with new results directory."""
@@ -1236,19 +1283,19 @@ def _update_analysis_type_visibility(self, change=None):
12361283
def _toggle_filtering_options(self, event):
12371284
"""Toggle visibility of replicate input fields based on filtering option."""
12381285
# Hide all first
1239-
self.minrep_either.visible = False
1240-
self.minrep_both.visible = False
1241-
self.minrep_c1.visible = False
1242-
self.minrep_c2.visible = False
1286+
self.min_valid_values_OR.visible = False
1287+
self.min_valid_values_AND.visible = False
1288+
self.min_valid_values_c1.visible = False
1289+
self.min_valid_values_c2.visible = False
12431290

12441291
# Show relevant widgets based on selection
12451292
if event.new == 'min. valid values in condition1 OR condition2':
1246-
self.minrep_either.visible = True
1293+
self.min_valid_values_OR.visible = True
12471294
elif event.new == 'min. valid values in condition1 AND condition2':
1248-
self.minrep_both.visible = True
1295+
self.min_valid_values_AND.visible = True
12491296
else: # set min. valid values per condition
1250-
self.minrep_c1.visible = True
1251-
self.minrep_c2.visible = True
1297+
self.min_valid_values_c1.visible = True
1298+
self.min_valid_values_c2.visible = True
12521299

12531300
def _update_console(self):
12541301
"""Update the console output widget with new log messages."""
@@ -1292,6 +1339,53 @@ def _update_run_button_state(self, event=None):
12921339
self.run_pipeline_button.disabled = True
12931340
self.run_pipeline_button.description = 'Please select an analysis type'
12941341

1342+
def _get_min_valid_values(self):
1343+
"""
1344+
Return the appropriate min_valid_values based on the selected filter mode.
1345+
"""
1346+
filter_mode = self.valid_values_filter_mode.value
1347+
1348+
print(f"Getting min_valid_values with UI filter_mode: {filter_mode}")
1349+
1350+
if filter_mode == 'min. valid values in condition1 OR condition2':
1351+
min_val = self.min_valid_values_OR.value
1352+
print(f"Using OR mode with value: {min_val}")
1353+
return min_val
1354+
elif filter_mode == 'min. valid values in condition1 AND condition2':
1355+
min_val = self.min_valid_values_AND.value
1356+
print(f"Using AND mode with value: {min_val}")
1357+
return min_val
1358+
else: # 'set min. valid values per condition'
1359+
# When using per-condition values, return None for the general min_valid_values
1360+
print("Using per-condition mode, returning None")
1361+
return None
1362+
1363+
def _translate_filter_mode_for_backend(self):
1364+
"""
1365+
Translate the UI filter mode option to the corresponding backend parameter value.
1366+
"""
1367+
ui_mode = self.valid_values_filter_mode.value
1368+
1369+
# Map UI options to backend values
1370+
mode_mapping = {
1371+
'min. valid values in condition1 OR condition2': 'either',
1372+
'min. valid values in condition1 AND condition2': 'both',
1373+
'set min. valid values per condition': 'per_condition'
1374+
}
1375+
1376+
backend_mode = mode_mapping.get(ui_mode, 'either') # Default to 'either' if not found
1377+
print(f"Translating UI filter mode '{ui_mode}' to backend mode '{backend_mode}'")
1378+
return backend_mode
1379+
1380+
def _toggle_ptm_fields(self, event):
1381+
"""Toggle visibility of PTM-related fields based on the PTM mapping checkbox."""
1382+
if event.new:
1383+
self.modification_type.visible = True
1384+
self.organism.visible = True
1385+
else:
1386+
self.modification_type.visible = False
1387+
self.organism.visible = False
1388+
12951389
class Tabs(param.Parameterized):
12961390
"""
12971391
This class creates a single pn.Tabs layout containing:

‎alphaquant/ui/gui_textfields.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ class Descriptions():
210210

211211
'ptm_settings': """For Spectronaut PTM analysis:
212212
1. Modification Type: Specify exactly as it appears in Spectronaut modified sequence
213-
Example: '[Phospho(STY)]' for phosphorylation
213+
Example: '[Phospho (STY)]' for phosphorylation
214214
2. Organism: Select proteome for site mapping (human/mouse available at the moment)
215215
216216
Note: Requires Spectronaut table with correct PTM columns (see table instructions)."""

‎example_nbs/differential_expression.ipynb

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"source": [
5656
"Now we can use the 'run_pipeline' command from AlphaQuant. This performs normalization between sample and then differential expression testing. The main output is a table of proteins with fold changes and an fdr value as a result of the differential expression test. Due to AlphaQuant's tree based approach, sensitivity is in general higher as compared to standard approaches like the t-test, i.e. there should be more proteins with significant fdr, especially for lower fold changes.\n",
5757
"\n",
58-
"Important: We analyze only proteins with highly complete data across both conditions by setting minrep_both = 7, requiring seven valid values out of 10 possible replicates in each condition. To include proteins that may be absent in one condition, use minrep_either = 7 instead, which requires seven valid values in at least one condition while allowing any number of values in the other."
58+
"Important: We analyze only proteins with highly complete data across both conditions by setting min_valid_values = 7, requiring seven valid values out of 10 possible replicates in each condition. To include proteins that may be absent in one condition. Additionally, we set valid_values_filter_mode = \"both\", which means that seven valid values need to be in both conditions for a protein to be included. If you want to include proteins completely missing in one condition, set set valid_values_filter_mode = \"either\", which requires seven valid values in at least one condition while allowing any number of values in the other."
5959
]
6060
},
6161
{
@@ -67,7 +67,7 @@
6767
"import alphaquant.run_pipeline as aq_pipeline\n",
6868
"\n",
6969
"aq_pipeline.run_pipeline(input_file=INPUT_FILE, samplemap_file=SAMPLEMAP_FILE,\n",
70-
" condpairs_list=CONDPAIRS_TO_COMPARE, results_dir=RESULTS_DIRECTORY, minrep_both=7)"
70+
" condpairs_list=CONDPAIRS_TO_COMPARE, results_dir=RESULTS_DIRECTORY, min_valid_values=7, valid_values_filter_mode=\"both\")"
7171
]
7272
},
7373
{

‎tests/e2e_tests_large/spectronaut_mixed_species.ipynb

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535
"source": [
3636
"import alphaquant.run_pipeline as run_pipeline\n",
3737
"\n",
38-
"run_pipeline.run_pipeline(input_file=INPUT_FILE, samplemap_file=SAMPLEMAP_FILE, results_dir=RESULTS_DIR, runtime_plots=True, protein_subset_for_normalization_file=PROTEIN_SUBSET_FOR_NORMALIZATION, \n",
38+
"run_pipeline.run_pipeline(input_file=INPUT_FILE, samplemap_file=SAMPLEMAP_FILE, results_dir=RESULTS_DIR, runtime_plots=True, protein_subset_for_normalization_file=PROTEIN_SUBSET_FOR_NORMALIZATION,\n",
3939
" annotation_columns=[\"PG.Organisms\"], condpairs_list=[CONDPAIR], peptides_to_exclude_file=SHARED_PEPTIDES_BETWEEN_SPECIES_FILE, input_type_to_use=\"spectronaut_fragion_isotopes_protein\",\n",
40-
" minrep_either=2)"
40+
" min_valid_values=2)"
4141
]
4242
},
4343
{

‎tests/e2e_tests_small/mixed_species.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"source": [
4747
"import alphaquant.run_pipeline as run_pipeline\n",
4848
"\n",
49-
"run_pipeline.run_pipeline(input_file=INPUT_FILE, samplemap_file=SAMPLEMAP, results_dir=RESULTS_DIR, runtime_plots=True, minrep_either= 2, take_median_ion= True,\n",
49+
"run_pipeline.run_pipeline(input_file=INPUT_FILE, samplemap_file=SAMPLEMAP, results_dir=RESULTS_DIR, runtime_plots=True, min_valid_values= 2, take_median_ion= True,\n",
5050
" annotation_columns=[\"PG.Genes\", \"PG.Organisms\"], input_type_to_use= \"spectronaut_fragion_ms1_protein\", peptides_to_exclude_file=SHARED_PEPTIDES_BETWEEN_SPECIES_FILE)"
5151
]
5252
},

‎tests/e2e_tests_small/multi_condition.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"source": [
5858
"import alphaquant.run_pipeline as run_pipeline\n",
5959
"\n",
60-
"run_pipeline.run_pipeline(input_file=INPUT_FILE, samplemap_file=SAMPLEMAP, results_dir=RESULTS_DIR, runtime_plots=True, minrep_either= 2, take_median_ion= True, multicond_median_analysis=True,\n",
60+
"run_pipeline.run_pipeline(input_file=INPUT_FILE, samplemap_file=SAMPLEMAP, results_dir=RESULTS_DIR, runtime_plots=True, min_valid_values= 2, take_median_ion= True, multicond_median_analysis=True,\n",
6161
" annotation_columns=[\"PG.Genes\", \"PG.Organisms\"], input_type_to_use= \"spectronaut_fragion_ms1_protein\", peptides_to_exclude_file=SHARED_PEPTIDES_BETWEEN_SPECIES_FILE)"
6262
]
6363
},

‎tests/e2e_tests_small/phospho.ipynb

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"source": [
5959
"import alphaquant.run_pipeline as aq_run_pipeline\n",
6060
"\n",
61-
"aq_run_pipeline.run_pipeline(input_file=INPUT_FILE, samplemap_file=SAMPLEMAP_FILE, results_dir=RESULTS_DIR, minrep_both=2, modification_type=\"[Phospho (STY)]\",\n",
61+
"aq_run_pipeline.run_pipeline(input_file=INPUT_FILE, samplemap_file=SAMPLEMAP_FILE, results_dir=RESULTS_DIR, min_valid_values=2, modification_type=\"[Phospho (STY)]\",\n",
6262
" perform_ptm_mapping=True,organism=\"human\", runtime_plots=True,peptides_to_exclude_file=PEPTIDES_TO_REMOVE, normalize=True)"
6363
]
6464
},
@@ -91,7 +91,7 @@
9191
"name": "python",
9292
"nbconvert_exporter": "python",
9393
"pygments_lexer": "ipython3",
94-
"version": "3.11.11"
94+
"version": "3.11.0"
9595
}
9696
},
9797
"nbformat": 4,

0 commit comments

Comments
 (0)
Please sign in to comment.