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.
- pyproject.toml +30 -0
- screenpro/__init__.py +31 -0
- screenpro/__main__.py +8 -0
- screenpro/assays/__init__.py +465 -0
- screenpro/dashboard/__init__.py +293 -0
- screenpro/load.py +239 -0
- screenpro/main.py +227 -0
- screenpro/ngs/__init__.py +390 -0
- screenpro/ngs/cas12.py +206 -0
- screenpro/ngs/cas9.py +276 -0
- screenpro/phenoscore/__init__.py +148 -0
- screenpro/phenoscore/_annotate.py +122 -0
- screenpro/phenoscore/delta.py +375 -0
- screenpro/phenoscore/deseq.py +57 -0
- screenpro/phenoscore/evaluate.py +66 -0
- screenpro/phenoscore/phenostat.py +84 -0
- screenpro/plotting/__init__.py +9 -0
- screenpro/plotting/_rank.py +88 -0
- screenpro/plotting/_utils.py +81 -0
- screenpro/plotting/pheno_plots.py +196 -0
- screenpro/plotting/qc_plots.py +45 -0
- screenpro/preprocessing.py +98 -0
- screenpro2-0.5.0.dist-info/LICENSE +25 -0
- screenpro2-0.5.0.dist-info/METADATA +366 -0
- screenpro2-0.5.0.dist-info/RECORD +27 -0
- screenpro2-0.5.0.dist-info/WHEEL +4 -0
- screenpro2-0.5.0.dist-info/entry_points.txt +3 -0
|
@@ -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
|