phosx 0.1.3__tar.gz
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.
- phosx-0.1.3/PKG-INFO +62 -0
- phosx-0.1.3/README.md +47 -0
- phosx-0.1.3/phosx/__init__.py +1 -0
- phosx-0.1.3/phosx/cli.py +68 -0
- phosx-0.1.3/phosx/data/pssm.h5 +0 -0
- phosx-0.1.3/phosx/pssm_enrichments.py +455 -0
- phosx-0.1.3/pyproject.toml +20 -0
phosx-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: phosx
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Alessandro Lussana
|
|
6
|
+
Author-email: alussana@ebi.ac.uk
|
|
7
|
+
Requires-Python: >=3.10,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Requires-Dist: h5py (>=3.9.0,<4.0.0)
|
|
12
|
+
Requires-Dist: pandas (>=2.0.3,<3.0.0)
|
|
13
|
+
Requires-Dist: seaborn (>=0.12.2,<0.13.0)
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# PhosX
|
|
17
|
+
|
|
18
|
+
Kinase activity inference from phosphosproteomics data based on substrate sequence specificity.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
### Build from source (requires [Poetry](https://python-poetry.org))
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
poetry build
|
|
26
|
+
pip install dist/*.whl
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Install from [PyPI](https://pypi.org)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
pip install phosx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
phosx [-h] [-v] [-p PSSM] [-n N_PERMUTATIONS] [-c N_PROC] [--plot-figures] [-o OUTPUT_DIR] seqrnk
|
|
39
|
+
```
|
|
40
|
+
```
|
|
41
|
+
positional arguments:
|
|
42
|
+
seqrnk Path to the seqrnk file.
|
|
43
|
+
|
|
44
|
+
options:
|
|
45
|
+
-h, --help show this help message and exit
|
|
46
|
+
-v, --version Print package version and exit
|
|
47
|
+
-p PSSM, --pssm PSSM Path to the h5 file storing custom PSSMs; defaults to built-in PSSMs
|
|
48
|
+
-n N_PERMUTATIONS, --n-permutations N_PERMUTATIONS
|
|
49
|
+
Number of random permutations; defaults to 10000
|
|
50
|
+
-c N_PROC, --n-proc N_PROC
|
|
51
|
+
Number of cores used for multithreading; defaults to 1
|
|
52
|
+
--plot-figures Save figures in svg format; see also --output_dir
|
|
53
|
+
-o OUTPUT_DIR, --output-dir OUTPUT_DIR
|
|
54
|
+
Output files directory; only relevant if used with --plot_figures; defaults to 'phosx_output/'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## TODO
|
|
58
|
+
|
|
59
|
+
- [ ] **Filter temporary warnings** - when plotting figures from `phosx.pssm_enrichments.ks_statistic` and `phosx.pssm_enrichments.compute_ks_pvalues`, many warnings will be thrown (`UserWarning: The figure layout has changed to tight`). This is a Matplotlib known issue: [https://github.com/matplotlib/matplotlib/issues/26290](https://github.com/matplotlib/matplotlib/issues/26290)
|
|
60
|
+
- [x] **Create dirs** - if not existent, for output files
|
|
61
|
+
- [ ] **Add docs**
|
|
62
|
+
- [ ] **FDR** - Report also the FDR q-value in the output table
|
phosx-0.1.3/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# PhosX
|
|
2
|
+
|
|
3
|
+
Kinase activity inference from phosphosproteomics data based on substrate sequence specificity.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Build from source (requires [Poetry](https://python-poetry.org))
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
poetry build
|
|
11
|
+
pip install dist/*.whl
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Install from [PyPI](https://pypi.org)
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
pip install phosx
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
phosx [-h] [-v] [-p PSSM] [-n N_PERMUTATIONS] [-c N_PROC] [--plot-figures] [-o OUTPUT_DIR] seqrnk
|
|
24
|
+
```
|
|
25
|
+
```
|
|
26
|
+
positional arguments:
|
|
27
|
+
seqrnk Path to the seqrnk file.
|
|
28
|
+
|
|
29
|
+
options:
|
|
30
|
+
-h, --help show this help message and exit
|
|
31
|
+
-v, --version Print package version and exit
|
|
32
|
+
-p PSSM, --pssm PSSM Path to the h5 file storing custom PSSMs; defaults to built-in PSSMs
|
|
33
|
+
-n N_PERMUTATIONS, --n-permutations N_PERMUTATIONS
|
|
34
|
+
Number of random permutations; defaults to 10000
|
|
35
|
+
-c N_PROC, --n-proc N_PROC
|
|
36
|
+
Number of cores used for multithreading; defaults to 1
|
|
37
|
+
--plot-figures Save figures in svg format; see also --output_dir
|
|
38
|
+
-o OUTPUT_DIR, --output-dir OUTPUT_DIR
|
|
39
|
+
Output files directory; only relevant if used with --plot_figures; defaults to 'phosx_output/'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## TODO
|
|
43
|
+
|
|
44
|
+
- [ ] **Filter temporary warnings** - when plotting figures from `phosx.pssm_enrichments.ks_statistic` and `phosx.pssm_enrichments.compute_ks_pvalues`, many warnings will be thrown (`UserWarning: The figure layout has changed to tight`). This is a Matplotlib known issue: [https://github.com/matplotlib/matplotlib/issues/26290](https://github.com/matplotlib/matplotlib/issues/26290)
|
|
45
|
+
- [x] **Create dirs** - if not existent, for output files
|
|
46
|
+
- [ ] **Add docs**
|
|
47
|
+
- [ ] **FDR** - Report also the FDR q-value in the output table
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.1.3'
|
phosx-0.1.3/phosx/cli.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from os import path
|
|
5
|
+
from phosx.pssm_enrichments import kinase_activities
|
|
6
|
+
|
|
7
|
+
def parse_phosx_args():
|
|
8
|
+
parser = argparse.ArgumentParser(
|
|
9
|
+
prog='phosx',
|
|
10
|
+
description='Kinase activity inference from phosphosproteomics data based on substrate sequence specificity',
|
|
11
|
+
epilog=''
|
|
12
|
+
)
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
'-v', '--version',
|
|
15
|
+
action="version",
|
|
16
|
+
version="v0.1.0",
|
|
17
|
+
help='Print package version and exit'
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
'seqrnk',
|
|
21
|
+
type=str,
|
|
22
|
+
help='Path to the seqrnk file.'
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'-p', '--pssm',
|
|
26
|
+
type=str,
|
|
27
|
+
default=str(path.join(path.dirname(__file__), 'data/pssm.h5')),
|
|
28
|
+
help='Path to the h5 file storing custom PSSMs; defaults to built-in PSSMs'
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
'-n', '--n-permutations',
|
|
32
|
+
type=int,
|
|
33
|
+
default=10000,
|
|
34
|
+
help='Number of random permutations; defaults to 10000'
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
'-c', '--n-proc',
|
|
38
|
+
type=int,
|
|
39
|
+
default=1,
|
|
40
|
+
help='Number of cores used for multithreading; defaults to 1'
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
'--plot-figures',
|
|
44
|
+
action='store_true',
|
|
45
|
+
help='Save figures in svg format; see also --output_dir'
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
'-o', '--output-dir',
|
|
49
|
+
type=str,
|
|
50
|
+
default='phosx_output/',
|
|
51
|
+
help='Output files directory; only relevant if used with --plot_figures; defaults to \'phosx_output/\''
|
|
52
|
+
)
|
|
53
|
+
args = parser.parse_args()
|
|
54
|
+
return args
|
|
55
|
+
|
|
56
|
+
def main():
|
|
57
|
+
args = parse_phosx_args()
|
|
58
|
+
kinase_activities(
|
|
59
|
+
args.seqrnk,
|
|
60
|
+
args.pssm,
|
|
61
|
+
args.n_permutations,
|
|
62
|
+
args.n_proc,
|
|
63
|
+
args.plot_figures,
|
|
64
|
+
args.output_dir
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if __name__ == '__main__':
|
|
68
|
+
main()
|
|
Binary file
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
import h5py
|
|
7
|
+
from random import shuffle
|
|
8
|
+
from multiprocessing import Pool
|
|
9
|
+
|
|
10
|
+
AA_LIST = ['G','P','A','V','L','I','M','C','F','Y','W','H','K','R','Q','N','E','D','S','T']
|
|
11
|
+
POSITIONS_LIST = list(range(-5,5))
|
|
12
|
+
|
|
13
|
+
def scale_neg1_pos1(
|
|
14
|
+
|
|
15
|
+
series:pd.Series
|
|
16
|
+
|
|
17
|
+
):
|
|
18
|
+
|
|
19
|
+
series = 2 * series / (series.max() - series.min())
|
|
20
|
+
series = series - series.min() - 1
|
|
21
|
+
|
|
22
|
+
return series
|
|
23
|
+
|
|
24
|
+
def scale_01(
|
|
25
|
+
|
|
26
|
+
series:pd.Series
|
|
27
|
+
|
|
28
|
+
):
|
|
29
|
+
|
|
30
|
+
series = series / (series.max() - series.min())
|
|
31
|
+
series = series - series.min()
|
|
32
|
+
|
|
33
|
+
return series
|
|
34
|
+
|
|
35
|
+
def score_sequence(
|
|
36
|
+
|
|
37
|
+
seq_str:str,
|
|
38
|
+
pssm_df:pd.DataFrame
|
|
39
|
+
|
|
40
|
+
):
|
|
41
|
+
|
|
42
|
+
if len(seq_str) != len(pssm_df):
|
|
43
|
+
|
|
44
|
+
raise Exception('Sequence length cannot be different from pssm length')
|
|
45
|
+
|
|
46
|
+
""" multiply probabilities instead of adding scores
|
|
47
|
+
p = 1
|
|
48
|
+
|
|
49
|
+
for i in range(len(seq_str)):
|
|
50
|
+
|
|
51
|
+
pos = list(pssm_df.index)[i]
|
|
52
|
+
p = p * pssm_df.loc[pos, seq_str[i]]
|
|
53
|
+
"""
|
|
54
|
+
p = 0
|
|
55
|
+
|
|
56
|
+
if pssm_df.loc[0, seq_str[5]] != 0:
|
|
57
|
+
|
|
58
|
+
for i in range(len(seq_str)):
|
|
59
|
+
|
|
60
|
+
if seq_str[i] != '_':
|
|
61
|
+
|
|
62
|
+
pos = list(pssm_df.index)[i]
|
|
63
|
+
p = p + pssm_df.loc[pos, seq_str[i]]
|
|
64
|
+
|
|
65
|
+
return p
|
|
66
|
+
|
|
67
|
+
def pssm_scoring(
|
|
68
|
+
|
|
69
|
+
seq: str,
|
|
70
|
+
pssm_df_dict: dict
|
|
71
|
+
|
|
72
|
+
):
|
|
73
|
+
|
|
74
|
+
record = {}
|
|
75
|
+
|
|
76
|
+
for kinase in pssm_df_dict.keys():
|
|
77
|
+
|
|
78
|
+
p = score_sequence(seq, pssm_df_dict[kinase])
|
|
79
|
+
record[kinase] = p
|
|
80
|
+
|
|
81
|
+
out_series = pd.Series(record)
|
|
82
|
+
|
|
83
|
+
return out_series
|
|
84
|
+
|
|
85
|
+
# take the largest deviation between test and null curves
|
|
86
|
+
def ks_statistic(
|
|
87
|
+
|
|
88
|
+
test_var_series:pd.Series,
|
|
89
|
+
null_var_df:pd.DataFrame,
|
|
90
|
+
plot_bool:bool=False,
|
|
91
|
+
out_plot_dir_str:str=''
|
|
92
|
+
|
|
93
|
+
):
|
|
94
|
+
|
|
95
|
+
kinase = test_var_series.name
|
|
96
|
+
|
|
97
|
+
deltas = []
|
|
98
|
+
running_sum_test = 0
|
|
99
|
+
running_sum_null = 0
|
|
100
|
+
|
|
101
|
+
for i in range(len(test_var_series)):
|
|
102
|
+
|
|
103
|
+
running_sum_test = running_sum_test + test_var_series[i]
|
|
104
|
+
running_sum_null = running_sum_null + null_var_df[kinase][i]
|
|
105
|
+
deltas.append(running_sum_test - running_sum_null)
|
|
106
|
+
|
|
107
|
+
max_delta = max(deltas)
|
|
108
|
+
min_delta = min(deltas)
|
|
109
|
+
|
|
110
|
+
if plot_bool:
|
|
111
|
+
|
|
112
|
+
data = pd.Series(deltas)
|
|
113
|
+
|
|
114
|
+
import matplotlib.pyplot as plt
|
|
115
|
+
import seaborn as sns
|
|
116
|
+
|
|
117
|
+
if not os.path.exists(out_plot_dir_str):
|
|
118
|
+
os.makedirs(out_plot_dir_str)
|
|
119
|
+
|
|
120
|
+
plt.clf()
|
|
121
|
+
fig, ax = plt.subplots(figsize=(4, 3))
|
|
122
|
+
|
|
123
|
+
ax = sns.lineplot(
|
|
124
|
+
data=data,
|
|
125
|
+
linewidth=2.5
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
ax.set(
|
|
129
|
+
title=f'{kinase}',
|
|
130
|
+
xlabel='Phosphosite rank',
|
|
131
|
+
ylabel='Running Sum Statistic'
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
sns.despine()
|
|
135
|
+
plt.tight_layout()
|
|
136
|
+
|
|
137
|
+
plt.savefig(os.path.join(out_plot_dir_str, f'{kinase}.svg'))
|
|
138
|
+
plt.close()
|
|
139
|
+
|
|
140
|
+
if abs(max_delta) > abs(min_delta):
|
|
141
|
+
|
|
142
|
+
return max_delta
|
|
143
|
+
|
|
144
|
+
else:
|
|
145
|
+
|
|
146
|
+
return min_delta
|
|
147
|
+
""" # take the deviation at the end of the test and null curves
|
|
148
|
+
def ks_statistic(
|
|
149
|
+
|
|
150
|
+
test_var_series:pd.Series,
|
|
151
|
+
null_var_df:pd.DataFrame,
|
|
152
|
+
plot_bool:bool=False,
|
|
153
|
+
out_plot_dir_str:str=''
|
|
154
|
+
|
|
155
|
+
):
|
|
156
|
+
|
|
157
|
+
kinase = test_var_series.name
|
|
158
|
+
|
|
159
|
+
running_sum_test = 0
|
|
160
|
+
running_sum_null = 0
|
|
161
|
+
|
|
162
|
+
running_sum_test_list = [0]
|
|
163
|
+
running_sum_null_list = [0]
|
|
164
|
+
|
|
165
|
+
for i in range(len(test_var_series)):
|
|
166
|
+
|
|
167
|
+
running_sum_test = running_sum_test + test_var_series[i]
|
|
168
|
+
running_sum_null = running_sum_null + null_var_df[kinase][i]
|
|
169
|
+
|
|
170
|
+
running_sum_test_list.append(running_sum_test)
|
|
171
|
+
running_sum_null_list.append(running_sum_null)
|
|
172
|
+
|
|
173
|
+
if plot_bool:
|
|
174
|
+
|
|
175
|
+
import matplotlib.pyplot as plt
|
|
176
|
+
import seaborn as sns
|
|
177
|
+
|
|
178
|
+
if not os.path.exists(out_plot_dir_str):
|
|
179
|
+
os.makedirs(out_plot_dir_str)
|
|
180
|
+
|
|
181
|
+
data = pd.DataFrame({
|
|
182
|
+
f'{kinase}': running_sum_test_list,
|
|
183
|
+
'Null': running_sum_null_list
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
plt.clf()
|
|
187
|
+
fig, ax = plt.subplots(figsize=(4, 3))
|
|
188
|
+
|
|
189
|
+
ax = sns.lineplot(
|
|
190
|
+
data=pd.DataFrame(data),
|
|
191
|
+
linewidth=2.5
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
ax.set(
|
|
195
|
+
title=f'{kinase}',
|
|
196
|
+
xlabel='Phosphosite rank',
|
|
197
|
+
ylabel='Statistic'
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
sns.despine()
|
|
201
|
+
plt.tight_layout()
|
|
202
|
+
|
|
203
|
+
plt.savefig(os.path.join(out_plot_dir_str, f'{kinase}.svg'))
|
|
204
|
+
plt.close()
|
|
205
|
+
|
|
206
|
+
delta = running_sum_test - running_sum_null
|
|
207
|
+
|
|
208
|
+
return delta
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
def compute_null_ks(
|
|
212
|
+
|
|
213
|
+
pssm_scoring_df:pd.DataFrame,
|
|
214
|
+
seqrnk_null_scores_df:pd.DataFrame,
|
|
215
|
+
seqrnk_series:pd.Series,
|
|
216
|
+
|
|
217
|
+
):
|
|
218
|
+
|
|
219
|
+
idx_list = list(range(len(seqrnk_series)))
|
|
220
|
+
|
|
221
|
+
# permute phosphosites
|
|
222
|
+
shuffle(idx_list)
|
|
223
|
+
shuffled_pssm_scoring_df = pssm_scoring_df.reindex(idx_list)
|
|
224
|
+
shuffled_pssm_scoring_df.index = list(range(len(seqrnk_series)))
|
|
225
|
+
|
|
226
|
+
# multiply the kinase pssm scores by the phosphosites seqrnk
|
|
227
|
+
shuffled_seqrnk_pssm_scores_df = shuffled_pssm_scoring_df.apply(
|
|
228
|
+
lambda x: x * seqrnk_series,
|
|
229
|
+
axis=0
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# compute ks between shuffled pssm scores and null scores
|
|
233
|
+
ks_series = shuffled_seqrnk_pssm_scores_df.apply(
|
|
234
|
+
ks_statistic,
|
|
235
|
+
args=[seqrnk_null_scores_df, False],
|
|
236
|
+
axis=0
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
ks_df = pd.DataFrame([ks_series])
|
|
240
|
+
|
|
241
|
+
return ks_df
|
|
242
|
+
|
|
243
|
+
def compute_ks_empirical_distrib(
|
|
244
|
+
|
|
245
|
+
pssm_scoring_df:pd.DataFrame,
|
|
246
|
+
null_scoring_df:pd.DataFrame,
|
|
247
|
+
seqrnk_series:pd.Series,
|
|
248
|
+
n:int=1000,
|
|
249
|
+
n_proc:int=1
|
|
250
|
+
|
|
251
|
+
):
|
|
252
|
+
# multiply the null pssm scores by the phosphosites seqrnk
|
|
253
|
+
seqrnk_null_scores_df = null_scoring_df.apply(
|
|
254
|
+
lambda x: x * seqrnk_series,
|
|
255
|
+
axis=0
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
arg1 = [pssm_scoring_df for i in range(n)]
|
|
259
|
+
arg2 = [seqrnk_null_scores_df for i in range(n)]
|
|
260
|
+
arg3 = [seqrnk_series for i in range(n)]
|
|
261
|
+
|
|
262
|
+
with Pool(processes=n_proc) as pool:
|
|
263
|
+
dfs_list = pool.starmap(compute_null_ks, zip(arg1, arg2, arg3))
|
|
264
|
+
|
|
265
|
+
out_df = pd.concat(dfs_list, axis=0)
|
|
266
|
+
out_df.index = list(range(len(out_df)))
|
|
267
|
+
|
|
268
|
+
return out_df
|
|
269
|
+
|
|
270
|
+
def compute_ks_pvalues(
|
|
271
|
+
|
|
272
|
+
ks_empirical_distrib_df: pd.DataFrame,
|
|
273
|
+
ks_series: pd.Series,
|
|
274
|
+
plot_bool:bool=False,
|
|
275
|
+
out_plot_dir_str:str=''
|
|
276
|
+
|
|
277
|
+
):
|
|
278
|
+
|
|
279
|
+
if plot_bool:
|
|
280
|
+
|
|
281
|
+
import matplotlib.pyplot as plt
|
|
282
|
+
import seaborn as sns
|
|
283
|
+
|
|
284
|
+
if not os.path.exists(out_plot_dir_str):
|
|
285
|
+
os.makedirs(out_plot_dir_str)
|
|
286
|
+
|
|
287
|
+
ks_pvalue_series = pd.Series()
|
|
288
|
+
ks_pvalue_series.name = 'p value'
|
|
289
|
+
|
|
290
|
+
for kinase_str in ks_empirical_distrib_df.columns:
|
|
291
|
+
|
|
292
|
+
empirical_list = ks_empirical_distrib_df[kinase_str]
|
|
293
|
+
|
|
294
|
+
value_float = ks_series[kinase_str]
|
|
295
|
+
|
|
296
|
+
num_int = sum([x>value_float for x in empirical_list])
|
|
297
|
+
den_int = len(empirical_list)
|
|
298
|
+
pvalue = num_int / den_int
|
|
299
|
+
|
|
300
|
+
if pvalue > 0.5:
|
|
301
|
+
num_int = den_int - num_int
|
|
302
|
+
pvalue = num_int / den_int
|
|
303
|
+
ks_pvalue_series[kinase_str] = pvalue
|
|
304
|
+
|
|
305
|
+
if plot_bool:
|
|
306
|
+
|
|
307
|
+
plt.clf()
|
|
308
|
+
fig, ax = plt.subplots(figsize=(5, 3))
|
|
309
|
+
|
|
310
|
+
ax = sns.displot(
|
|
311
|
+
data=empirical_list,
|
|
312
|
+
height=3,
|
|
313
|
+
aspect=5/3
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
ax.set(
|
|
317
|
+
title=f'Empirical ks distribution for {kinase_str} ({len(empirical_list)} perm.)',
|
|
318
|
+
xlabel='KS',
|
|
319
|
+
ylabel='Count'
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
plt.axvline(x=value_float)
|
|
323
|
+
|
|
324
|
+
sns.despine()
|
|
325
|
+
plt.tight_layout()
|
|
326
|
+
|
|
327
|
+
plt.savefig(os.path.join(out_plot_dir_str, f'{kinase_str}_ks_dist.svg'))
|
|
328
|
+
plt.close()
|
|
329
|
+
|
|
330
|
+
return(ks_pvalue_series)
|
|
331
|
+
|
|
332
|
+
def read_pssms(
|
|
333
|
+
|
|
334
|
+
pssms_h5_file:str
|
|
335
|
+
|
|
336
|
+
):
|
|
337
|
+
|
|
338
|
+
pssms_h5 = h5py.File(pssms_h5_file, 'r')
|
|
339
|
+
pssm_df_dict = {}
|
|
340
|
+
|
|
341
|
+
for kinase in pssms_h5.keys():
|
|
342
|
+
|
|
343
|
+
pssm_df_dict[kinase] = pd.DataFrame(pssms_h5[kinase])
|
|
344
|
+
pssm_df_dict[kinase].columns = AA_LIST
|
|
345
|
+
pssm_df_dict[kinase].index = POSITIONS_LIST
|
|
346
|
+
|
|
347
|
+
return pssm_df_dict
|
|
348
|
+
|
|
349
|
+
def read_seqrnk(
|
|
350
|
+
|
|
351
|
+
seqrnk_file:str
|
|
352
|
+
|
|
353
|
+
):
|
|
354
|
+
|
|
355
|
+
seqrnk = pd.read_csv(seqrnk_file, sep='\t', header=None)
|
|
356
|
+
seqrnk.columns = ['Sequence', 'Score']
|
|
357
|
+
|
|
358
|
+
return seqrnk
|
|
359
|
+
|
|
360
|
+
def kinase_activities(
|
|
361
|
+
|
|
362
|
+
seqrnk_file:str,
|
|
363
|
+
pssms_h5_file:str,
|
|
364
|
+
n_perm:int=10000,
|
|
365
|
+
n_proc:int=1,
|
|
366
|
+
plot_figures:bool=False,
|
|
367
|
+
out_plot_dir:str='',
|
|
368
|
+
|
|
369
|
+
):
|
|
370
|
+
|
|
371
|
+
pssm_df_dict = read_pssms(pssms_h5_file)
|
|
372
|
+
seqrnk = read_seqrnk(seqrnk_file)
|
|
373
|
+
|
|
374
|
+
# tmp: filter out Y phosphosites
|
|
375
|
+
seqrnk = seqrnk.loc[
|
|
376
|
+
[seqrnk['Sequence'][i][5] == 'S' or seqrnk['Sequence'][i][5] == 'T' for i in range(len(seqrnk))]
|
|
377
|
+
]
|
|
378
|
+
seqrnk.index = range(len(seqrnk))
|
|
379
|
+
|
|
380
|
+
# score phosphosite sequences with each PSSM
|
|
381
|
+
seq_series = seqrnk['Sequence']
|
|
382
|
+
arg1 = [seq_series[i] for i in range(len(seq_series))]
|
|
383
|
+
arg2 = [pssm_df_dict for i in range(len(seq_series))]
|
|
384
|
+
with Pool(processes=n_proc) as pool:
|
|
385
|
+
dfs_list = pool.starmap(pssm_scoring, zip(arg1, arg2))
|
|
386
|
+
pssm_scoring_df = pd.concat(dfs_list, axis=1).T
|
|
387
|
+
pssm_scoring_df.index = list(range(len(seq_series)))
|
|
388
|
+
|
|
389
|
+
# scale the PSSM scores between 0 and 1 for each phosphosite
|
|
390
|
+
pssm_scoring_scaled01_df = pssm_scoring_df.apply(
|
|
391
|
+
scale_01,
|
|
392
|
+
axis=1
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# obtain mean (null) pssm scores for each psssm
|
|
396
|
+
# TODO can be made just a Series (following code to be optimised)
|
|
397
|
+
pssm_null_scoring_df = pssm_scoring_scaled01_df.apply(
|
|
398
|
+
lambda x: pd.Series([x.mean()] * len(x), index=x.index),
|
|
399
|
+
axis=0
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# compute empirical distribution of ks statistics
|
|
403
|
+
ks_empirical_distrib_df = compute_ks_empirical_distrib(
|
|
404
|
+
pssm_scoring_df=pssm_scoring_scaled01_df,
|
|
405
|
+
null_scoring_df=pssm_null_scoring_df,
|
|
406
|
+
seqrnk_series=seqrnk['Score'],
|
|
407
|
+
n=n_perm,
|
|
408
|
+
n_proc=n_proc
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
# multiply the pssm scores by the phosphosites seqrnk
|
|
412
|
+
seqrnk_pssm_scoring_row_scaled_df = pssm_scoring_scaled01_df.apply(
|
|
413
|
+
lambda x: x * seqrnk['Score'],
|
|
414
|
+
axis=0
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
# multiply the null scores by the phosphosites seqrnk
|
|
418
|
+
seqrnk_pssm_null_scoring_df = pssm_null_scoring_df.apply(
|
|
419
|
+
lambda x: x * seqrnk['Score'],
|
|
420
|
+
axis=0
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# compute ks statistics
|
|
424
|
+
ks_series = seqrnk_pssm_scoring_row_scaled_df.apply(
|
|
425
|
+
ks_statistic,
|
|
426
|
+
args=[seqrnk_pssm_null_scoring_df, plot_figures, out_plot_dir],
|
|
427
|
+
axis=0
|
|
428
|
+
)
|
|
429
|
+
ks_series.name = 'KS'
|
|
430
|
+
|
|
431
|
+
# compute ks pvalues
|
|
432
|
+
ks_pvalue_series = compute_ks_pvalues(
|
|
433
|
+
ks_empirical_distrib_df=ks_empirical_distrib_df,
|
|
434
|
+
ks_series=ks_series,
|
|
435
|
+
plot_bool=plot_figures,
|
|
436
|
+
out_plot_dir_str=out_plot_dir
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# compute FPR
|
|
440
|
+
results_df = pd.concat([ks_series, ks_pvalue_series], axis=1)
|
|
441
|
+
results_df['FPR p value'] = results_df['p value'] * len(results_df)
|
|
442
|
+
results_df['FPR p value'].loc[results_df['FPR p value'] > 1] = 1
|
|
443
|
+
|
|
444
|
+
# compute activity score as -log10(p value), signed with the sign of ks,
|
|
445
|
+
# capped based on smallest computable p value
|
|
446
|
+
activity_score_series = results_df['p value'].copy()
|
|
447
|
+
min_non0_fpr_float = min(activity_score_series.loc[activity_score_series != 0])
|
|
448
|
+
max_abs_activity_score_float = -np.log10(min_non0_fpr_float)
|
|
449
|
+
activity_score_series.loc[activity_score_series == 0] = np.nextafter(np.float32(0), np.float32(1))
|
|
450
|
+
activity_score_series = -np.log10(activity_score_series)
|
|
451
|
+
activity_score_series.loc[activity_score_series > max_abs_activity_score_float] = max_abs_activity_score_float
|
|
452
|
+
results_df['Activity Score'] = activity_score_series * results_df['KS'].apply(lambda x: 1 if x > 0 else -1)
|
|
453
|
+
|
|
454
|
+
# export results
|
|
455
|
+
print(results_df.to_csv(sep='\t', header=True, index=True))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "phosx"
|
|
3
|
+
version = "0.1.3"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = ["Alessandro Lussana <alussana@ebi.ac.uk>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
include = ["phosx/data/*"]
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.10"
|
|
11
|
+
h5py = "^3.9.0"
|
|
12
|
+
pandas = "^2.0.3"
|
|
13
|
+
seaborn = "^0.12.2"
|
|
14
|
+
|
|
15
|
+
[tool.poetry.scripts]
|
|
16
|
+
phosx = 'phosx.cli:main'
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["poetry-core"]
|
|
20
|
+
build-backend = "poetry.core.masonry.api"
|