ScreenPro2 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ """
2
+ annotate module
3
+ """
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ hit_dict = {
9
+ 'gamma':{
10
+ 'up_hit':'up_hit',
11
+ 'down_hit':'essential_hit'
12
+ },
13
+ 'tau':{
14
+ 'up_hit':'up_hit',
15
+ 'down_hit':'down_hit'
16
+ },
17
+ 'rho':{
18
+ 'up_hit':'resistance_hit',
19
+ 'down_hit':'sensitivity_hit'
20
+ },
21
+ 'delta':{
22
+ 'up_hit':'up_hit',
23
+ 'down_hit':'down_hit'
24
+ }
25
+ }
26
+
27
+
28
+ def getCombinedScore(df_in, score_col='score', pvalue_col='pvalue', target_col='target', ctrl_label='negative_control'):
29
+ """
30
+ Calculate the combined score column based on the given phenotypic scores and p-values.
31
+ Combined score is calculated as:
32
+
33
+ $combined\_score = \frac{score}{pseudo\_sd} \times -\log_{10}(pvalue)$
34
+
35
+ Parameters:
36
+ df_in (pandas.DataFrame): The input DataFrame.
37
+ score_col (str): The column name for the individual scores. Default is 'score'.
38
+ pvalue_col (str): The column name for the p-values. Default is 'pvalue'.
39
+ target_col (str): The column name for the target variable. Default is 'target'.
40
+ combined_score_col (str): The column name for the combined scores. Default is 'combined_score'.
41
+ ctrl_label (str): The label for the control group. Default is 'control'.
42
+
43
+ Returns:
44
+ pandas.Series: The calculated combined score column.
45
+ """
46
+ # make a copy of input dataframe
47
+ df = df_in.copy()
48
+
49
+ for col in [score_col, pvalue_col, target_col]:
50
+ if col not in df.columns:
51
+ raise ValueError(f'Column "{col}" not found in the input DataFrame.')
52
+
53
+ # calculate pseudo_sd
54
+ pseudo_sd = df[df[target_col].eq(ctrl_label)][score_col].tolist()
55
+ pseudo_sd = np.std(pseudo_sd)
56
+
57
+ # calculate combined score
58
+ return df[score_col]/pseudo_sd * -np.log10(df[pvalue_col])
59
+
60
+
61
+ def annotateScoreTable(df_in, up_hit, down_hit, threshold, score_col='score', pvalue_col='pvalue', target_col='target', ctrl_label='negative_control'):
62
+ """
63
+ Annotate the given score tabel
64
+
65
+
66
+ Parameters:
67
+ df_in (pd.DataFrame): score dataframe
68
+ up_hit (str): up hit label
69
+ down_hit (str): down hit label
70
+ threshold (int): threshold value
71
+ score_col (str): score column name. Default is 'score'.
72
+ target_col (str): column name for the target variable. Default is 'target'.
73
+ pvalue_col (str): pvalue column name. Default is 'pvalue'.
74
+ ctrl_label (str): control label value. Default is 'negative_control'.
75
+
76
+ Returns:
77
+ pd.DataFrame: annotated score dataframe
78
+ """
79
+ # make a copy of input dataframe
80
+ df = df_in.copy()
81
+
82
+ for col in [score_col, pvalue_col, target_col]:
83
+ if col not in df.columns:
84
+ raise ValueError(f'Column "{col}" not found in the input DataFrame.')
85
+
86
+ df[score_col] = df[score_col].astype(float)
87
+ df[pvalue_col] = df[pvalue_col].astype(float)
88
+
89
+ # add combined score column
90
+ df['combined_score'] = getCombinedScore(
91
+ df,
92
+ score_col=score_col, pvalue_col=pvalue_col, target_col=target_col,
93
+ ctrl_label=ctrl_label)
94
+
95
+ # add label column
96
+ df['label'] = '.'
97
+
98
+ # annotate hits: up
99
+ df.loc[
100
+ (df[score_col] > 0) & (~df[target_col].eq(ctrl_label)) &
101
+ (df['combined_score'] >= threshold), 'label'
102
+ ] = up_hit
103
+
104
+ # annotate hits: down
105
+ df.loc[
106
+ (df[score_col] < 0) & (~df[target_col].eq(ctrl_label)) &
107
+ (df['combined_score'] <= -threshold), 'label'
108
+ ] = down_hit
109
+
110
+ # annotate control
111
+ df.loc[df[target_col].eq(ctrl_label), 'label'] = ctrl_label
112
+
113
+ # annotate non-hit
114
+ df.loc[df['label'] == '.', 'label'] = 'target_non_hit'
115
+
116
+ # reorder factors
117
+ df['label'] = pd.Categorical(
118
+ df['label'],
119
+ categories=[down_hit, up_hit, ctrl_label, 'target_non_hit']
120
+ )
121
+
122
+ return df
@@ -0,0 +1,375 @@
1
+ """
2
+ delta module
3
+ """
4
+ import numpy as np
5
+ import anndata as ad
6
+ import pandas as pd
7
+
8
+ from .phenostat import (
9
+ matrixStat, multipleTestsCorrection
10
+ )
11
+
12
+
13
+ ### Key functions for calculating delta phenotype score
14
+
15
+ def compareByReplicates(adata, df_cond_ref, df_cond_test, var_names='target', test='ttest', ctrl_label='negative_control', growth_rate=1, filter_type='mean', filter_threshold=40):
16
+ """Calculate phenotype score and p-values comparing `cond_test` vs `cond_ref`.
17
+
18
+ In this function, the phenotype calculation is done by comparing multiple replicates of `cond_test` vs `cond_ref`.
19
+
20
+ Args:
21
+ adata (AnnData): AnnData object
22
+ df_cond_ref (pd.DataFrame): dataframe of condition reference
23
+ df_cond_test (pd.DataFrame): dataframe of condition test
24
+ var_names (str): variable names to use as index in the result dataframe
25
+ test (str): test to use for calculating p-value ('MW': Mann-Whitney U rank; 'ttest' : t-test)
26
+ ctrl_label (str): control label, default is 'negative_control'
27
+ growth_rate (int): growth rate
28
+ filter_type (str): filter type to apply to low counts ('mean', 'both', 'either')
29
+ filter_threshold (int): filter threshold for low counts (default is 40)
30
+
31
+ Returns:
32
+ pd.DataFrame: result dataframe
33
+ """
34
+ adat = adata.copy()
35
+
36
+ # apply NA to low counts
37
+ df_cond_ref, df_cond_test = applyNAtoLowCounts(
38
+ df_cond_ref=df_cond_ref, df_cond_test=df_cond_test,
39
+ filter_type=filter_type,
40
+ filter_threshold=filter_threshold
41
+ )
42
+
43
+ # convert to numpy arrays
44
+ x = df_cond_ref.to_numpy()
45
+ y = df_cond_test.to_numpy()
46
+
47
+ # get control values
48
+ x_ctrl = df_cond_ref[adat.var.targetType.eq(ctrl_label)].dropna().to_numpy()
49
+ y_ctrl = df_cond_test[adat.var.targetType.eq(ctrl_label)].dropna().to_numpy()
50
+
51
+ # calculate phenotype scores
52
+ scores = calculateDelta(
53
+ x = x, y = y,
54
+ x_ctrl = x_ctrl, y_ctrl = y_ctrl,
55
+ growth_rate = growth_rate,
56
+ )
57
+
58
+ # average scores across replicates
59
+ scores = [np.mean(s) for s in scores]
60
+
61
+ # compute p-value
62
+ p_values = matrixStat(x, y, test=test, level = 'col')
63
+
64
+ # get adjusted p-values
65
+ adj_p_values = multipleTestsCorrection(p_values)
66
+
67
+ # get target information
68
+ targets_df = adat.var[var_names].copy()
69
+
70
+ # combine results into a dataframe
71
+ result = pd.concat([
72
+ pd.Series(scores, index=adat.var.index, name='score'),
73
+ pd.Series(p_values, index=adat.var.index, name=f'{test} pvalue'),
74
+ pd.Series(adj_p_values, index=adat.var.index, name='BH adj_pvalue'),
75
+ ], axis=1)
76
+
77
+ # add targets information
78
+ result = pd.concat([targets_df, result], axis=1)
79
+
80
+ return result
81
+
82
+
83
+ def compareByTargetGroup(adata, df_cond_ref, df_cond_test, keep_top_n, var_names='target', test='ttest', ctrl_label='negative_control', growth_rate=1, filter_type='mean', filter_threshold=40):
84
+ """Calculate phenotype score and p-values comparing `cond_test` vs `cond_ref`.
85
+
86
+ In this function, the phenotype calculation is done by comparing groups of
87
+ guide elements (e.g. sgRNAs) that target the same gene or groups of pseudogene (i.e.
88
+ subsampled groups of non-targeting control elements) between `cond_test` vs `cond_ref`.
89
+
90
+ Args:
91
+ adata (AnnData): AnnData object
92
+ df_cond_ref (pd.DataFrame): dataframe of condition reference
93
+ df_cond_test (pd.DataFrame): dataframe of condition test
94
+ keep_top_n (int): number of top guide elements to keep
95
+ var_names (str): variable names to use as index in the result dataframe
96
+ test (str): test to use for calculating p-value ('MW': Mann-Whitney U rank; 'ttest' : t-test)
97
+ ctrl_label (str): control label, default is 'negative_control'
98
+ growth_rate (int): growth rate
99
+ filter_type (str): filter type to apply to low counts ('mean', 'both', 'either')
100
+ filter_threshold (int): filter threshold for low counts (default is 40)
101
+
102
+ Returns:
103
+ pd.DataFrame: result dataframe
104
+ """
105
+
106
+ adat = adata.copy()
107
+
108
+ # apply NA to low counts
109
+ df_cond_ref, df_cond_test = applyNAtoLowCounts(
110
+ df_cond_ref=df_cond_ref, df_cond_test=df_cond_test,
111
+ filter_type=filter_type,
112
+ filter_threshold=filter_threshold
113
+ )
114
+
115
+ # get control values
116
+ x_ctrl = df_cond_ref[adat.var.targetType.eq(ctrl_label)].dropna().to_numpy()
117
+ y_ctrl = df_cond_test[adat.var.targetType.eq(ctrl_label)].dropna().to_numpy()
118
+
119
+ targets = []
120
+ scores = []
121
+ p_values = []
122
+ target_sizes = []
123
+
124
+ # group by target genes or pseudogenes to aggregate counts for score calculation
125
+ for target_name, target_group in adat.var.groupby(var_names):
126
+
127
+ # calculate phenotype scores and p-values for each target group
128
+ target_score, target_p_value, target_size = scoreTargetGroup(
129
+ target_group=target_group,
130
+ df_cond_ref=df_cond_ref,
131
+ df_cond_test=df_cond_test,
132
+ x_ctrl=x_ctrl, y_ctrl=y_ctrl,
133
+ test=test, growth_rate=growth_rate,
134
+ keep_top_n=keep_top_n
135
+ )
136
+
137
+ scores.append(target_score)
138
+ p_values.append(target_p_value)
139
+ targets.append(target_name)
140
+ target_sizes.append(target_size)
141
+
142
+ # average scores across replicates
143
+ scores = [np.mean(s) for s in scores]
144
+
145
+ # get adjusted p-values
146
+ adj_p_values = multipleTestsCorrection(np.array(p_values))
147
+
148
+ # get target information
149
+ if type(var_names) == str:
150
+ targets_df = pd.DataFrame(targets, columns=[var_names])
151
+ elif type(var_names) == list:
152
+ targets_df = pd.DataFrame(targets, columns=var_names)
153
+
154
+ # combine results into a dataframe
155
+ result = pd.concat([
156
+ pd.Series(scores, name='score', dtype=float),
157
+ pd.Series(p_values, name=f'{test} pvalue', dtype=float),
158
+ pd.Series(adj_p_values, name='BH adj_pvalue', dtype=float),
159
+ pd.Series(target_sizes, name='number_of_guide_elements', dtype=int),
160
+ ], axis=1)
161
+
162
+ # add targets information
163
+ result = pd.concat([targets_df, result], axis=1)
164
+
165
+ # set index to var_names
166
+ if type(var_names) == list and len(var_names) > 1:
167
+ result.index = result[var_names].agg('-'.join, axis=1)
168
+ else:
169
+ result.index = result[var_names]
170
+
171
+ return result
172
+
173
+
174
+ def getPhenotypeData(adata, score_tag, cond_ref, cond_test, growth_rate_reps=None, ctrl_label='negative_control'):
175
+ """Calculate phenotype score for each pair of replicates
176
+
177
+ Args:
178
+ adata (AnnData): AnnData object
179
+ score_tag (str): score tag. e.g. 'delta', 'gamma', 'tau', 'rho'.
180
+ cond_ref (str): condition reference
181
+ cond_test (str): condition test
182
+ growth_rate_reps (dict): growth rate for each replicate. Key is replicate number, value is growth rate.
183
+ ctrl_label (str): control label, default is 'negative_control'
184
+ """
185
+ score_name = f'{score_tag}:{cond_test}_vs_{cond_ref}'
186
+
187
+ adat = adata.copy()
188
+
189
+ adat_ctrl = adat[:, adat.var.targetType.eq(ctrl_label)].copy()
190
+
191
+ results = {}
192
+
193
+ if growth_rate_reps is None:
194
+ growth_rate_reps = dict([(replicate, 1) for replicate in adat.obs.replicate.unique()])
195
+
196
+ for replicate in adat.obs.replicate.unique():
197
+ x=adat[adat.obs.query(f'condition == "{cond_ref}" & replicate == {str(replicate)}').index].X.T
198
+ y=adat[adat.obs.query(f'condition == "{cond_test}" & replicate == {str(replicate)}').index].X.T
199
+
200
+ x_ctrl=adat_ctrl[adat_ctrl.obs.query(f'condition == "{cond_ref}" & replicate == {str(replicate)}').index].X.T
201
+ y_ctrl=adat_ctrl[adat_ctrl.obs.query(f'condition == "{cond_test}" & replicate == {str(replicate)}').index].X.T
202
+
203
+ res = calculateDelta(
204
+ x=x,y=y,x_ctrl=x_ctrl,y_ctrl=y_ctrl,
205
+ growth_rate=growth_rate_reps[replicate],
206
+ )
207
+
208
+ results.update({f'{score_name}::replicate_{replicate}': res.reshape(-1)})
209
+
210
+ out = ad.AnnData(
211
+ pd.DataFrame(results, index=adat.var.index).T
212
+ )
213
+
214
+ return out
215
+
216
+
217
+ def calculateDelta(x, y, x_ctrl, y_ctrl, growth_rate):
218
+ """Calculate phenotype score normalized by negative control and growth rate.
219
+
220
+ Args:
221
+ x (np.array): array of values
222
+ y (np.array): array of values
223
+ x_ctrl (np.array): array of values
224
+ y_ctrl (np.array): array of values
225
+ growth_rate (int): growth rate
226
+
227
+ Returns:
228
+ np.array: array of scores
229
+ """
230
+ # calculate control median and std
231
+ ctrl_median = np.median(
232
+ calculateLog2e(x=x_ctrl, y=y_ctrl),
233
+ axis=0 # for each individual sample (i.e. replicate)
234
+ )
235
+
236
+ # calculate log2e (i.e. log2 fold change enrichment y / x)
237
+ log2e = calculateLog2e(x=x, y=y)
238
+
239
+ # calculate delta score normalized by control median and growth rate
240
+ delta = (log2e - ctrl_median) / growth_rate
241
+
242
+ return delta
243
+
244
+
245
+ ### Utility functions
246
+
247
+ def calculateLog2e(x, y):
248
+ return np.log2(y) - np.log2(x)
249
+
250
+
251
+ def averageBestN(scores, numToAverage):
252
+ # Sort and find top n guide per target, see #18
253
+ return np.mean(sorted(scores, key=abs, reverse=True)[:numToAverage])
254
+
255
+
256
+ def getBestTargetByTSS(score_df,target_col,pvalue_col):
257
+ """
258
+ collapse the gene-transcript indices into a single score for a gene by best p-value
259
+ """
260
+ return score_df.dropna().groupby(target_col).apply(
261
+ lambda x: x.loc[x[pvalue_col].idxmin()]
262
+ )
263
+
264
+
265
+ def scoreTargetGroup(target_group, df_cond_ref, df_cond_test, x_ctrl, y_ctrl, test='ttest', growth_rate=1, keep_top_n=None):
266
+ # select target group and convert to numpy arrays
267
+ x = df_cond_ref.loc[target_group.index,:].dropna().to_numpy()
268
+ y = df_cond_test.loc[target_group.index,:].dropna().to_numpy()
269
+
270
+ # calculate phenotype scores
271
+ target_scores = calculateDelta(
272
+ x = x, y = y,
273
+ x_ctrl = x_ctrl, y_ctrl = y_ctrl,
274
+ growth_rate = growth_rate,
275
+ )
276
+
277
+ # get target size
278
+ target_size = target_scores.shape[0] # number of guide elements in the target group
279
+
280
+ if target_size == 0:
281
+ target_score = np.full(target_scores.shape[1], np.nan)
282
+
283
+ elif (keep_top_n is None or keep_top_n is False) or target_size <= keep_top_n:
284
+ # average scores across guides
285
+ target_score = np.mean(target_scores, axis=0)
286
+
287
+ elif keep_top_n > 0 or target_size > keep_top_n:
288
+ # get top n scores per target
289
+ target_score = np.apply_along_axis(averageBestN, axis=0, arr=target_scores, numToAverage=keep_top_n)
290
+ target_size = keep_top_n # update target size to keep_top_n
291
+
292
+ else:
293
+ raise ValueError(f'Invalid value for keep_top_n: {keep_top_n}')
294
+
295
+ # compute p-value
296
+ target_p_value = matrixStat(x, y, test=test, level='all')
297
+
298
+ return target_score, target_p_value, target_size
299
+
300
+
301
+ def generatePseudoGeneAnnData(adata, num_pseudogenes='auto', pseudogene_size='auto', ctrl_label='negative_control'):
302
+ """Generate pseudogenes from negative control elements in the library.
303
+
304
+ Args:
305
+ adata (AnnData): AnnData object
306
+ num_pseudogenes (int): number of pseudogenes to generate
307
+ pseudogene_size (int): number of sgRNA elements in each pseudogene
308
+ ctrl_label (str): control label, default is 'negative_control'
309
+
310
+ Returns:
311
+ AnnData: AnnData object with pseudogenes
312
+ """
313
+ #TODO: check for `target` and `targetType` columns in adata.var
314
+ #TODO: add input arg to replace "target" and name it `target_col` or something similar
315
+
316
+ if pseudogene_size == 'auto':
317
+ # sgRNA elements / target in the library
318
+ pseudogene_size = int(adata.var[~adata.var.targetType.eq(ctrl_label)].groupby('target').size().mean())
319
+
320
+ if num_pseudogenes == 'auto':
321
+ # approx number of target in the library
322
+ num_pseudogenes = len(adata.var.loc[~adata.var.targetType.eq(ctrl_label),'target'].unique()) * pseudogene_size
323
+
324
+ adata_ctrl = adata[:,adata.var.targetType.eq(ctrl_label)].copy()
325
+ ctrl_elements = adata_ctrl.var.index.to_list()
326
+
327
+ adata_pseudo_list = []
328
+ pseudo_source_sgrna = []
329
+
330
+ for pseudo_num in range(0, num_pseudogenes, pseudogene_size):
331
+ pseudo_elements = np.random.choice(ctrl_elements, pseudogene_size, replace=False)
332
+ pseudo_labels = [f'pseudo_{pseudo_num}_{i}' for i in range(1,pseudogene_size+1)]
333
+
334
+ adata_pseudo = ad.AnnData(
335
+ X = adata_ctrl.X[:,adata_ctrl.var.index.isin(pseudo_elements)],
336
+ obs = adata_ctrl.obs
337
+ )
338
+ adata_pseudo.var_names = pseudo_labels
339
+ adata_pseudo_list.append(adata_pseudo)
340
+
341
+ for element in pseudo_elements:
342
+ pseudo_source_sgrna.append(element)
343
+
344
+ out = ad.concat(adata_pseudo_list, axis=1)
345
+ out.var['target'] = out.var.index.str.split('_').str[:-1].str.join('_')
346
+ out.var['targetType'] = ctrl_label
347
+ out.var['source'] = pseudo_source_sgrna
348
+ out.obs = adata_ctrl.obs.copy()
349
+
350
+ return out
351
+
352
+
353
+ def applyNAtoLowCounts(df_cond_ref, df_cond_test, filter_type, filter_threshold):
354
+ # more flexible read filtering by adding np.nan scores and/or pvalues to low count rows
355
+ # keep row if either both/all columns are above threshold, or if either/any column is
356
+ # in other words, mask if any column is below threshold or only if all columns are below
357
+ # source https://github.com/mhorlbeck/ScreenProcessing/blob/master/process_experiments.py#L464C1-L478C1
358
+
359
+ df = pd.concat({'ref':df_cond_ref, 'test':df_cond_test},axis=1)
360
+
361
+ if filter_type == 'mean':
362
+ filter = df.apply(
363
+ lambda row: np.mean(row) < filter_threshold, axis=1)
364
+ elif filter_type == 'both' or filter_type == 'all':
365
+ filter = df.apply(
366
+ lambda row: min(row) < filter_threshold, axis=1)
367
+ elif filter_type == 'either' or filter_type == 'any':
368
+ filter = df.apply(
369
+ lambda row: max(row) < filter_threshold, axis=1)
370
+ else:
371
+ raise ValueError('filter type not recognized or not implemented')
372
+
373
+ df.loc[filter, :] = np.nan
374
+
375
+ return df['ref'], df['test']
@@ -0,0 +1,57 @@
1
+ """
2
+ deseq module: adapt pyDESeq2 for use in ScreenPro2 package
3
+ """
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ import anndata as ad
8
+ import os, contextlib
9
+
10
+ from pydeseq2.dds import DeseqDataSet
11
+ from pydeseq2.default_inference import DefaultInference
12
+ from pydeseq2.ds import DeseqStats
13
+
14
+
15
+ def runDESeq(adata, design, n_cpus=8,quiet=False):
16
+
17
+ inference = DefaultInference(n_cpus=n_cpus)
18
+
19
+ print(f'\tcreating `dds` object...')
20
+
21
+ dds = DeseqDataSet(
22
+ counts=adata.to_df().astype(int),
23
+ metadata=adata.obs,
24
+ design_factors=design, # compare samples based on the "condition"
25
+ refit_cooks=True,
26
+ inference=inference,
27
+ quiet=quiet
28
+ )
29
+ dds.var = adata.var.copy()
30
+
31
+ dds.deseq2()
32
+
33
+ return dds
34
+
35
+
36
+ def extractDESeqResults(dds, design, ref_level, tested_level, n_cpus=8, quiet=False):
37
+
38
+ inference = DefaultInference(n_cpus=n_cpus)
39
+
40
+ result_name = f'{tested_level}_vs_{ref_level}'
41
+
42
+ print(f'\t{tested_level}_vs_{ref_level}')
43
+
44
+ stat_res = DeseqStats(
45
+ dds,
46
+ contrast=[design, tested_level, ref_level],
47
+ inference=inference,
48
+ quiet=quiet
49
+ )
50
+
51
+ with open(os.devnull, 'w') as devnull:
52
+ with contextlib.redirect_stdout(devnull):
53
+ stat_res.summary()
54
+
55
+ results = pd.concat([dds.var['target'], stat_res.results_df], axis=1)
56
+
57
+ return result_name, results
@@ -0,0 +1,66 @@
1
+ ## Copyright (c) 2022-2024 ScreenPro2 Development Team.
2
+ ## All rights reserved.
3
+ ## Gilbart Lab, UCSF / Arc Institute.
4
+ ## Multi-Omics Tech Center, Arc Insititue.
5
+ ##
6
+ ## courtesy Tyler Fair (@tdfair), M. Horlbeck, (@mhorlbeck)
7
+
8
+ '''
9
+ evaluate module: evaluate essentiality detection performance
10
+ '''
11
+
12
+ import numpy as np
13
+ from sklearn import metrics
14
+ from sklearn.metrics import precision_recall_curve
15
+ import matplotlib.lines as mlines
16
+
17
+
18
+ def calcROC(df_in, essential, nonessential, score_col, target_col='target', verbose=False):
19
+ df = df_in.copy()
20
+ df[target_col] = df[target_col].str.split('-').str[0]
21
+
22
+ # AUC-ROC
23
+ df['DepMap'] = np.nan
24
+ df.loc[df[target_col].isin(essential),'DepMap'] = 'essential'
25
+ df.loc[df[target_col].isin(nonessential),'DepMap'] = 'non_essential'
26
+
27
+ y_true = df[df['DepMap'].notna()]['DepMap']
28
+ y_scores = df[df['DepMap'].notna()][score_col]
29
+
30
+ fpr, tpr, thresholds = metrics.roc_curve(y_true, y_scores, pos_label='non_essential')
31
+
32
+ if verbose: print('ROC-AUC score:', metrics.roc_auc_score(y_true, y_scores))
33
+
34
+ return fpr, tpr
35
+
36
+
37
+ def calcPR(df_in, truePos, trueNeg, score_col, target_col='target', ascending=True, verbose=False):
38
+
39
+ df = df_in.copy()
40
+
41
+ scoreList = df.set_index(target_col)[score_col]
42
+
43
+ truePos = truePos.intersection(scoreList.index)
44
+ trueNeg = trueNeg.intersection(scoreList.index)
45
+
46
+ cumulativeTup = [(0,1,np.nan)]
47
+ cumulativeTP = 0.0
48
+ cumulativeFP = 0.0
49
+
50
+ tup_cross95 = None
51
+
52
+ for gene, fold_change in scoreList.sort_values(inplace=False, ascending=ascending).items():
53
+ if gene in truePos or gene in trueNeg:
54
+ if gene in truePos:
55
+ cumulativeTP += 1
56
+
57
+ elif gene in trueNeg:
58
+ cumulativeFP += 1
59
+
60
+ cumulativeTup.append((cumulativeTP / len(truePos), cumulativeTP / (cumulativeTP + cumulativeFP), fold_change))
61
+
62
+ tup_cross95 = [cross95 for cross95 in cumulativeTup[::-1] if cross95[1] >= 0.95][0]
63
+
64
+ if verbose: print('Precision-Recall AUC:', tup_cross95)
65
+
66
+ return cumulativeTup, tup_cross95
@@ -0,0 +1,84 @@
1
+ """
2
+ phenostat module: internal module for statistical analysis of phenoscore data.
3
+ """
4
+
5
+ from scipy.stats import ttest_rel
6
+ import numpy as np
7
+ from statsmodels.stats.multitest import multipletests
8
+
9
+
10
+ def matrixStat(x, y, test, level, transform='log10'):
11
+ """
12
+ Get p-values comparing `y` vs `x` matrices.
13
+
14
+ Parameters:
15
+ x (np.array): array of values
16
+ y (np.array): array of values
17
+ test (str): test to use for calculating p-value
18
+ level (str): level at which to calculate p-value
19
+ transform (str): transformation to apply to values before running test
20
+
21
+ Returns:
22
+ np.array: array of p-values
23
+ """
24
+ # log-transform values
25
+ if transform == None:
26
+ pass
27
+ elif transform == 'log10':
28
+ x = np.log10(x)
29
+ y = np.log10(y)
30
+ else:
31
+ raise ValueError(f'Transform "{transform}" not recognized')
32
+
33
+ # calculate p-values
34
+ if test == 'MW':
35
+ # run Mann-Whitney U rank test
36
+ raise ValueError('Mann-Whitney U rank test not implemented')
37
+
38
+ elif test == 'KS':
39
+ # run Kolmorogov-Smirnov test
40
+ raise ValueError('Kolmorogov-Smirnov test not implemented')
41
+
42
+ elif test == 'ttest':
43
+ # run ttest
44
+ if level == 'col':
45
+ p_value = ttest_rel(y, x, axis=1)[1]
46
+ elif level == 'row':
47
+ p_value = ttest_rel(y, x, axis=0)[1]
48
+ elif level == 'all':
49
+ # average across all values
50
+ p_value = ttest_rel(y, x, axis=None)[1]
51
+ else:
52
+ raise ValueError(f'Level "{level}" not recognized')
53
+ return p_value
54
+ else:
55
+ raise ValueError(f'Test "{test}" not recognized')
56
+
57
+
58
+ def multipleTestsCorrection(p_values, method='fdr_bh'):
59
+ """
60
+ Calculate adjusted p-values using multiple testing correction.
61
+
62
+ Parameters:
63
+ p_values (np.array): array of p-values
64
+ method (str): method to use for multiple testing correction
65
+
66
+ Returns:
67
+ np.array: array of adjusted p-values
68
+ """
69
+ if method == 'fdr_bh':
70
+ # fill na with 1
71
+ p_values[np.isnan(p_values)] = 1
72
+ # Calculate the adjusted p-values using the Benjamini-Hochberg method
73
+ if p_values is None:
74
+ raise ValueError('p_values is None')
75
+ _, adj_p_values, _, _ = multipletests(p_values, alpha=0.05, method='fdr_bh')
76
+
77
+ else:
78
+ raise ValueError(f'Method "{method}" not recognized')
79
+
80
+ return adj_p_values
81
+
82
+
83
+ def empiricalFDR():
84
+ pass