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
pyproject.toml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "ScreenPro2"
|
|
3
|
+
version = "0.5.0"
|
|
4
|
+
description = "Flexible analysis of high-content CRISPR screening"
|
|
5
|
+
authors = [
|
|
6
|
+
"Abe Arab <abea@arcinstitute.org>"
|
|
7
|
+
]
|
|
8
|
+
license = "MIT"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
homepage = "https://github.com/ArcInstitute/ScreenPro2"
|
|
11
|
+
repository = "https://github.com/ArcInstitute/ScreenPro2"
|
|
12
|
+
keywords = ["CRISPR", "screening", "bioinformatics"]
|
|
13
|
+
packages = [
|
|
14
|
+
{ include = "screenpro" },
|
|
15
|
+
{ include = "pyproject.toml" },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[tool.poetry.dependencies]
|
|
19
|
+
python = ">=3.9"
|
|
20
|
+
|
|
21
|
+
[tool.poetry.scripts]
|
|
22
|
+
screenpro = "screenpro.main:main"
|
|
23
|
+
|
|
24
|
+
[tool.poetry.group.test.dependencies]
|
|
25
|
+
pytest = "*"
|
|
26
|
+
tomli = "*"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
|
|
30
|
+
build-backend = "poetry_dynamic_versioning.backend"
|
screenpro/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Copyright (c) 2022-2025 ScreenPro2 Development Team.
|
|
2
|
+
## All rights reserved.
|
|
3
|
+
## Gilbart Lab, UCSF / Arc Institute.
|
|
4
|
+
## Multi-Omics Tech Center, Arc Insititue.
|
|
5
|
+
|
|
6
|
+
'''ScreenPro2: A Python package for pooled CRISPR screens analysis
|
|
7
|
+
|
|
8
|
+
This package contains several modules, including:
|
|
9
|
+
|
|
10
|
+
**Main modules:**
|
|
11
|
+
- ngs: tools for generating counts from NGS data
|
|
12
|
+
- phenoscore: tools for calculating phenoscores
|
|
13
|
+
- assays: wrappers for analyzing CRISPR screens data from standard assays
|
|
14
|
+
|
|
15
|
+
**Additional modules:**
|
|
16
|
+
- load: tools for loading and saving data
|
|
17
|
+
- visualize: tools for visualizing data
|
|
18
|
+
- datasets: API for accessing pre-processed datasets
|
|
19
|
+
'''
|
|
20
|
+
|
|
21
|
+
from . import ngs
|
|
22
|
+
from . import load
|
|
23
|
+
from . import preprocessing as pp
|
|
24
|
+
from . import phenoscore as ps
|
|
25
|
+
from . import assays
|
|
26
|
+
from . import plotting as pl
|
|
27
|
+
from . import dashboard
|
|
28
|
+
|
|
29
|
+
from .ngs import GuideCounter
|
|
30
|
+
from .assays import PooledScreens, GImaps
|
|
31
|
+
from .dashboard import DrugScreenDashboard
|
screenpro/__main__.py
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
## Copyright (c) 2022-2025 ScreenPro2 Development Team.
|
|
2
|
+
## All rights reserved.
|
|
3
|
+
## Gilbart Lab, UCSF / Arc Institute.
|
|
4
|
+
## Multi-Omics Tech Center, Arc Insititue.
|
|
5
|
+
|
|
6
|
+
"""Assays module
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
12
|
+
import anndata as ad
|
|
13
|
+
import scanpy as sc
|
|
14
|
+
from typing import Literal
|
|
15
|
+
|
|
16
|
+
from ..phenoscore import (
|
|
17
|
+
runPhenoScore, getPhenotypeData,
|
|
18
|
+
runDESeq, extractDESeqResults,
|
|
19
|
+
)
|
|
20
|
+
from ..preprocessing import addPseudoCount, findLowCounts, normalizeSeqDepth
|
|
21
|
+
from ..phenoscore._annotate import annotateScoreTable, hit_dict
|
|
22
|
+
from ..plotting import volcano_plot, label_resistance_hit, label_sensitivity_hit
|
|
23
|
+
|
|
24
|
+
import warnings
|
|
25
|
+
from copy import copy
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PooledScreens(object):
|
|
29
|
+
"""
|
|
30
|
+
pooledScreens class for processing CRISPR screen datasets
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, adata, test='ttest', n_reps=3, verbose=False):
|
|
34
|
+
"""
|
|
35
|
+
Args:
|
|
36
|
+
adata (AnnData): AnnData object with adata.X as a matrix of sgRNA counts
|
|
37
|
+
test (str): statistical test to use for calculating phenotype scores
|
|
38
|
+
n_reps (int): number of replicates to use for calculating phenotype scores
|
|
39
|
+
verbose (bool): whether to print verbose output
|
|
40
|
+
"""
|
|
41
|
+
self.adata = adata.copy()
|
|
42
|
+
self.pdata = None
|
|
43
|
+
self.test = test
|
|
44
|
+
self.n_reps = n_reps
|
|
45
|
+
self.phenotypes = {}
|
|
46
|
+
self.verbose = verbose
|
|
47
|
+
|
|
48
|
+
def copy(self):
|
|
49
|
+
return copy(self)
|
|
50
|
+
|
|
51
|
+
def _add_phenotype_results(self, run_name, phenotype_name, phenotype_table):
|
|
52
|
+
if phenotype_name in self.phenotypes[run_name]['results'].keys():
|
|
53
|
+
raise ValueError(f"Phenotype '{phenotype_name}' already exists in self.phenotypes['results']!")
|
|
54
|
+
self.phenotypes[run_name]['results'][phenotype_name] = phenotype_table
|
|
55
|
+
|
|
56
|
+
def _calculateGrowthFactor(self, untreated, treated, db_rate_col):
|
|
57
|
+
"""
|
|
58
|
+
Calculate growth factor for gamma, tau, or rho score per replicates.
|
|
59
|
+
|
|
60
|
+
Parameters:
|
|
61
|
+
untreated (str): untreated condition
|
|
62
|
+
treated (str): treated condition
|
|
63
|
+
db_rate_col (str): column name for doubling rate
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
pd.DataFrame: growth factor dataframe
|
|
67
|
+
"""
|
|
68
|
+
adat = self.adata.copy()
|
|
69
|
+
if 'condition' not in adat.obs.columns or 'replicate' not in adat.obs.columns:
|
|
70
|
+
raise ValueError("The 'condition' and 'replicate' columns self.adata.")
|
|
71
|
+
growth_factors = []
|
|
72
|
+
# calculate growth factor for gamma, tau, or rho score per replicates
|
|
73
|
+
for replicate in adat.obs.replicate.unique():
|
|
74
|
+
db_untreated = adat.obs.query(f'condition == "{untreated}" & replicate == {str(replicate)}')[db_rate_col][0]
|
|
75
|
+
db_treated = adat.obs.query(f'condition == "{treated}" & replicate == {str(replicate)}')[db_rate_col][0]
|
|
76
|
+
|
|
77
|
+
growth_factors.append(('gamma', db_untreated, replicate, f'gamma_replicate_{replicate}'))
|
|
78
|
+
growth_factors.append(('tau', db_treated, replicate, f'tau_replicate_{replicate}'))
|
|
79
|
+
growth_factors.append(('rho', np.abs(db_untreated - db_treated), replicate, f'rho_replicate_{replicate}'))
|
|
80
|
+
|
|
81
|
+
out = pd.DataFrame(growth_factors, columns=['score', 'growth_factor', 'replicate', 'index']).set_index('index')
|
|
82
|
+
out.index.name = None
|
|
83
|
+
|
|
84
|
+
return out
|
|
85
|
+
|
|
86
|
+
def _getTreatmentDoublingRate(self, untreated, treated, db_rate_col):
|
|
87
|
+
if 'pop_doubling' not in self.adata.obs.columns or db_rate_col == None:
|
|
88
|
+
warnings.warn('No doubling rate information provided.')
|
|
89
|
+
db_untreated = 1
|
|
90
|
+
db_treated = 1
|
|
91
|
+
db_diff = 1
|
|
92
|
+
growth_factor_table = None
|
|
93
|
+
|
|
94
|
+
else:
|
|
95
|
+
growth_factor_table = self._calculateGrowthFactor(
|
|
96
|
+
untreated = untreated, treated = treated, db_rate_col = db_rate_col
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
db_untreated=growth_factor_table.query(f'score=="gamma"')['growth_factor'].mean()
|
|
100
|
+
db_treated=growth_factor_table.query(f'score=="tau"')['growth_factor'].mean()
|
|
101
|
+
db_diff = np.abs(db_untreated - db_treated)
|
|
102
|
+
|
|
103
|
+
return db_untreated, db_treated, db_diff
|
|
104
|
+
|
|
105
|
+
def _auto_run_name(self):
|
|
106
|
+
if len(list(self.phenotypes.keys())) == 1:
|
|
107
|
+
run_name = list(self.phenotypes.keys())[0]
|
|
108
|
+
else:
|
|
109
|
+
raise ValueError(
|
|
110
|
+
'Multiple phenotype calculation runs found.'
|
|
111
|
+
'Please specify run_name. Available runs: '
|
|
112
|
+
'' + ', '.join(self.phenotypes.keys())
|
|
113
|
+
)
|
|
114
|
+
return run_name
|
|
115
|
+
|
|
116
|
+
def filterLowCounts(self, filter_type='all', minimum_reads=1):
|
|
117
|
+
"""
|
|
118
|
+
Filter low counts in adata.X
|
|
119
|
+
"""
|
|
120
|
+
findLowCounts(
|
|
121
|
+
self.adata,
|
|
122
|
+
filter_type=filter_type,
|
|
123
|
+
minimum_reads=minimum_reads,
|
|
124
|
+
verbose=self.verbose
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
self.adata = self.adata[:,~self.adata.var.low_count].copy()
|
|
128
|
+
|
|
129
|
+
def countNormalization(self, pseudo_count_value=0.5):
|
|
130
|
+
"""
|
|
131
|
+
Preprocess and normalize the counts data in adata.X
|
|
132
|
+
|
|
133
|
+
Steps:
|
|
134
|
+
1. Add pseudocount to counts
|
|
135
|
+
2. Normalize counts by sequencing depth
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
self.adata.layers['raw_counts'] = self.adata.X.copy()
|
|
139
|
+
|
|
140
|
+
# add pseudocount
|
|
141
|
+
addPseudoCount(self.adata, behavior='default', value=pseudo_count_value)
|
|
142
|
+
|
|
143
|
+
if self.verbose: print('Pseudocount added to counts.')
|
|
144
|
+
|
|
145
|
+
# normalize counts by sequencing depth
|
|
146
|
+
normalizeSeqDepth(self.adata)
|
|
147
|
+
|
|
148
|
+
if self.verbose: print('Counts normalized by sequencing depth.')
|
|
149
|
+
|
|
150
|
+
def calculateDrugScreenDESeq(self, untreated, treated, t0=None, run_name='pyDESeq2', **kwargs):
|
|
151
|
+
"""
|
|
152
|
+
Calculate DESeq2 results for a given drug screen dataset.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
design (str): design matrix for DESeq2-based analysis
|
|
156
|
+
untreated (str): name of the untreated condition
|
|
157
|
+
treated (str): name of the treated condition
|
|
158
|
+
t0 (str): name of the untreated condition
|
|
159
|
+
run_name (str): name for the phenotype calculation run
|
|
160
|
+
**kwargs: additional arguments to pass to runDESeq
|
|
161
|
+
"""
|
|
162
|
+
if run_name in self.phenotypes.keys():
|
|
163
|
+
raise ValueError(f"Phenotype calculation run '{run_name}' already exists in self.phenoypes!")
|
|
164
|
+
else:
|
|
165
|
+
self.phenotypes[run_name] = {}
|
|
166
|
+
|
|
167
|
+
self.phenotypes[run_name]['config'] = {
|
|
168
|
+
'method':'pyDESeq2',
|
|
169
|
+
'untreated':untreated,
|
|
170
|
+
'treated':treated,
|
|
171
|
+
't0':t0,
|
|
172
|
+
'n_reps':self.n_reps,
|
|
173
|
+
}
|
|
174
|
+
self.phenotypes[run_name]['results'] = {}
|
|
175
|
+
|
|
176
|
+
if type(treated) != list: treated = [treated]
|
|
177
|
+
|
|
178
|
+
# run pyDESeq2 analysis
|
|
179
|
+
adt = self.adata.copy()
|
|
180
|
+
adt.X = adt.layers['raw_counts']
|
|
181
|
+
dds = runDESeq(adt, 'condition', **kwargs)
|
|
182
|
+
|
|
183
|
+
# extract comparison results
|
|
184
|
+
if t0 != None and type(t0) == str:
|
|
185
|
+
# Calculate `gamma`, `rho`, and `tau` phenotype scores
|
|
186
|
+
gamma_name, gamma = extractDESeqResults(
|
|
187
|
+
dds, design='condition', ref_level=t0, tested_level=untreated, **kwargs
|
|
188
|
+
)
|
|
189
|
+
self._add_phenotype_results(run_name, f'gamma:{gamma_name}', gamma)
|
|
190
|
+
|
|
191
|
+
for tr in treated:
|
|
192
|
+
tau_name, tau = extractDESeqResults(
|
|
193
|
+
dds, design='condition', ref_level=t0, tested_level=tr, **kwargs
|
|
194
|
+
)
|
|
195
|
+
self._add_phenotype_results(run_name, f'tau:{tau_name}', tau)
|
|
196
|
+
|
|
197
|
+
for tr in treated:
|
|
198
|
+
rho_name, rho = extractDESeqResults(
|
|
199
|
+
dds, design='condition', ref_level=untreated, tested_level=tr, **kwargs
|
|
200
|
+
)
|
|
201
|
+
self._add_phenotype_results(run_name, f'rho:{rho_name}', rho)
|
|
202
|
+
|
|
203
|
+
def calculateDrugScreen(self, score_level: Literal["compare_reps", "compare_guides"], untreated: str, treated: str, t0: str=None, db_rate_col: str='pop_doubling', run_name: str=None, count_filter_threshold: int=40, count_filter_type: Literal['mean', 'both', 'either']='mean', **kwargs):
|
|
204
|
+
"""
|
|
205
|
+
Calculate `gamma`, `rho`, and `tau` phenotype scores for a drug screen dataset in a given `score_level`. This function
|
|
206
|
+
is a wrapper around runPhenoScore. Check the args of runPhenoScore carefully before using it.
|
|
207
|
+
|
|
208
|
+
For a given phenotype score, runPhenoScore implements a count filter threshold. By default this threshold changes
|
|
209
|
+
any guide or target whose mean count across replicates being compared is <40 to NAs. Because this can lead to
|
|
210
|
+
unexpected behavior when the user relies on filterLowCounts, we specify both the count_filter_threshold and
|
|
211
|
+
count_filter_type arguments of runPhenoScore explicitly here.
|
|
212
|
+
|
|
213
|
+
self.adata.obs must have a 'condition' column. If doubling infomation is provided, it also needs a 'replicate' column.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
score_level (str): name of the score level. Must be "compare_reps" or "compare_guides"
|
|
217
|
+
untreated (str): name of the untreated condition
|
|
218
|
+
treated (str): name of the treated condition
|
|
219
|
+
t0 (str): name of the untreated condition
|
|
220
|
+
db_rate_col (str): column name for the doubling rate, default is 'pop_doubling'
|
|
221
|
+
run_name (str): name for the phenotype calculation run
|
|
222
|
+
count_filter_threshold (int): filter threshold for counts across compared replicates. Default is 40.
|
|
223
|
+
count_filter_type (str): type of filter for counts across replicates. Default is 'mean.'
|
|
224
|
+
**kwargs: additional arguments to pass to runPhenoScore
|
|
225
|
+
"""
|
|
226
|
+
if not run_name: run_name = score_level
|
|
227
|
+
if run_name in self.phenotypes.keys():
|
|
228
|
+
raise ValueError(f"Phenotype calculation run '{run_name}' already exists in self.phenoypes!")
|
|
229
|
+
else:
|
|
230
|
+
self.phenotypes[run_name] = {}
|
|
231
|
+
self.phenotypes[run_name]['config'] = {
|
|
232
|
+
'method':'ScreenPro2 - phenoscore',
|
|
233
|
+
'untreated':untreated,
|
|
234
|
+
'treated':treated,
|
|
235
|
+
't0':t0,
|
|
236
|
+
'n_reps':self.n_reps,
|
|
237
|
+
'test':self.test,
|
|
238
|
+
'score_level':score_level,
|
|
239
|
+
}
|
|
240
|
+
self.phenotypes[run_name]['results'] = {}
|
|
241
|
+
|
|
242
|
+
if type(treated) != list: treated = [treated]
|
|
243
|
+
|
|
244
|
+
# calculate phenotype scores: gamma, tau, rho
|
|
245
|
+
if t0 != None and type(t0) == str:
|
|
246
|
+
db_untreated,_,_ = self._getTreatmentDoublingRate(untreated, treated[0], db_rate_col)
|
|
247
|
+
gamma_name, gamma = runPhenoScore(
|
|
248
|
+
self.adata, cond_ref=t0, cond_test=untreated, growth_rate=db_untreated,
|
|
249
|
+
n_reps=self.n_reps,
|
|
250
|
+
test=self.test, score_level=score_level,
|
|
251
|
+
count_filter_threshold=count_filter_threshold,
|
|
252
|
+
count_filter_type=count_filter_type,
|
|
253
|
+
**kwargs
|
|
254
|
+
)
|
|
255
|
+
self._add_phenotype_results(run_name, f'gamma:{gamma_name}', gamma)
|
|
256
|
+
|
|
257
|
+
for tr in treated:
|
|
258
|
+
_, db_tr, db_diff = self._getTreatmentDoublingRate(untreated, tr, db_rate_col)
|
|
259
|
+
if t0 != None and type(t0) == str:
|
|
260
|
+
tau_name, tau = runPhenoScore(
|
|
261
|
+
self.adata, cond_ref=t0, cond_test=tr, growth_rate=db_tr,
|
|
262
|
+
n_reps=self.n_reps,
|
|
263
|
+
test=self.test, score_level=score_level,
|
|
264
|
+
count_filter_threshold=count_filter_threshold,
|
|
265
|
+
count_filter_type=count_filter_type,
|
|
266
|
+
**kwargs
|
|
267
|
+
)
|
|
268
|
+
self._add_phenotype_results(run_name, f'tau:{tau_name}', tau)
|
|
269
|
+
|
|
270
|
+
#TODO: warning / error if db_untreated and db_treated are too close, i.e. growth_rate ~= 0.
|
|
271
|
+
rho_name, rho = runPhenoScore(
|
|
272
|
+
self.adata, cond_ref=untreated, cond_test=tr, growth_rate=db_diff,
|
|
273
|
+
n_reps=self.n_reps,
|
|
274
|
+
test=self.test, score_level=score_level,
|
|
275
|
+
count_filter_threshold=count_filter_threshold,
|
|
276
|
+
count_filter_type=count_filter_type,
|
|
277
|
+
**kwargs
|
|
278
|
+
)
|
|
279
|
+
self._add_phenotype_results(run_name, f'rho:{rho_name}', rho)
|
|
280
|
+
|
|
281
|
+
def calculateFlowBasedScreen(self, low_bin, high_bin, score_level, run_name=None, **kwargs):
|
|
282
|
+
"""
|
|
283
|
+
Calculate phenotype scores for a flow-based screen dataset.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
low_bin (str): name of the low bin condition
|
|
287
|
+
high_bin (str): name of the high bin condition
|
|
288
|
+
score_level (str): name of the score level
|
|
289
|
+
run_name (str): name for the phenotype calculation run
|
|
290
|
+
**kwargs: additional arguments to pass to runPhenoScore
|
|
291
|
+
"""
|
|
292
|
+
if not run_name: run_name = score_level
|
|
293
|
+
if run_name in self.phenotypes.keys():
|
|
294
|
+
raise ValueError(f"Phenotype calculation run '{run_name}' already exists in self.phenoypes!")
|
|
295
|
+
else:
|
|
296
|
+
self.phenotypes[run_name] = {}
|
|
297
|
+
self.phenotypes[run_name]['config'] = {
|
|
298
|
+
'method':'ScreenPro2 - phenoscore',
|
|
299
|
+
'low_bin':low_bin,
|
|
300
|
+
'high_bin':high_bin,
|
|
301
|
+
'test':self.test,
|
|
302
|
+
'score_level':score_level,
|
|
303
|
+
}
|
|
304
|
+
self.phenotypes[run_name]['results'] = {}
|
|
305
|
+
|
|
306
|
+
# calculate phenotype scores
|
|
307
|
+
delta_name, delta = runPhenoScore(
|
|
308
|
+
self.adata, cond_ref=low_bin, cond_test=high_bin, n_reps=self.n_reps,
|
|
309
|
+
test=self.test, score_level=score_level,
|
|
310
|
+
**kwargs
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
self._add_phenotype_results(run_name, f'delta:{delta_name}', delta)
|
|
314
|
+
|
|
315
|
+
def listPhenotypeScores(self, run_name='auto'):
|
|
316
|
+
"""
|
|
317
|
+
List available phenotype scores for a given run_name
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
run_name (str): name of the phenotype calculation run to retrieve
|
|
321
|
+
"""
|
|
322
|
+
if run_name == 'auto': run_name = self._auto_run_name()
|
|
323
|
+
|
|
324
|
+
out = list(self.phenotypes[run_name]['results'].keys())
|
|
325
|
+
|
|
326
|
+
return out
|
|
327
|
+
|
|
328
|
+
def getPhenotypeScores(self, phenotype_name, threshold, run_name='auto', **kwargs):
|
|
329
|
+
"""
|
|
330
|
+
Get phenotype scores for a given phenotype_name
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
phenotype_name (str): name of the phenotype score
|
|
334
|
+
run_name (str): name of the phenotype calculation run to retrieve
|
|
335
|
+
"""
|
|
336
|
+
if run_name == 'auto': run_name = self._auto_run_name()
|
|
337
|
+
|
|
338
|
+
score_tag, _ = phenotype_name.split(':')
|
|
339
|
+
|
|
340
|
+
out = annotateScoreTable(
|
|
341
|
+
self.phenotypes[run_name]['results'][phenotype_name],
|
|
342
|
+
up_hit=hit_dict[score_tag]['up_hit'],
|
|
343
|
+
down_hit=hit_dict[score_tag]['down_hit'],
|
|
344
|
+
threshold=threshold,
|
|
345
|
+
**kwargs
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
return out
|
|
349
|
+
|
|
350
|
+
def buildPhenotypeData(self, run_name='auto',db_rate_col='pop_doubling', **kwargs):
|
|
351
|
+
if run_name == 'auto': run_name = self._auto_run_name()
|
|
352
|
+
if run_name=='compare_reps':
|
|
353
|
+
pass
|
|
354
|
+
else:
|
|
355
|
+
raise ValueError('Only `compare_reps` run_name is supported for now!')
|
|
356
|
+
|
|
357
|
+
untreated = self.phenotypes[run_name]['config']['untreated']
|
|
358
|
+
|
|
359
|
+
pdata_list = []
|
|
360
|
+
|
|
361
|
+
for phenotype_name in self.listPhenotypeScores(run_name=run_name):
|
|
362
|
+
|
|
363
|
+
score_tag, comparison = phenotype_name.split(':')
|
|
364
|
+
cond_test, cond_ref = comparison.split('_vs_')
|
|
365
|
+
|
|
366
|
+
#TODO: fix `_calculateGrowthFactor` and `_getTreatmentDoublingRate`
|
|
367
|
+
if db_rate_col:
|
|
368
|
+
growth_rate_reps = self._calculateGrowthFactor(
|
|
369
|
+
untreated = untreated,
|
|
370
|
+
treated = cond_test, # should be part of "treated" list!
|
|
371
|
+
db_rate_col = db_rate_col
|
|
372
|
+
).query(
|
|
373
|
+
f'score=="{score_tag}"'
|
|
374
|
+
).set_index('replicate')['growth_factor'].to_dict()
|
|
375
|
+
|
|
376
|
+
else:
|
|
377
|
+
growth_rate_reps=None
|
|
378
|
+
|
|
379
|
+
pdata = getPhenotypeData(
|
|
380
|
+
self.adata, score_tag=score_tag,
|
|
381
|
+
cond_ref=cond_ref, cond_test=cond_test,
|
|
382
|
+
growth_rate_reps=growth_rate_reps,
|
|
383
|
+
**kwargs
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
pdata_list.append(pdata)
|
|
387
|
+
|
|
388
|
+
self.pdata = ad.concat(pdata_list, axis=0)
|
|
389
|
+
self.pdata.var = self.adata.var.copy()
|
|
390
|
+
|
|
391
|
+
def drawVolcano(
|
|
392
|
+
self, ax,
|
|
393
|
+
phenotype_name,
|
|
394
|
+
threshold,
|
|
395
|
+
dot_size=1,
|
|
396
|
+
run_name='auto',
|
|
397
|
+
score_col='score',
|
|
398
|
+
pvalue_col='pvalue',
|
|
399
|
+
xlabel='auto',
|
|
400
|
+
ylabel='-log10(pvalue)',
|
|
401
|
+
xlims='auto',
|
|
402
|
+
ylims='auto',
|
|
403
|
+
ctrl_label='negative_control',
|
|
404
|
+
resistance_hits=None,
|
|
405
|
+
sensitivity_hits=None,
|
|
406
|
+
size_txt=None,
|
|
407
|
+
t_x=0, t_y=0,
|
|
408
|
+
**args
|
|
409
|
+
):
|
|
410
|
+
if run_name == 'auto': run_name = self._auto_run_name()
|
|
411
|
+
|
|
412
|
+
score_tag, _ = phenotype_name.split(':')
|
|
413
|
+
|
|
414
|
+
df = self.phenotypes[run_name]['results'][phenotype_name].dropna()
|
|
415
|
+
|
|
416
|
+
df = annotateScoreTable(
|
|
417
|
+
df,
|
|
418
|
+
up_hit=hit_dict[score_tag]['up_hit'],
|
|
419
|
+
down_hit=hit_dict[score_tag]['down_hit'],
|
|
420
|
+
score_col=score_col, pvalue_col=pvalue_col,
|
|
421
|
+
ctrl_label=ctrl_label,
|
|
422
|
+
threshold=threshold,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
df['-log10(pvalue)'] = -np.log10(df[pvalue_col])
|
|
426
|
+
|
|
427
|
+
if xlabel == 'auto':
|
|
428
|
+
xlabel = phenotype_name.replace(':', ': ').replace('_', ' ')
|
|
429
|
+
|
|
430
|
+
volcano_plot(ax, df,
|
|
431
|
+
up_hit=hit_dict[score_tag]['up_hit'],
|
|
432
|
+
down_hit=hit_dict[score_tag]['down_hit'],
|
|
433
|
+
score_col=score_col, pvalue_col=pvalue_col,
|
|
434
|
+
xlabel=xlabel, ylabel=ylabel,
|
|
435
|
+
dot_size=dot_size, xlims=xlims, ylims=ylims,
|
|
436
|
+
ctrl_label=ctrl_label,
|
|
437
|
+
**args)
|
|
438
|
+
|
|
439
|
+
if resistance_hits != None:
|
|
440
|
+
if type(resistance_hits) != list: resistance_hits = [resistance_hits]
|
|
441
|
+
for hit in resistance_hits:
|
|
442
|
+
label_resistance_hit(
|
|
443
|
+
ax=ax, df_in=df, label=hit,
|
|
444
|
+
x_col=score_col,
|
|
445
|
+
y_col='-log10(pvalue)',
|
|
446
|
+
size=dot_size * 2,
|
|
447
|
+
size_txt=size_txt,
|
|
448
|
+
t_x=t_x, t_y=t_y
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
if sensitivity_hits != None:
|
|
452
|
+
if type(sensitivity_hits) != list: sensitivity_hits = [sensitivity_hits]
|
|
453
|
+
for hit in sensitivity_hits:
|
|
454
|
+
label_sensitivity_hit(
|
|
455
|
+
ax=ax, df_in=df, label=hit,
|
|
456
|
+
x_col=score_col,
|
|
457
|
+
y_col='-log10(pvalue)',
|
|
458
|
+
size=dot_size * 2,
|
|
459
|
+
size_txt=size_txt,
|
|
460
|
+
t_x=t_x, t_y=t_y
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class GImaps(object):
|
|
465
|
+
pass
|