cellmaps-utils 0.10.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.
- cellmaps_utils/__init__.py +9 -0
- cellmaps_utils/apms_readme.txt +85 -0
- cellmaps_utils/apms_zmadex_saint_readme.txt +165 -0
- cellmaps_utils/apmstool.py +945 -0
- cellmaps_utils/basecmdtool.py +108 -0
- cellmaps_utils/cellmaps_utilscmd.py +133 -0
- cellmaps_utils/challenge.py +1000 -0
- cellmaps_utils/constants.py +392 -0
- cellmaps_utils/crispr_readme.txt +19 -0
- cellmaps_utils/crisprtool.py +369 -0
- cellmaps_utils/ddotconverter.py +285 -0
- cellmaps_utils/exceptions.py +15 -0
- cellmaps_utils/hcx_utils.py +198 -0
- cellmaps_utils/hidefconverter.py +245 -0
- cellmaps_utils/hierdiff.py +142 -0
- cellmaps_utils/ifimage_readme.txt +31 -0
- cellmaps_utils/iftool.py +657 -0
- cellmaps_utils/logutils.py +214 -0
- cellmaps_utils/music_utils.py +246 -0
- cellmaps_utils/ndexupload.py +193 -0
- cellmaps_utils/provenance.py +890 -0
- cellmaps_utils/secms_readme.txt +81 -0
- cellmaps_utils/secmstool.py +262 -0
- cellmaps_utils/statsgenerator.py +237 -0
- cellmaps_utils/tabletool.py +399 -0
- cellmaps_utils-0.10.0.data/scripts/cellmaps_utilscmd.py +133 -0
- cellmaps_utils-0.10.0.dist-info/AUTHORS.rst +20 -0
- cellmaps_utils-0.10.0.dist-info/LICENSE +22 -0
- cellmaps_utils-0.10.0.dist-info/METADATA +275 -0
- cellmaps_utils-0.10.0.dist-info/RECORD +32 -0
- cellmaps_utils-0.10.0.dist-info/WHEEL +5 -0
- cellmaps_utils-0.10.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import uuid
|
|
5
|
+
import shutil
|
|
6
|
+
from datetime import date
|
|
7
|
+
import logging
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import cellmaps_utils
|
|
10
|
+
from cellmaps_utils.basecmdtool import BaseCommandLineTool
|
|
11
|
+
from cellmaps_utils.exceptions import CellMapsError
|
|
12
|
+
from cellmaps_utils import constants
|
|
13
|
+
from cellmaps_utils.provenance import ProvenanceUtil
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class APMSDataLoader(BaseCommandLineTool):
|
|
19
|
+
"""
|
|
20
|
+
Creates RO-Crate of AP-MS data from
|
|
21
|
+
raw AP-MS tables
|
|
22
|
+
"""
|
|
23
|
+
COMMAND = 'apmsconverter'
|
|
24
|
+
|
|
25
|
+
BAIT_COL_NAME = 'Bait'
|
|
26
|
+
|
|
27
|
+
LEGACY_FORMAT = 'legacy'
|
|
28
|
+
"""
|
|
29
|
+
Value for ``--input_format`` denoting the original SAINT tables
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
ZMADEX_SAINT_FORMAT = 'zmadex_saint'
|
|
33
|
+
"""
|
|
34
|
+
Value for ``--input_format`` denoting the Zmadex/SAINT tables
|
|
35
|
+
introduced with the 2025_04_04 Krogan release
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
INPUT_FORMATS = [LEGACY_FORMAT, ZMADEX_SAINT_FORMAT]
|
|
39
|
+
|
|
40
|
+
DRUG_COL = 'drug'
|
|
41
|
+
"""
|
|
42
|
+
Name of column in Zmadex/SAINT input used to split treatment arms.
|
|
43
|
+
The column is consumed by the split and never written out
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
ZMADEX_SAINT_COL_MAP = {'baitGene': 'Bait',
|
|
47
|
+
'preyProtein': 'Prey',
|
|
48
|
+
'preyGene': 'PreyGene',
|
|
49
|
+
'batch': 'Batch',
|
|
50
|
+
'meanZmadex': 'Zmadex',
|
|
51
|
+
'meanLog2FC': 'BaitControlLog2FC',
|
|
52
|
+
'obsCount': 'ObservedCount',
|
|
53
|
+
'missingCount': 'MissingCount',
|
|
54
|
+
'SAINT.BFDR.intensity': 'SAINTBFDRIntensity',
|
|
55
|
+
'SAINT.BFDR.spc': 'SAINTBFDRSpectralCount',
|
|
56
|
+
'passZ': 'PassZ',
|
|
57
|
+
'passSAINTintensity': 'PassSAINTIntensity',
|
|
58
|
+
'passSAINTspc': 'PassSAINTSpectralCount',
|
|
59
|
+
'label': 'ReferenceLabel'}
|
|
60
|
+
"""
|
|
61
|
+
Zmadex/SAINT source column to normalized AP-MS column
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
APMS_COLS = ['Bait', 'Prey', 'PreyGene', 'Batch',
|
|
65
|
+
'Zmadex', 'BaitControlLog2FC',
|
|
66
|
+
'ObservedCount', 'MissingCount',
|
|
67
|
+
'SAINTBFDRIntensity', 'SAINTBFDRSpectralCount',
|
|
68
|
+
'PassZ', 'PassSAINTIntensity', 'PassSAINTSpectralCount',
|
|
69
|
+
'ReferenceLabel']
|
|
70
|
+
"""
|
|
71
|
+
Column order of apms.tsv and apms_unfiltered.tsv. Identical for every
|
|
72
|
+
treatment so downstream consumers need no per-crate branching
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
MSSTATS_COL_MAP = {'Batch': 'Batch',
|
|
76
|
+
'Bait': 'Bait',
|
|
77
|
+
'Protein': 'Prey',
|
|
78
|
+
'log2FC': 'DrugEffectLog2FC',
|
|
79
|
+
'SE': 'DrugEffectSE',
|
|
80
|
+
'Tvalue': 'DrugEffectTValue',
|
|
81
|
+
'DF': 'DrugEffectDF',
|
|
82
|
+
'pvalue': 'DrugEffectPValue',
|
|
83
|
+
'adj.pvalue': 'DrugEffectAdjustedPValue',
|
|
84
|
+
'Significant': 'DrugEffectSignificant',
|
|
85
|
+
'Effect': 'DrugEffect',
|
|
86
|
+
'issue': 'DrugEffectIssue',
|
|
87
|
+
'Label': 'DrugEffectComparison'}
|
|
88
|
+
"""
|
|
89
|
+
MSstats source column to normalized drug effect column
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
DRUG_EFFECT_COLS = ['Batch', 'Bait', 'Prey',
|
|
93
|
+
'DrugEffectLog2FC', 'DrugEffectSE',
|
|
94
|
+
'DrugEffectTValue', 'DrugEffectDF',
|
|
95
|
+
'DrugEffectPValue', 'DrugEffectAdjustedPValue',
|
|
96
|
+
'DrugEffectSignificant', 'DrugEffect',
|
|
97
|
+
'DrugEffectIssue', 'DrugEffectComparison']
|
|
98
|
+
"""
|
|
99
|
+
Column order of apms_drug_effect.tsv
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
KEY_COLS = ['Batch', 'Bait', 'Prey']
|
|
103
|
+
"""
|
|
104
|
+
Join key. All three are needed, HDAC2 and USP7 appear in two batches
|
|
105
|
+
so Bait + Prey alone is not unique
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
PASS_COLS = ['PassZ', 'PassSAINTIntensity', 'PassSAINTSpectralCount']
|
|
109
|
+
|
|
110
|
+
FILTER_ANY_PASS = 'any_pass'
|
|
111
|
+
FILTER_NONE = 'none'
|
|
112
|
+
|
|
113
|
+
FILTER_MODES = {FILTER_ANY_PASS: PASS_COLS,
|
|
114
|
+
'passz': ['PassZ'],
|
|
115
|
+
'saint_intensity': ['PassSAINTIntensity'],
|
|
116
|
+
'saint_spc': ['PassSAINTSpectralCount'],
|
|
117
|
+
FILTER_NONE: []}
|
|
118
|
+
"""
|
|
119
|
+
``--filter`` value to the columns that must hold ``TRUE``
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
DRUG_EFFECT_ROWS_ALL = 'all'
|
|
123
|
+
DRUG_EFFECT_ROWS_SAINT_UNIVERSE = 'saint_universe'
|
|
124
|
+
DRUG_EFFECT_ROWS_APMS_KEYS = 'apms_keys'
|
|
125
|
+
|
|
126
|
+
DRUG_EFFECT_ROWS_CHOICES = [DRUG_EFFECT_ROWS_ALL,
|
|
127
|
+
DRUG_EFFECT_ROWS_SAINT_UNIVERSE,
|
|
128
|
+
DRUG_EFFECT_ROWS_APMS_KEYS]
|
|
129
|
+
|
|
130
|
+
TRUE_VAL = 'TRUE'
|
|
131
|
+
|
|
132
|
+
KNOWN_DRUGS = ['DMSO', 'VRST']
|
|
133
|
+
|
|
134
|
+
DRUG_TO_TREATMENT = {'DMSO': 'untreated',
|
|
135
|
+
'VRST': 'vorinostat'}
|
|
136
|
+
|
|
137
|
+
KNOWN_DRUG_EFFECTS = ['up', 'down', 'insig',
|
|
138
|
+
'gained', 'lost', 'unidentified']
|
|
139
|
+
|
|
140
|
+
GENE_SET_ALIASES = {'SMARCA4': 'BRG1',
|
|
141
|
+
'MORF4L1': 'MRG15',
|
|
142
|
+
'NSD1': 'KMT3B'}
|
|
143
|
+
"""
|
|
144
|
+
Baits published under a legacy alias in the CM4AI gene set tables.
|
|
145
|
+
Resolved before the gene set check so they are not reported missing
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
CHROMATIN_GENE_SET = set("""
|
|
149
|
+
PARP1 BAP1 USP7 NEDD4 BRCA1 BARD1 BRE1A CBL MSL1 PHF6 TAF1 UHRF1 UHRF2 KAT2A KAT3A KAT3B
|
|
150
|
+
KAT6A KAT6B HDAC1 HDAC2 HDAC3 HDAC4 HDAC5 HDAC6 HDAC7 HDAC8 HDAC9 HDAC10 HDAC11 SIRT2
|
|
151
|
+
KDM3B KDM4C KDM5A KDM5B KDM5C KDM6A KDM6B TET1 TET2 TET3 ATM ATR AURKB AURKA CSNK2A1
|
|
152
|
+
HASPIN JAK2 MSK1 MSK2 PRKCA PRKCB PRKCD BAZ1B PRKAA1 PRKAA2 PRKAB1 PRKAB2 PRKAG1 PRKAG2
|
|
153
|
+
PRKAG3 DNMT1 DNMT3A DNMT3B KMT1D KMT1F KMT2A KMT2C KMT2D KMT2E KMT2H KMT3B PRMT1 EYA1
|
|
154
|
+
EYA3 PPP1CA PPP1CB PPP1CC PPP4C BPTF BRD4 BRD7 BRPF1 CHD4 ING1 TRIM24 YWHAZ YWHAE YWHAQ
|
|
155
|
+
YWHAB YWHAH YWHAG BRG1 CBX2 CBX3 MRG15 53BP1 BRD3 CHD1 MBTD1 PDP1 RAG2
|
|
156
|
+
""".split())
|
|
157
|
+
"""
|
|
158
|
+
CM4AI Year 1 chromatin modifier genes, see
|
|
159
|
+
https://cm4ai.org/product-documentation/#year-1-chromatin-modifier-genes-proteins
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
METABOLIC_GENE_SET = set("""
|
|
163
|
+
FOLR1 MTHFR MTR MTHFD1 MAT1A MAT2A MAT2B AHCY SHMT1 RFK FLAD1 TKFC ACAD9 ACADM ACADS
|
|
164
|
+
ACADSB ACADVL ACAT1 ACOT7 ACOX3 ACSS2 ACSS3 GMPS LDHA LDHB LDHD ACLY ALDH5A1 DLAT DLD
|
|
165
|
+
GLUD1 GLUD2 IDH1 IDH3A IDH3B IDH3G MDH2 OGDH PDHA1 PDHA2 PDHB SDHA G6PD GAPDH NADK
|
|
166
|
+
NDUFS4 NDUFAB1 NMNAT2 NNT PGD PGM1 PHGDH PYGL SRC KIT ITPKA AKT1 BRAF CDK4 DAPK3 FYN
|
|
167
|
+
PIK3CA PIK3C2A NUAK1 STK4 MAPK1 MTOR PIP5K1C RIPK1 RPS6KA3 KRAS TGFBR2 WEE1 PPA2 AHCYL2
|
|
168
|
+
CYP27A1 DIO3 GSTM1 HEXA NAGS NCOA1 PANK2 PARP4 PARP6 PLCD4 PLCG1 SLC2A1 UGDH UGT8 UBE3C
|
|
169
|
+
CYP24A1 GCLM GYG2 PDE4A PLPP7 AMDHD2 ABHD6
|
|
170
|
+
""".split())
|
|
171
|
+
"""
|
|
172
|
+
CM4AI Year 2 metabolic enzyme genes, see
|
|
173
|
+
https://cm4ai.org/product-documentation/#year-2-metabolic-enzyme-genes-proteins
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
GENE_SETS = {'chromatin': CHROMATIN_GENE_SET,
|
|
177
|
+
'metabolic': METABOLIC_GENE_SET}
|
|
178
|
+
|
|
179
|
+
SUMMARY_MARKER = '@@DATASET_SUMMARY@@'
|
|
180
|
+
"""
|
|
181
|
+
Placeholder in apms_zmadex_saint_readme.txt replaced with a
|
|
182
|
+
description of what this particular RO-Crate holds
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def __init__(self, theargs,
|
|
186
|
+
provenance_utils=ProvenanceUtil()):
|
|
187
|
+
"""
|
|
188
|
+
Constructor
|
|
189
|
+
|
|
190
|
+
:param theargs: Command line arguments that at minimum need
|
|
191
|
+
to have the following attributes:
|
|
192
|
+
:type theargs: :py:class:`~python.argparse.Namespace`
|
|
193
|
+
"""
|
|
194
|
+
super().__init__()
|
|
195
|
+
self._outdir = os.path.abspath(theargs.outdir)
|
|
196
|
+
self._inputs = theargs.inputs
|
|
197
|
+
self._name = theargs.name
|
|
198
|
+
self._organization_name = theargs.organization_name
|
|
199
|
+
self._project_name = theargs.project_name
|
|
200
|
+
self._release = theargs.release
|
|
201
|
+
self._cell_line = theargs.cell_line
|
|
202
|
+
self._treatment = theargs.treatment
|
|
203
|
+
self._tissue = theargs.tissue
|
|
204
|
+
self._author = theargs.author
|
|
205
|
+
self._gene_set = theargs.gene_set
|
|
206
|
+
self._baitcolname = theargs.baitcolname
|
|
207
|
+
self._set_name = theargs.set_name
|
|
208
|
+
self._input_format = getattr(theargs, 'input_format',
|
|
209
|
+
APMSDataLoader.LEGACY_FORMAT)
|
|
210
|
+
self._drug = getattr(theargs, 'drug', None)
|
|
211
|
+
self._drug_effect_input = getattr(theargs, 'drug_effect_input', None)
|
|
212
|
+
self._drug_effect_rows = getattr(theargs, 'drug_effect_rows',
|
|
213
|
+
APMSDataLoader.DRUG_EFFECT_ROWS_ALL)
|
|
214
|
+
self._exclude_baits = getattr(theargs, 'exclude_baits', ['MDA'])
|
|
215
|
+
self._filter = getattr(theargs, 'filter',
|
|
216
|
+
APMSDataLoader.FILTER_ANY_PASS)
|
|
217
|
+
self._no_unfiltered = getattr(theargs, 'no_unfiltered', False)
|
|
218
|
+
self._provenance_utils = provenance_utils
|
|
219
|
+
self._softwareid = None
|
|
220
|
+
self._input_data_dict = theargs.__dict__
|
|
221
|
+
self._qc = {}
|
|
222
|
+
|
|
223
|
+
def run(self):
|
|
224
|
+
"""
|
|
225
|
+
Run method to create RO-Crate from AP-MS data tables. This process involves merging input tables,
|
|
226
|
+
registering the dataset and related software in the RO-Crate.
|
|
227
|
+
|
|
228
|
+
:return:
|
|
229
|
+
"""
|
|
230
|
+
self._validate_args()
|
|
231
|
+
self._generate_rocrate_dir_path()
|
|
232
|
+
if os.path.exists(self._outdir):
|
|
233
|
+
raise CellMapsError(self._outdir + ' already exists')
|
|
234
|
+
|
|
235
|
+
logger.debug('Creating directory ' + str(self._outdir))
|
|
236
|
+
os.makedirs(self._outdir, mode=0o755)
|
|
237
|
+
|
|
238
|
+
keywords = [self._project_name, self._release,
|
|
239
|
+
self._cell_line, self._treatment, self._tissue,
|
|
240
|
+
'AP-MS edgelist']
|
|
241
|
+
|
|
242
|
+
if self._gene_set is not None:
|
|
243
|
+
keywords.append(self._gene_set)
|
|
244
|
+
|
|
245
|
+
description = ' '.join(keywords)
|
|
246
|
+
|
|
247
|
+
info_dict = {
|
|
248
|
+
constants.DATASET_NAME: self._name,
|
|
249
|
+
constants.DATASET_ORGANIZATION_NAME: self._organization_name,
|
|
250
|
+
constants.DATASET_PROJECT_NAME: self._project_name,
|
|
251
|
+
constants.DATASET_RELEASE: self._release,
|
|
252
|
+
constants.DATASET_CELL_LINE: self._cell_line,
|
|
253
|
+
constants.DATASET_TREATMENT: self._treatment,
|
|
254
|
+
constants.DATASET_TISSUE: self._tissue,
|
|
255
|
+
constants.DATASET_AUTHOR: self._author,
|
|
256
|
+
constants.DATASET_GENE_SET: self._gene_set
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
self.save_dataset_info_to_json(self._outdir, info_dict, constants.DATASET_INFO_FILE)
|
|
260
|
+
|
|
261
|
+
self._provenance_utils.register_rocrate(self._outdir,
|
|
262
|
+
name=self._name,
|
|
263
|
+
organization_name=self._organization_name,
|
|
264
|
+
project_name=self._project_name,
|
|
265
|
+
description=description,
|
|
266
|
+
keywords=keywords,
|
|
267
|
+
guid=self._get_fairscape_id())
|
|
268
|
+
gen_dsets = []
|
|
269
|
+
|
|
270
|
+
if self._input_format == APMSDataLoader.ZMADEX_SAINT_FORMAT:
|
|
271
|
+
gen_dsets.extend(self._run_zmadex_saint(description=description,
|
|
272
|
+
keywords=keywords))
|
|
273
|
+
else:
|
|
274
|
+
file_path = self._merge_and_save_apms_data()
|
|
275
|
+
file_desc = description + ' AP-MS file'
|
|
276
|
+
file_keywords = keywords.copy()
|
|
277
|
+
file_keywords.extend(['file'])
|
|
278
|
+
dset_id = self._provenance_utils.register_dataset(rocrate_path=self._outdir, source_file=file_path,
|
|
279
|
+
skip_copy=True,
|
|
280
|
+
data_dict={'name': ' AP-MS file',
|
|
281
|
+
'description': file_desc,
|
|
282
|
+
'keywords': file_keywords,
|
|
283
|
+
'data-format': 'tsv',
|
|
284
|
+
'author': self._author,
|
|
285
|
+
'version': self._release,
|
|
286
|
+
'date-published': date.today().strftime('%Y-%m-%d')},
|
|
287
|
+
guid=self._get_fairscape_id())
|
|
288
|
+
gen_dsets.append(dset_id)
|
|
289
|
+
self._register_software(keywords=keywords, description=description)
|
|
290
|
+
self._register_computation(generated_dataset_ids=gen_dsets,
|
|
291
|
+
description=description,
|
|
292
|
+
keywords=keywords)
|
|
293
|
+
self._copy_over_apms_readme()
|
|
294
|
+
return 0
|
|
295
|
+
|
|
296
|
+
def _validate_args(self):
|
|
297
|
+
"""
|
|
298
|
+
Checks the combination of command line flags makes sense before
|
|
299
|
+
any directory is created
|
|
300
|
+
|
|
301
|
+
:raises CellMapsError: if the flags are contradictory
|
|
302
|
+
"""
|
|
303
|
+
if self._input_format not in APMSDataLoader.INPUT_FORMATS:
|
|
304
|
+
raise CellMapsError('--input_format must be one of ' +
|
|
305
|
+
str(APMSDataLoader.INPUT_FORMATS) +
|
|
306
|
+
' received: ' + str(self._input_format))
|
|
307
|
+
|
|
308
|
+
if self._input_format == APMSDataLoader.LEGACY_FORMAT:
|
|
309
|
+
if self._drug_effect_input is not None:
|
|
310
|
+
raise CellMapsError('--drug_effect_input is only supported '
|
|
311
|
+
'with --input_format ' +
|
|
312
|
+
APMSDataLoader.ZMADEX_SAINT_FORMAT)
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
if self._inputs is None or len(self._inputs) != 1:
|
|
316
|
+
raise CellMapsError('--input_format ' +
|
|
317
|
+
APMSDataLoader.ZMADEX_SAINT_FORMAT +
|
|
318
|
+
' expects exactly one --inputs file, received: ' +
|
|
319
|
+
str(self._inputs))
|
|
320
|
+
|
|
321
|
+
if self._drug is None:
|
|
322
|
+
raise CellMapsError('--drug is required with --input_format ' +
|
|
323
|
+
APMSDataLoader.ZMADEX_SAINT_FORMAT)
|
|
324
|
+
|
|
325
|
+
if self._filter not in APMSDataLoader.FILTER_MODES:
|
|
326
|
+
raise CellMapsError('--filter must be one of ' +
|
|
327
|
+
str(list(APMSDataLoader.FILTER_MODES.keys())) +
|
|
328
|
+
' received: ' + str(self._filter))
|
|
329
|
+
|
|
330
|
+
if self._drug_effect_rows not in APMSDataLoader.DRUG_EFFECT_ROWS_CHOICES:
|
|
331
|
+
raise CellMapsError('--drug_effect_rows must be one of ' +
|
|
332
|
+
str(APMSDataLoader.DRUG_EFFECT_ROWS_CHOICES) +
|
|
333
|
+
' received: ' + str(self._drug_effect_rows))
|
|
334
|
+
|
|
335
|
+
if self._drug_effect_input is None:
|
|
336
|
+
if self._drug_effect_rows != APMSDataLoader.DRUG_EFFECT_ROWS_ALL:
|
|
337
|
+
raise CellMapsError('--drug_effect_rows has nothing to act on '
|
|
338
|
+
'without --drug_effect_input')
|
|
339
|
+
elif self._drug == 'DMSO':
|
|
340
|
+
logger.warning('--drug_effect_input was passed with --drug DMSO. '
|
|
341
|
+
'The MSstats contrast is vorinostat versus DMSO, so '
|
|
342
|
+
'it normally belongs with the treated arm only')
|
|
343
|
+
|
|
344
|
+
expected_treatment = APMSDataLoader.DRUG_TO_TREATMENT.get(self._drug)
|
|
345
|
+
if expected_treatment is not None and expected_treatment != self._treatment:
|
|
346
|
+
logger.warning('--drug ' + str(self._drug) + ' is usually paired '
|
|
347
|
+
'with --treatment ' + expected_treatment +
|
|
348
|
+
' but --treatment is ' + str(self._treatment))
|
|
349
|
+
|
|
350
|
+
def _run_zmadex_saint(self, description='', keywords=None):
|
|
351
|
+
"""
|
|
352
|
+
Writes and registers every file of a Zmadex/SAINT RO-Crate.
|
|
353
|
+
|
|
354
|
+
The two inputs are never merged. The AP-MS tables are built from the
|
|
355
|
+
Zmadex/SAINT file and the drug effect table from the MSstats file,
|
|
356
|
+
the only thing passing between them is a key set used by the
|
|
357
|
+
``--drug_effect_rows`` restriction.
|
|
358
|
+
|
|
359
|
+
:return: ids of registered datasets
|
|
360
|
+
:rtype: list
|
|
361
|
+
"""
|
|
362
|
+
keywords = [] if keywords is None else keywords
|
|
363
|
+
gen_dsets = []
|
|
364
|
+
|
|
365
|
+
saint_df = self._load_zmadex_saint()
|
|
366
|
+
arm_df = self._select_drug_arm(saint_df)
|
|
367
|
+
filtered_df = self._apply_filter(arm_df)
|
|
368
|
+
|
|
369
|
+
if len(filtered_df) == 0:
|
|
370
|
+
raise CellMapsError('Filter ' + str(self._filter) + ' left no rows '
|
|
371
|
+
'for --drug ' + str(self._drug) +
|
|
372
|
+
'. Refusing to write an empty ' +
|
|
373
|
+
constants.APMS_TSV_FILE)
|
|
374
|
+
|
|
375
|
+
gen_dsets.append(self._write_and_register(filtered_df,
|
|
376
|
+
constants.APMS_TSV_FILE,
|
|
377
|
+
name='AP-MS file',
|
|
378
|
+
desc=description + ' AP-MS file',
|
|
379
|
+
keywords=keywords))
|
|
380
|
+
|
|
381
|
+
if not self._no_unfiltered:
|
|
382
|
+
gen_dsets.append(self._write_and_register(arm_df,
|
|
383
|
+
constants.APMS_UNFILTERED_TSV_FILE,
|
|
384
|
+
name='AP-MS unfiltered file',
|
|
385
|
+
desc=description +
|
|
386
|
+
' complete pre-filter AP-MS matrix, '
|
|
387
|
+
'same schema as ' +
|
|
388
|
+
constants.APMS_TSV_FILE,
|
|
389
|
+
keywords=keywords))
|
|
390
|
+
|
|
391
|
+
if self._drug_effect_input is not None:
|
|
392
|
+
drug_df = self._load_msstats()
|
|
393
|
+
drug_df = self._restrict_drug_effect(drug_df, arm_df, filtered_df)
|
|
394
|
+
self._check_drug_effect_coverage(filtered_df, drug_df)
|
|
395
|
+
gen_dsets.append(self._write_and_register(drug_df,
|
|
396
|
+
constants.APMS_DRUG_EFFECT_TSV_FILE,
|
|
397
|
+
name='AP-MS drug effect file',
|
|
398
|
+
desc=description +
|
|
399
|
+
' MSstats vorinostat versus DMSO '
|
|
400
|
+
'contrast keyed on Batch, Bait and '
|
|
401
|
+
'Prey. Not an interaction table',
|
|
402
|
+
keywords=keywords))
|
|
403
|
+
|
|
404
|
+
self._check_gene_set(arm_df)
|
|
405
|
+
gen_dsets.append(self._write_qc(description=description,
|
|
406
|
+
keywords=keywords))
|
|
407
|
+
return gen_dsets
|
|
408
|
+
|
|
409
|
+
def _read_table(self, input_file):
|
|
410
|
+
"""
|
|
411
|
+
Reads **input_file** keeping every value as text so the floats in
|
|
412
|
+
the source round trip unchanged and blanks stay blank
|
|
413
|
+
|
|
414
|
+
:return: contents of file
|
|
415
|
+
:rtype: :py:class:`pandas.DataFrame`
|
|
416
|
+
"""
|
|
417
|
+
thesep = ',' if input_file.endswith('.csv') else '\t'
|
|
418
|
+
return pd.read_csv(input_file, sep=thesep, dtype=str, na_filter=False)
|
|
419
|
+
|
|
420
|
+
def _load_zmadex_saint(self):
|
|
421
|
+
"""
|
|
422
|
+
Loads the Zmadex/SAINT file and renames its columns to the
|
|
423
|
+
normalized AP-MS schema
|
|
424
|
+
|
|
425
|
+
:raises CellMapsError: if a required column is missing
|
|
426
|
+
:rtype: :py:class:`pandas.DataFrame`
|
|
427
|
+
"""
|
|
428
|
+
df = self._read_table(self._inputs[0])
|
|
429
|
+
required = list(APMSDataLoader.ZMADEX_SAINT_COL_MAP.keys())
|
|
430
|
+
required.append(APMSDataLoader.DRUG_COL)
|
|
431
|
+
missing = [x for x in required if x not in df.columns]
|
|
432
|
+
if len(missing) > 0:
|
|
433
|
+
raise CellMapsError('Zmadex/SAINT input ' + str(self._inputs[0]) +
|
|
434
|
+
' is missing required column(s): ' + str(missing))
|
|
435
|
+
self._qc['zmadex_saint_input_file'] = os.path.basename(self._inputs[0])
|
|
436
|
+
self._qc['zmadex_saint_input_rows'] = len(df)
|
|
437
|
+
return df.rename(columns=APMSDataLoader.ZMADEX_SAINT_COL_MAP)
|
|
438
|
+
|
|
439
|
+
def _select_drug_arm(self, df):
|
|
440
|
+
"""
|
|
441
|
+
Selects the rows matching ``--drug`` and drops the drug column,
|
|
442
|
+
leaving the schema identical for every treatment
|
|
443
|
+
|
|
444
|
+
:raises CellMapsError: if the drug is absent or the key is not unique
|
|
445
|
+
:rtype: :py:class:`pandas.DataFrame`
|
|
446
|
+
"""
|
|
447
|
+
found_drugs = sorted(df[APMSDataLoader.DRUG_COL].unique())
|
|
448
|
+
self._qc['drugs_in_input'] = found_drugs
|
|
449
|
+
unexpected = [x for x in found_drugs
|
|
450
|
+
if x not in APMSDataLoader.KNOWN_DRUGS]
|
|
451
|
+
if len(unexpected) > 0:
|
|
452
|
+
logger.warning('Unexpected value(s) in ' +
|
|
453
|
+
APMSDataLoader.DRUG_COL + ' column: ' +
|
|
454
|
+
str(unexpected))
|
|
455
|
+
if self._drug not in found_drugs:
|
|
456
|
+
raise CellMapsError('--drug ' + str(self._drug) + ' not found in ' +
|
|
457
|
+
APMSDataLoader.DRUG_COL + ' column. Found: ' +
|
|
458
|
+
str(found_drugs))
|
|
459
|
+
|
|
460
|
+
arm_df = df[df[APMSDataLoader.DRUG_COL] == self._drug]
|
|
461
|
+
arm_df = arm_df[APMSDataLoader.APMS_COLS].reset_index(drop=True)
|
|
462
|
+
|
|
463
|
+
dupes = int(arm_df.duplicated(APMSDataLoader.KEY_COLS).sum())
|
|
464
|
+
if dupes > 0:
|
|
465
|
+
raise CellMapsError(str(dupes) + ' duplicate ' +
|
|
466
|
+
str(APMSDataLoader.KEY_COLS) + ' row(s) found '
|
|
467
|
+
'for --drug ' + str(self._drug))
|
|
468
|
+
self._qc['drug_arm'] = self._drug
|
|
469
|
+
self._qc['apms_rows_before_filter'] = len(arm_df)
|
|
470
|
+
self._qc['apms_duplicate_keys'] = dupes
|
|
471
|
+
for colname in APMSDataLoader.PASS_COLS:
|
|
472
|
+
self._qc[colname + '_true'] = int((arm_df[colname] ==
|
|
473
|
+
APMSDataLoader.TRUE_VAL).sum())
|
|
474
|
+
self._qc['apms_rows_with_blank_scores'] = int((arm_df['Zmadex'] == '').sum())
|
|
475
|
+
return arm_df
|
|
476
|
+
|
|
477
|
+
def _apply_filter(self, df):
|
|
478
|
+
"""
|
|
479
|
+
Keeps rows holding ``TRUE`` in any column the ``--filter`` mode names.
|
|
480
|
+
|
|
481
|
+
Blanks need no special handling, an empty string is simply not
|
|
482
|
+
``TRUE``, which is why the frame is read with ``dtype=str`` and
|
|
483
|
+
``na_filter=False``
|
|
484
|
+
|
|
485
|
+
:rtype: :py:class:`pandas.DataFrame`
|
|
486
|
+
"""
|
|
487
|
+
cols = APMSDataLoader.FILTER_MODES[self._filter]
|
|
488
|
+
self._qc['filter'] = self._filter
|
|
489
|
+
if len(cols) == 0:
|
|
490
|
+
self._qc['apms_rows_after_filter'] = len(df)
|
|
491
|
+
return df
|
|
492
|
+
filtered_df = df[(df[cols] == APMSDataLoader.TRUE_VAL).any(axis=1)]
|
|
493
|
+
filtered_df = filtered_df.reset_index(drop=True)
|
|
494
|
+
self._qc['apms_rows_after_filter'] = len(filtered_df)
|
|
495
|
+
logger.info('Filter ' + str(self._filter) + ' kept ' +
|
|
496
|
+
str(len(filtered_df)) + ' of ' + str(len(df)) + ' rows')
|
|
497
|
+
return filtered_df
|
|
498
|
+
|
|
499
|
+
def _load_msstats(self):
|
|
500
|
+
"""
|
|
501
|
+
Loads the MSstats file, drops the excluded baits and renames the
|
|
502
|
+
columns to the normalized drug effect schema.
|
|
503
|
+
|
|
504
|
+
Protein groups are counted and written through unchanged. Exploding
|
|
505
|
+
them gains 0.55% more keys while creating tens of thousands of
|
|
506
|
+
ambiguous ones, and every key that survives the AP-MS filter is
|
|
507
|
+
already covered without it
|
|
508
|
+
|
|
509
|
+
:raises CellMapsError: if a required column is missing
|
|
510
|
+
:rtype: :py:class:`pandas.DataFrame`
|
|
511
|
+
"""
|
|
512
|
+
df = self._read_table(self._drug_effect_input)
|
|
513
|
+
missing = [x for x in APMSDataLoader.MSSTATS_COL_MAP
|
|
514
|
+
if x not in df.columns]
|
|
515
|
+
if len(missing) > 0:
|
|
516
|
+
raise CellMapsError('MSstats input ' + str(self._drug_effect_input) +
|
|
517
|
+
' is missing required column(s): ' + str(missing))
|
|
518
|
+
self._qc['msstats_input_file'] = os.path.basename(self._drug_effect_input)
|
|
519
|
+
self._qc['msstats_input_rows'] = len(df)
|
|
520
|
+
|
|
521
|
+
excluded = {}
|
|
522
|
+
for bait in self._exclude_baits:
|
|
523
|
+
excluded[bait] = int((df['Bait'] == bait).sum())
|
|
524
|
+
df = df[df['Bait'] != bait]
|
|
525
|
+
self._qc['msstats_rows_excluded_by_bait'] = excluded
|
|
526
|
+
self._qc['msstats_rows_after_bait_exclusion'] = len(df)
|
|
527
|
+
|
|
528
|
+
self._qc['msstats_protein_group_rows'] = int(df['Protein'].str.contains(';').sum())
|
|
529
|
+
|
|
530
|
+
unexpected = [x for x in sorted(df['Effect'].unique())
|
|
531
|
+
if x not in APMSDataLoader.KNOWN_DRUG_EFFECTS]
|
|
532
|
+
if len(unexpected) > 0:
|
|
533
|
+
logger.warning('Unexpected value(s) in Effect column: ' +
|
|
534
|
+
str(unexpected))
|
|
535
|
+
|
|
536
|
+
df = df.rename(columns=APMSDataLoader.MSSTATS_COL_MAP)
|
|
537
|
+
df = df[APMSDataLoader.DRUG_EFFECT_COLS].reset_index(drop=True)
|
|
538
|
+
self._qc['drug_effect_categories'] = {str(k): int(v) for k, v in
|
|
539
|
+
df['DrugEffect'].value_counts().items()}
|
|
540
|
+
return df
|
|
541
|
+
|
|
542
|
+
def _restrict_drug_effect(self, drug_df, arm_df, filtered_df):
|
|
543
|
+
"""
|
|
544
|
+
Applies ``--drug_effect_rows``. This is a key membership test, it
|
|
545
|
+
selects rows and never adds a column or changes row multiplicity.
|
|
546
|
+
|
|
547
|
+
``all`` is a no-op and is the only mode computable from the MSstats
|
|
548
|
+
file alone. ``apms_keys`` gives the same result as
|
|
549
|
+
``saint_universe`` when ``--filter none`` is in effect, since the
|
|
550
|
+
unfiltered arm is the SAINT universe for that arm
|
|
551
|
+
|
|
552
|
+
:rtype: :py:class:`pandas.DataFrame`
|
|
553
|
+
"""
|
|
554
|
+
self._qc['drug_effect_rows'] = self._drug_effect_rows
|
|
555
|
+
before = len(drug_df)
|
|
556
|
+
self._qc['drug_effect_rows_before_restriction'] = before
|
|
557
|
+
|
|
558
|
+
if self._drug_effect_rows == APMSDataLoader.DRUG_EFFECT_ROWS_SAINT_UNIVERSE:
|
|
559
|
+
target_df = arm_df
|
|
560
|
+
elif self._drug_effect_rows == APMSDataLoader.DRUG_EFFECT_ROWS_APMS_KEYS:
|
|
561
|
+
target_df = filtered_df
|
|
562
|
+
else:
|
|
563
|
+
self._qc['drug_effect_rows_dropped_by_restriction'] = 0
|
|
564
|
+
self._qc['drug_effect_rows_written'] = before
|
|
565
|
+
return drug_df
|
|
566
|
+
|
|
567
|
+
drug_keys = pd.MultiIndex.from_frame(drug_df[APMSDataLoader.KEY_COLS])
|
|
568
|
+
target_keys = pd.MultiIndex.from_frame(target_df[APMSDataLoader.KEY_COLS])
|
|
569
|
+
drug_df = drug_df[drug_keys.isin(target_keys)].reset_index(drop=True)
|
|
570
|
+
|
|
571
|
+
self._qc['drug_effect_rows_dropped_by_restriction'] = before - len(drug_df)
|
|
572
|
+
self._qc['drug_effect_rows_written'] = len(drug_df)
|
|
573
|
+
logger.info('--drug_effect_rows ' + str(self._drug_effect_rows) +
|
|
574
|
+
' kept ' + str(len(drug_df)) + ' of ' + str(before) + ' rows')
|
|
575
|
+
return drug_df
|
|
576
|
+
|
|
577
|
+
def _check_drug_effect_coverage(self, filtered_df, drug_df):
|
|
578
|
+
"""
|
|
579
|
+
The only place the join between the two inputs survives. It reports
|
|
580
|
+
how much of apms.tsv the drug effect table covers and writes nothing
|
|
581
|
+
|
|
582
|
+
"""
|
|
583
|
+
apms_keys = pd.MultiIndex.from_frame(filtered_df[APMSDataLoader.KEY_COLS])
|
|
584
|
+
drug_keys = pd.MultiIndex.from_frame(drug_df[APMSDataLoader.KEY_COLS])
|
|
585
|
+
covered = int(apms_keys.isin(drug_keys).sum())
|
|
586
|
+
total = len(apms_keys)
|
|
587
|
+
self._qc['apms_keys'] = total
|
|
588
|
+
self._qc['apms_keys_with_drug_effect'] = covered
|
|
589
|
+
self._qc['apms_keys_without_drug_effect'] = total - covered
|
|
590
|
+
if covered < total:
|
|
591
|
+
logger.warning(str(total - covered) + ' of ' + str(total) +
|
|
592
|
+
' rows in ' + constants.APMS_TSV_FILE +
|
|
593
|
+
' have no match in ' +
|
|
594
|
+
constants.APMS_DRUG_EFFECT_TSV_FILE +
|
|
595
|
+
'. The two inputs may have drifted out of sync')
|
|
596
|
+
|
|
597
|
+
def _check_gene_set(self, arm_df):
|
|
598
|
+
"""
|
|
599
|
+
Compares the baits against the published CM4AI gene set, resolving
|
|
600
|
+
legacy aliases first. Reference material, never a release gate, so
|
|
601
|
+
this only warns
|
|
602
|
+
|
|
603
|
+
"""
|
|
604
|
+
baits = sorted(arm_df['Bait'].unique())
|
|
605
|
+
self._qc['bait_count'] = len(baits)
|
|
606
|
+
gene_set = APMSDataLoader.GENE_SETS.get(self._gene_set)
|
|
607
|
+
if gene_set is None:
|
|
608
|
+
return
|
|
609
|
+
unlisted = [x for x in baits
|
|
610
|
+
if APMSDataLoader.GENE_SET_ALIASES.get(x, x) not in gene_set]
|
|
611
|
+
self._qc['baits_not_in_gene_set'] = unlisted
|
|
612
|
+
if len(unlisted) > 0:
|
|
613
|
+
logger.warning(str(len(unlisted)) + ' bait(s) are not on the '
|
|
614
|
+
'published ' + str(self._gene_set) + ' gene set: ' +
|
|
615
|
+
str(unlisted))
|
|
616
|
+
|
|
617
|
+
def _write_and_register(self, df, filename, name='', desc='', keywords=None):
|
|
618
|
+
"""
|
|
619
|
+
Writes **df** into the RO-Crate and registers it as a dataset
|
|
620
|
+
|
|
621
|
+
:return: id of registered dataset
|
|
622
|
+
:rtype: str
|
|
623
|
+
"""
|
|
624
|
+
keywords = [] if keywords is None else keywords
|
|
625
|
+
file_path = os.path.join(self._outdir, filename)
|
|
626
|
+
df.to_csv(file_path, sep='\t', index=False)
|
|
627
|
+
logger.info('Wrote ' + str(len(df)) + ' rows to ' + file_path)
|
|
628
|
+
file_keywords = keywords.copy()
|
|
629
|
+
file_keywords.extend(['file'])
|
|
630
|
+
return self._provenance_utils.register_dataset(rocrate_path=self._outdir,
|
|
631
|
+
source_file=file_path,
|
|
632
|
+
skip_copy=True,
|
|
633
|
+
data_dict={'name': name,
|
|
634
|
+
'description': desc,
|
|
635
|
+
'keywords': file_keywords,
|
|
636
|
+
'data-format': 'tsv',
|
|
637
|
+
'author': self._author,
|
|
638
|
+
'version': self._release,
|
|
639
|
+
'date-published': date.today().strftime('%Y-%m-%d')},
|
|
640
|
+
guid=self._get_fairscape_id())
|
|
641
|
+
|
|
642
|
+
def _write_qc(self, description='', keywords=None):
|
|
643
|
+
"""
|
|
644
|
+
Writes the QC/summary statistics file and registers it
|
|
645
|
+
|
|
646
|
+
:return: id of registered dataset
|
|
647
|
+
:rtype: str
|
|
648
|
+
"""
|
|
649
|
+
keywords = [] if keywords is None else keywords
|
|
650
|
+
qc_path = os.path.join(self._outdir, constants.APMS_QC_FILE)
|
|
651
|
+
with open(qc_path, 'w') as f:
|
|
652
|
+
json.dump(self._qc, f, indent=2, sort_keys=True)
|
|
653
|
+
logger.info('AP-MS summary: ' + json.dumps(self._qc, sort_keys=True))
|
|
654
|
+
file_keywords = keywords.copy()
|
|
655
|
+
file_keywords.extend(['file'])
|
|
656
|
+
qc_desc = description + ' AP-MS summary statistics'
|
|
657
|
+
return self._provenance_utils.register_dataset(rocrate_path=self._outdir,
|
|
658
|
+
source_file=qc_path,
|
|
659
|
+
skip_copy=True,
|
|
660
|
+
data_dict={'name': 'AP-MS QC file',
|
|
661
|
+
'description': qc_desc,
|
|
662
|
+
'keywords': file_keywords,
|
|
663
|
+
'data-format': 'json',
|
|
664
|
+
'author': self._author,
|
|
665
|
+
'version': self._release,
|
|
666
|
+
'date-published': date.today().strftime('%Y-%m-%d')},
|
|
667
|
+
guid=self._get_fairscape_id())
|
|
668
|
+
|
|
669
|
+
def _get_fairscape_id(self):
|
|
670
|
+
"""
|
|
671
|
+
Creates a unique id
|
|
672
|
+
:return:
|
|
673
|
+
"""
|
|
674
|
+
return str(uuid.uuid4()) + ':' + os.path.basename(self._outdir)
|
|
675
|
+
|
|
676
|
+
def _get_dataset_summary(self):
|
|
677
|
+
"""
|
|
678
|
+
Builds the description of what this RO-Crate actually holds, so the
|
|
679
|
+
readme reports the filter and row set that were used rather than a
|
|
680
|
+
hard coded guess
|
|
681
|
+
|
|
682
|
+
:rtype: str
|
|
683
|
+
"""
|
|
684
|
+
lines = ['Contents of this RO-Crate',
|
|
685
|
+
'-------------------------',
|
|
686
|
+
'',
|
|
687
|
+
'Treatment: ' + str(self._treatment) +
|
|
688
|
+
' (drug arm ' + str(self._drug) + ')',
|
|
689
|
+
'',
|
|
690
|
+
constants.APMS_TSV_FILE + ': ' +
|
|
691
|
+
str(self._qc.get('apms_rows_after_filter', 'unknown')) +
|
|
692
|
+
' rows, filtered with --filter ' + str(self._filter) +
|
|
693
|
+
' from ' + str(self._qc.get('apms_rows_before_filter', 'unknown')) +
|
|
694
|
+
' rows.']
|
|
695
|
+
if self._no_unfiltered:
|
|
696
|
+
lines.append('')
|
|
697
|
+
lines.append('This RO-Crate does not include ' +
|
|
698
|
+
constants.APMS_UNFILTERED_TSV_FILE + '.')
|
|
699
|
+
else:
|
|
700
|
+
lines.append('')
|
|
701
|
+
lines.append(constants.APMS_UNFILTERED_TSV_FILE + ': ' +
|
|
702
|
+
str(self._qc.get('apms_rows_before_filter', 'unknown')) +
|
|
703
|
+
' rows, the complete pre-filter matrix with the '
|
|
704
|
+
'identical schema. Nothing is dropped from it.')
|
|
705
|
+
|
|
706
|
+
lines.append('')
|
|
707
|
+
if self._drug_effect_input is None:
|
|
708
|
+
lines.append('This RO-Crate has no ' +
|
|
709
|
+
constants.APMS_DRUG_EFFECT_TSV_FILE + ' by design. The '
|
|
710
|
+
'MSstats contrast is vorinostat versus DMSO, so it is '
|
|
711
|
+
'published with the vorinostat RO-Crate only.')
|
|
712
|
+
else:
|
|
713
|
+
lines.append(constants.APMS_DRUG_EFFECT_TSV_FILE + ': ' +
|
|
714
|
+
str(self._qc.get('drug_effect_rows_written', 'unknown')) +
|
|
715
|
+
' rows, selected with --drug_effect_rows ' +
|
|
716
|
+
str(self._drug_effect_rows) + '.')
|
|
717
|
+
if self._drug_effect_rows == APMSDataLoader.DRUG_EFFECT_ROWS_ALL:
|
|
718
|
+
lines.append('')
|
|
719
|
+
lines.append('This is the complete MSstats output minus the '
|
|
720
|
+
'excluded bait(s) ' + str(self._exclude_baits) +
|
|
721
|
+
'. It therefore holds many more rows than ' +
|
|
722
|
+
constants.APMS_TSV_FILE + ', including preys that '
|
|
723
|
+
'are not in the AP-MS tables at all.')
|
|
724
|
+
lines.append('')
|
|
725
|
+
lines.append('Of the ' + str(self._qc.get('apms_keys', 'unknown')) +
|
|
726
|
+
' rows in ' + constants.APMS_TSV_FILE + ', ' +
|
|
727
|
+
str(self._qc.get('apms_keys_with_drug_effect', 'unknown')) +
|
|
728
|
+
' have a drug effect record.')
|
|
729
|
+
return '\n'.join(lines)
|
|
730
|
+
|
|
731
|
+
def _copy_over_apms_readme(self):
|
|
732
|
+
"""
|
|
733
|
+
Copies over the readme matching the input format. A reader of a given
|
|
734
|
+
RO-Crate should see only the columns that RO-Crate holds
|
|
735
|
+
|
|
736
|
+
"""
|
|
737
|
+
if self._input_format == APMSDataLoader.ZMADEX_SAINT_FORMAT:
|
|
738
|
+
src = os.path.join(os.path.dirname(__file__),
|
|
739
|
+
'apms_zmadex_saint_readme.txt')
|
|
740
|
+
with open(src, 'r') as f:
|
|
741
|
+
readme = f.read()
|
|
742
|
+
readme = readme.replace(APMSDataLoader.SUMMARY_MARKER,
|
|
743
|
+
self._get_dataset_summary())
|
|
744
|
+
with open(os.path.join(self._outdir, 'readme.txt'), 'w') as f:
|
|
745
|
+
f.write(readme)
|
|
746
|
+
return
|
|
747
|
+
apms_readme = os.path.join(os.path.dirname(__file__), 'apms_readme.txt')
|
|
748
|
+
shutil.copy(apms_readme, os.path.join(self._outdir, 'readme.txt'))
|
|
749
|
+
|
|
750
|
+
def _generate_rocrate_dir_path(self):
|
|
751
|
+
"""
|
|
752
|
+
Generates a directory path for the RO-Crate based on provided metadata like project name, gene set,
|
|
753
|
+
cell line, treatment, and release version.
|
|
754
|
+
"""
|
|
755
|
+
dir_name = self._project_name.lower() + '_'
|
|
756
|
+
if self._gene_set is not None:
|
|
757
|
+
dir_name += self._gene_set.lower() + '_'
|
|
758
|
+
dir_name += self._cell_line.lower() + '_'
|
|
759
|
+
dir_name += self._treatment.lower()
|
|
760
|
+
if self._set_name is not None:
|
|
761
|
+
dir_name += '_' + self._set_name
|
|
762
|
+
dir_name += '_apms_'
|
|
763
|
+
dir_name += self._release.lower()
|
|
764
|
+
dir_name = dir_name.replace(' ', '_')
|
|
765
|
+
self._outdir = os.path.join(self._outdir, dir_name)
|
|
766
|
+
|
|
767
|
+
def _merge_and_save_apms_data(self):
|
|
768
|
+
"""
|
|
769
|
+
Merges AP-MS data from input files into a single DataFrame and saves the combined data to a TSV file within
|
|
770
|
+
the RO-Crate directory.
|
|
771
|
+
|
|
772
|
+
:return: The file path to the saved AP-MS data TSV file.
|
|
773
|
+
"""
|
|
774
|
+
df_list = []
|
|
775
|
+
for input in self._inputs:
|
|
776
|
+
thesep = '\t'
|
|
777
|
+
if input.endswith('.csv'):
|
|
778
|
+
thesep = ','
|
|
779
|
+
cur_df = pd.read_csv(input, sep=thesep, na_filter=False)
|
|
780
|
+
if self._baitcolname != 'Bait':
|
|
781
|
+
if self._baitcolname in cur_df.columns:
|
|
782
|
+
cur_df.rename({self._baitcolname: 'Bait'}, axis=1, inplace=True)
|
|
783
|
+
# Handles case where HDAC2 in initial cm4ai dataset
|
|
784
|
+
# had several columns lacking .x suffix
|
|
785
|
+
# we are fixing this by checking for those columns and if
|
|
786
|
+
# found just renaming them in place
|
|
787
|
+
for colname in ['PreyGene', 'NumReplicates', 'AvgP',
|
|
788
|
+
'MaxP', 'TopoAvgP', 'TopoMaxP',
|
|
789
|
+
'SaintScore', 'FoldChange', 'BFDR',
|
|
790
|
+
'boosted_by']:
|
|
791
|
+
if colname in cur_df.columns:
|
|
792
|
+
if colname + '.x' in cur_df.columns:
|
|
793
|
+
cur_df.drop(columns=colname + '.x', inplace=True)
|
|
794
|
+
cur_df.rename({colname: colname + '.x'}, axis=1, inplace=True)
|
|
795
|
+
|
|
796
|
+
df_list.append(cur_df)
|
|
797
|
+
|
|
798
|
+
df = pd.concat(df_list)
|
|
799
|
+
|
|
800
|
+
apms_path = os.path.join(self._outdir, constants.APMS_TSV_FILE)
|
|
801
|
+
|
|
802
|
+
df.to_csv(apms_path, sep='\t', index=False)
|
|
803
|
+
return apms_path
|
|
804
|
+
|
|
805
|
+
def _register_computation(self, generated_dataset_ids=[],
|
|
806
|
+
description='',
|
|
807
|
+
keywords=[]):
|
|
808
|
+
"""
|
|
809
|
+
Registers the computation process in the RO-Crate
|
|
810
|
+
# Todo: added in used dataset, software and what is being generated
|
|
811
|
+
:return:
|
|
812
|
+
"""
|
|
813
|
+
logger.debug('Getting id of input rocrate')
|
|
814
|
+
comp_keywords = keywords.copy()
|
|
815
|
+
comp_keywords.extend(['computation'])
|
|
816
|
+
description = description + ' run of ' + cellmaps_utils.__name__
|
|
817
|
+
self._provenance_utils.register_computation(self._outdir,
|
|
818
|
+
name='AP-MS',
|
|
819
|
+
run_by=str(self._provenance_utils.get_login()),
|
|
820
|
+
command=str(self._input_data_dict),
|
|
821
|
+
description=description,
|
|
822
|
+
keywords=comp_keywords,
|
|
823
|
+
used_software=[self._softwareid],
|
|
824
|
+
generated=generated_dataset_ids,
|
|
825
|
+
guid=self._get_fairscape_id())
|
|
826
|
+
|
|
827
|
+
def _register_software(self, description='',
|
|
828
|
+
keywords=[]):
|
|
829
|
+
"""
|
|
830
|
+
Registers this tool
|
|
831
|
+
|
|
832
|
+
:raises CellMapsImageEmbeddingError: If fairscape call fails
|
|
833
|
+
"""
|
|
834
|
+
software_keywords = keywords.copy()
|
|
835
|
+
software_keywords.extend(['tools', cellmaps_utils.__name__])
|
|
836
|
+
software_description = description + ' ' + \
|
|
837
|
+
cellmaps_utils.__description__
|
|
838
|
+
self._softwareid = self._provenance_utils.register_software(self._outdir,
|
|
839
|
+
name=cellmaps_utils.__name__,
|
|
840
|
+
description=software_description,
|
|
841
|
+
author=cellmaps_utils.__author__,
|
|
842
|
+
version=cellmaps_utils.__version__,
|
|
843
|
+
file_format='py',
|
|
844
|
+
keywords=software_keywords,
|
|
845
|
+
url=cellmaps_utils.__repo_url__,
|
|
846
|
+
guid=self._get_fairscape_id())
|
|
847
|
+
|
|
848
|
+
def add_subparser(subparsers):
|
|
849
|
+
"""
|
|
850
|
+
Adds a command-line subparser for the APMSDataLoader tool.
|
|
851
|
+
|
|
852
|
+
:return:
|
|
853
|
+
"""
|
|
854
|
+
desc = """
|
|
855
|
+
|
|
856
|
+
Version {version}
|
|
857
|
+
|
|
858
|
+
{cmd} Loads AP-MS data into a RO-Crate
|
|
859
|
+
""".format(version=cellmaps_utils.__version__,
|
|
860
|
+
cmd=APMSDataLoader.COMMAND)
|
|
861
|
+
|
|
862
|
+
parser = subparsers.add_parser(APMSDataLoader.COMMAND,
|
|
863
|
+
help='Loads AP-MS data into a RO-Crate',
|
|
864
|
+
description=desc,
|
|
865
|
+
formatter_class=constants.ArgParseFormatter)
|
|
866
|
+
parser.add_argument('outdir',
|
|
867
|
+
help='Directory where RO-Crate will be created')
|
|
868
|
+
parser.add_argument('--inputs', required=True, nargs="+",
|
|
869
|
+
help='One or more table files with the following '
|
|
870
|
+
'fields: [Bait, Prey] and for filtering also '
|
|
871
|
+
'containing [BFDR.x, logOddsScore')
|
|
872
|
+
parser.add_argument('--input_format',
|
|
873
|
+
choices=APMSDataLoader.INPUT_FORMATS,
|
|
874
|
+
default=APMSDataLoader.LEGACY_FORMAT,
|
|
875
|
+
help='Format of --inputs. "' +
|
|
876
|
+
APMSDataLoader.LEGACY_FORMAT + '" merges the '
|
|
877
|
+
'original SAINT tables unchanged. "' +
|
|
878
|
+
APMSDataLoader.ZMADEX_SAINT_FORMAT + '" expects '
|
|
879
|
+
'exactly one Zmadex/SAINT table and requires --drug')
|
|
880
|
+
parser.add_argument('--drug', choices=APMSDataLoader.KNOWN_DRUGS,
|
|
881
|
+
help='Treatment arm to select from the drug column '
|
|
882
|
+
'of a Zmadex/SAINT input. Required with '
|
|
883
|
+
'--input_format ' +
|
|
884
|
+
APMSDataLoader.ZMADEX_SAINT_FORMAT)
|
|
885
|
+
parser.add_argument('--drug_effect_input',
|
|
886
|
+
help='MSstats drug effect table. Written to ' +
|
|
887
|
+
constants.APMS_DRUG_EFFECT_TSV_FILE + ' as its '
|
|
888
|
+
'own file so ' + constants.APMS_TSV_FILE +
|
|
889
|
+
' keeps the same schema in every RO-Crate. The '
|
|
890
|
+
'contrast is vorinostat versus DMSO, so pass '
|
|
891
|
+
'this on the treated run only')
|
|
892
|
+
parser.add_argument('--drug_effect_rows',
|
|
893
|
+
choices=APMSDataLoader.DRUG_EFFECT_ROWS_CHOICES,
|
|
894
|
+
default=APMSDataLoader.DRUG_EFFECT_ROWS_ALL,
|
|
895
|
+
help='Which MSstats rows to publish. "all" is the '
|
|
896
|
+
'complete table minus --exclude_baits, '
|
|
897
|
+
'"saint_universe" keeps rows whose key is in '
|
|
898
|
+
'the drug arm, "apms_keys" keeps rows whose key '
|
|
899
|
+
'is in the written ' + constants.APMS_TSV_FILE)
|
|
900
|
+
parser.add_argument('--exclude_baits', nargs='+', default=['MDA'],
|
|
901
|
+
help='Bait(s) to drop from --drug_effect_input. The '
|
|
902
|
+
'default is the parental cell line control')
|
|
903
|
+
parser.add_argument('--filter',
|
|
904
|
+
choices=list(APMSDataLoader.FILTER_MODES.keys()),
|
|
905
|
+
default=APMSDataLoader.FILTER_ANY_PASS,
|
|
906
|
+
help='Rows to keep in ' + constants.APMS_TSV_FILE +
|
|
907
|
+
'. "none" writes the complete matrix. Applies '
|
|
908
|
+
'to the AP-MS tables only, never to ' +
|
|
909
|
+
constants.APMS_DRUG_EFFECT_TSV_FILE)
|
|
910
|
+
parser.add_argument('--no_unfiltered', action='store_true',
|
|
911
|
+
help='Do not write ' +
|
|
912
|
+
constants.APMS_UNFILTERED_TSV_FILE)
|
|
913
|
+
parser.add_argument('--author', default='Krogan Lab',
|
|
914
|
+
help='Author that created this data')
|
|
915
|
+
parser.add_argument('--name', default='AP-MS',
|
|
916
|
+
help='Name of this run, needed for FAIRSCAPE')
|
|
917
|
+
parser.add_argument('--organization_name', default='Krogan Lab',
|
|
918
|
+
help='Name of organization running this tool, needed '
|
|
919
|
+
'for FAIRSCAPE. Usually set to lab')
|
|
920
|
+
parser.add_argument('--project_name', default='CM4AI',
|
|
921
|
+
help='Name of project running this tool, needed for '
|
|
922
|
+
'FAIRSCAPE. Usually set to funding source')
|
|
923
|
+
parser.add_argument('--release', required=True,
|
|
924
|
+
help='Version of release. For example: 0.1 alpha')
|
|
925
|
+
parser.add_argument('--treatment', default='untreated',
|
|
926
|
+
choices=['paclitaxel', 'vorinostat', 'untreated'],
|
|
927
|
+
help='Treatment of sample.')
|
|
928
|
+
parser.add_argument('--cell_line', default='MDA-MB-468',
|
|
929
|
+
help='Name of cell line. For example MDA-MB-468')
|
|
930
|
+
parser.add_argument('--gene_set', choices=['chromatin', 'metabolic'],
|
|
931
|
+
default='chromatin',
|
|
932
|
+
help='Gene set for dataset')
|
|
933
|
+
parser.add_argument('--set_name',
|
|
934
|
+
help='If set, adds value to RO-Crate folder name before _apms_<version>. '
|
|
935
|
+
'Example values set1')
|
|
936
|
+
parser.add_argument('--tissue', choices=['undifferentiated', 'neuron',
|
|
937
|
+
'cardiomyocytes', ''],
|
|
938
|
+
default='breast; mammary gland',
|
|
939
|
+
help='Tissue for dataset. Since the default --cell_line '
|
|
940
|
+
'is MDA-MB-468, this value is set to the tissue '
|
|
941
|
+
'for that cell line')
|
|
942
|
+
parser.add_argument('--baitcolname', default='Bait',
|
|
943
|
+
help='Name of bait column in input file(s)')
|
|
944
|
+
|
|
945
|
+
return parser
|