sceps 0.1.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.
sceps/__init__.py ADDED
@@ -0,0 +1,22 @@
1
+ """scEPS (single-cell Expression exPlainability Statistics).
2
+
3
+ Integrates GWAS and single-cell disease cell atlas data to identify
4
+ disease-associated cell neighborhoods.
5
+
6
+ The Python API lives in :mod:`sceps.sceps_core`::
7
+
8
+ from sceps.sceps_core import *
9
+
10
+ Nothing heavyweight is imported here on purpose: the console scripts in
11
+ ``sceps.cli`` import this package first, and pulling scanpy in at this point
12
+ would slow down ``--help`` for every command.
13
+ """
14
+
15
+ from importlib.metadata import PackageNotFoundError, version
16
+
17
+ try:
18
+ __version__ = version("sceps")
19
+ except PackageNotFoundError: # running from a source tree without an install
20
+ __version__ = "0.0.0.dev0"
21
+
22
+ __all__ = ["__version__"]
sceps/cli/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """Command-line entry points for scEPS.
2
+
3
+ Each module here exposes a ``main()`` that is wired to a console script in
4
+ ``pyproject.toml``:
5
+
6
+ ============================== ==============================
7
+ command module
8
+ ============================== ==============================
9
+ ``sceps`` :mod:`sceps.cli.run`
10
+ ``sceps-cluster-neighborhood`` :mod:`sceps.cli.cluster_neighborhood`
11
+ ``sceps-aggregate`` :mod:`sceps.cli.aggregate`
12
+ ``sceps-corr`` :mod:`sceps.cli.corr`
13
+ ============================== ==============================
14
+ """
sceps/cli/aggregate.py ADDED
@@ -0,0 +1,402 @@
1
+ import argparse, sys, glob, os
2
+ import pandas as pd
3
+ import numpy as np
4
+ import scipy as sp
5
+ import scipy.stats
6
+ from tqdm import tqdm
7
+ import logging, random
8
+ import scanpy as sc
9
+
10
+ from ..utils import EPS
11
+ from ..stats_test import get_weighted_mean
12
+
13
+ from ..scdata import *
14
+ from sklearn.cluster import MiniBatchKMeans
15
+ import statsmodels.stats.multitest as smm
16
+
17
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
18
+
19
+ estimand_col = ['OMEGA_GWAS', 'OMEGA_CONTROL', 'OMEGA_REST', 'OMEGA_OVERALL', 'OMEGA_DIFF']
20
+ info_col = ['NEIGHBORHOOD_SIZE', 'NUM_DONOR', \
21
+ 'MEAN_MEAN_EXPR_GWAS', 'MEAN_MEAN_EXPR_CONTROL', 'MEAN_MEAN_EXPR_REST', 'MEAN_MEAN_EXPR_ALL', \
22
+ 'MEAN_VAR_EXPR_GWAS', 'MEAN_VAR_EXPR_CONTROL', 'MEAN_VAR_EXPR_REST', 'MEAN_VAR_EXPR_ALL', \
23
+ 'VAR_OUTCOME', 'NUM_GENE_GWAS', 'NUM_GENE_CONTROL', 'NUM_GENE_REST']
24
+
25
+ def main():
26
+
27
+ # get command line
28
+ args = get_command_line()
29
+
30
+ # set seed
31
+ random.seed(args.seed)
32
+ np.random.seed(args.seed)
33
+
34
+ # append prefix to the estimand columns if using weighted estimands
35
+ if args.use_sigma:
36
+ for i in range(len(estimand_col)):
37
+ estimand_col[i] = estimand_col[i].replace('OMEGA', 'SIGMA')
38
+
39
+ # load the single-cell data
40
+ logging.info("Loading single-cell data from {}".format(args.adata))
41
+ adata = sc.read_h5ad(args.adata)
42
+
43
+ # If args.cell_id_col not in adata.obs.columns, we use adata.obs.index as replacement
44
+ obs = adata.obs.copy()
45
+ if args.cell_id_col not in obs.columns:
46
+ if (args.cell_id_col is None) or (args.cell_id_col == ''):
47
+ args.cell_id_col = 'sceps.cell_index'
48
+ obs[args.cell_id_col] = obs.index.copy()
49
+ adata.obs = obs
50
+
51
+ # define neighborhood clusters for bootstrap
52
+ if args.neighborhood_clusters != '':
53
+ logging.info("Using pre-computed clusters of cell neighborhoods from {}".format(args.neighborhood_clusters))
54
+ df_neighborhood_cluster = pd.read_table(args.neighborhood_clusters)
55
+ adata = adata[adata.obs[args.cell_id_col].isin(df_neighborhood_cluster[args.cell_id_col])].copy()
56
+ cell2cluster = dict(zip(df_neighborhood_cluster[args.cell_id_col], df_neighborhood_cluster[args.block_bootstrap]))
57
+ adata.obs[args.block_bootstrap] = adata.obs[args.cell_id_col].map(cell2cluster)
58
+
59
+ # aggregate results across batches of sceps runs
60
+ logging.info("Aggregating scEPS results across batches of runs")
61
+ df_sceps = aggregate_sceps_batches(args)
62
+ adata = adata[adata.obs[args.cell_id_col].isin(df_sceps['CELL'])].copy()
63
+ obs = adata.obs.copy()
64
+
65
+ # perform testing at cell type level
66
+ if args.cell_type_col != '':
67
+ all_cell_type_col = args.cell_type_col.split(',')
68
+ for cell_type_col in all_cell_type_col:
69
+
70
+ logging.info("Testing at cell type level {}".format(cell_type_col))
71
+
72
+ # create output file name
73
+ if args.use_sigma == True:
74
+ out_fnm = '{}.{}.sceps.sigma.celltype.txt'.format(args.out, cell_type_col)
75
+ else:
76
+ out_fnm = '{}.{}.sceps.omega.celltype.txt'.format(args.out, cell_type_col)
77
+
78
+ # test all cell types in the cell_type_col column
79
+ df_test_ct = test_all_cell_types(args, cell_type_col, obs, df_sceps)
80
+
81
+ # save results
82
+ df_test_ct.to_csv(out_fnm, sep='\t', index=False, float_format='%.5g')
83
+ else:
84
+ # no cell type column specified, aggregate across all cells
85
+ if args.use_sigma == True:
86
+ out_fnm = '{}.sceps.sigma.celltype.txt'.format(args.out)
87
+ else:
88
+ out_fnm = '{}.sceps.omega.celltype.txt'.format(args.out)
89
+
90
+ # create a temporary cell type column
91
+ obs['scEPS_tmp_cell_type_col'] = 'All'
92
+ df_test_ct = test_all_cell_types(args, 'scEPS_tmp_cell_type_col', obs, df_sceps)
93
+
94
+ # save results
95
+ df_test_ct.to_csv(out_fnm, sep='\t', index=False, float_format='%.5g')
96
+
97
+
98
+ def aggregate_sceps_batches(args):
99
+ """
100
+ Aggregate scEPS results across batches of cells
101
+ """
102
+
103
+ # load the list of neighborhoods to use
104
+ df_usenb = None
105
+ if args.use_neighborhoods != '':
106
+ df_usenb = pd.read_table(args.use_neighborhoods, header=None)
107
+
108
+ # load sceps output files
109
+ if args.prefix.endswith('.txt.gz') == False:
110
+ if args.use_sigma == False:
111
+ all_out_ = glob.glob('{}*sceps.omega.txt.gz'.format(args.prefix))
112
+ else:
113
+ all_out_ = glob.glob('{}*sceps.sigma.txt.gz'.format(args.prefix))
114
+ else:
115
+ all_out_ = glob.glob(args.prefix)
116
+
117
+ # get output file name
118
+ if args.use_sigma == False:
119
+ out_fnm = '{}.sceps.omega.txt.gz'.format(args.out)
120
+ else:
121
+ out_fnm = '{}.sceps.sigma.txt.gz'.format(args.out)
122
+
123
+ # exclude files that contains specific string
124
+ all_out = []
125
+ if args.exclude_files_with_str is not None:
126
+ for fnm in all_out_:
127
+ if fnm.find(args.exclude_files_with_str) == -1:
128
+ all_out.append(fnm)
129
+ else:
130
+ all_out = all_out_
131
+
132
+ # aggregate the results
133
+ logging.info('Found {} scEPS score files'.format(len(all_out)))
134
+ df_sceps = []
135
+ for fnm in tqdm(all_out):
136
+ df_sceps_batch = pd.read_table(fnm)
137
+ if df_usenb is not None:
138
+ df_sceps_batch = df_sceps_batch[df_sceps_batch['CELL'].isin(df_usenb[0])].reset_index(drop=True)
139
+ df_sceps.append(df_sceps_batch)
140
+ df_sceps = pd.concat(df_sceps, ignore_index=True)
141
+
142
+ # apply testing at cell neighborhood level
143
+ logging.info("Testing at cell neighborhood level")
144
+ test_cell(df_sceps)
145
+
146
+ # save the aggregated sceps score file
147
+ df_sceps.to_csv(out_fnm, sep='\t', index=False, float_format='%.5g')
148
+
149
+ return df_sceps
150
+
151
+ def test_cell(df_sceps):
152
+
153
+ ncell = df_sceps.shape[0]
154
+
155
+ # iterate through estimand
156
+ for estimand in estimand_col:
157
+
158
+ pval = df_sceps['P_Z_{}'.format(estimand)]
159
+ est_val = df_sceps[estimand].values
160
+
161
+ signif_fdr5 = np.zeros(ncell, dtype=bool)
162
+ signif_fdr10 = np.zeros(ncell, dtype=bool)
163
+ signif_fdr20 = np.zeros(ncell, dtype=bool)
164
+
165
+ signif_fdr5 = (est_val > 0) & (smm.multipletests(pval, alpha=0.05, method='fdr_bh')[0])
166
+ signif_fdr10 = (est_val > 0) & (smm.multipletests(pval, alpha=0.10, method='fdr_bh')[0])
167
+ signif_fdr20 = (est_val > 0) & (smm.multipletests(pval, alpha=0.20, method='fdr_bh')[0])
168
+
169
+ if estimand.find('DIFF') > 0:
170
+ estimand_gwas = estimand.replace('DIFF', 'GWAS')
171
+ signif_fdr5 = signif_fdr5 & (df_sceps[estimand_gwas].values > 0)
172
+ signif_fdr10 = signif_fdr10 & (df_sceps[estimand_gwas].values > 0)
173
+ signif_fdr20 = signif_fdr20 & (df_sceps[estimand_gwas].values > 0)
174
+
175
+ df_sceps['SIGNIF_FDR5_{}'.format(estimand)] = signif_fdr5
176
+ df_sceps['SIGNIF_FDR10_{}'.format(estimand)] = signif_fdr10
177
+ df_sceps['SIGNIF_FDR20_{}'.format(estimand)] = signif_fdr20
178
+
179
+ return df_sceps
180
+
181
+
182
+ def test_all_cell_types(args, cell_type_col, obs, df_sceps):
183
+ """
184
+ Test all the cell types
185
+ """
186
+
187
+ # get all cell type
188
+ all_ct = pd.unique(obs[cell_type_col])
189
+
190
+ # perform testing for each cell type
191
+ df_test_out = []
192
+ for ct in tqdm(all_ct):
193
+
194
+ # get test stats
195
+ df_test_out_ct = test_cell_type(args, cell_type_col, obs, ct, df_sceps)
196
+ if df_test_out_ct is None:
197
+ continue
198
+
199
+ # add additional information
200
+ df_test_out_ct['CELL_TYPE'] = ct
201
+ if args.add_column is not None:
202
+ if (args.add_column[0] in df_test_out_ct.columns) == False:
203
+ df_test_out_ct[args.add_column[0]] = args.add_column[1]
204
+
205
+ # append to list
206
+ df_test_out.append(df_test_out_ct)
207
+
208
+ # aggregate results
209
+ if len(df_test_out) > 0:
210
+ df_test_out = pd.concat(df_test_out)
211
+ df_test_out = df_test_out.dropna(axis=1, how='all')
212
+ ct_col = df_test_out.pop('CELL_TYPE')
213
+ df_test_out.insert(0, 'CELL_TYPE', ct_col)
214
+
215
+ return df_test_out
216
+
217
+ # return none if nothing to return
218
+ return None
219
+
220
+
221
+ def test_cell_type(args, cell_type_col, obs, ct, df_sceps):
222
+ """
223
+ Test a particular cell type
224
+ """
225
+
226
+ # extract cells from the cell type
227
+ ct_cells = obs[obs[cell_type_col]==ct][args.cell_id_col]
228
+ df_sceps_ct = df_sceps[df_sceps['CELL'].isin(ct_cells)].copy()
229
+
230
+ # add block information if using block bootstrap
231
+ if args.block_bootstrap != '':
232
+ cell2block = dict(zip(obs[args.cell_id_col], obs[args.block_bootstrap]))
233
+ df_sceps_ct['BLOCK'] = df_sceps_ct['CELL'].map(cell2block)
234
+
235
+ # check if dataframe empty
236
+ num_cells = df_sceps_ct.shape[0]
237
+ if num_cells == 0:
238
+ return None
239
+
240
+ # get summary info
241
+ df_all_test_out = []
242
+ for col in info_col:
243
+ if col in df_sceps_ct.columns:
244
+ df_all_test_out.append(pd.DataFrame({'MEAN_'+col: [np.mean(df_sceps_ct[col])]}))
245
+
246
+ # get bootstrap test statistics
247
+ use_inv_var_wgt = False
248
+ if args.use_inverse_variance_weights == True:
249
+ use_inv_var_wgt = True
250
+ for col in estimand_col:
251
+ test_out = get_bootstrap_test_stats(df_sceps_ct, col,
252
+ nbs=args.num_bootstrap, use_inv_var_wgt=use_inv_var_wgt)
253
+ df_all_test_out.append(pd.DataFrame(test_out))
254
+ df_all_test_out = pd.concat(df_all_test_out, axis=1)
255
+
256
+ # add number of neighborhoods
257
+ df_all_test_out['NUM_CELL'] = num_cells
258
+
259
+ return df_all_test_out
260
+
261
+
262
+ def get_bootstrap_test_stats(df, col, nbs=1000, use_inv_var_wgt=False, count_signif=True):
263
+
264
+ # prepare bootstrap
265
+ ncell = df.shape[0]
266
+ stats_vec = df[col].values
267
+ se_vec = df['SE_'+col].values
268
+ var_vec = np.square(se_vec)
269
+ inv_var_vec = 1.0 / (var_vec + EPS)
270
+ weight_vec = np.ones(ncell) / ncell
271
+ if use_inv_var_wgt == True:
272
+ weight_vec = inv_var_vec / np.sum(inv_var_vec)
273
+ mean_stats = np.sum(stats_vec * weight_vec)
274
+ all_mean_stats_bs = []
275
+
276
+ # get number of significant cell neighborhood
277
+ if count_signif == True:
278
+ num_signif_fdr5 = np.sum(df['SIGNIF_FDR5_{}'.format(col)])
279
+ num_signif_fdr10 = np.sum(df['SIGNIF_FDR10_{}'.format(col)])
280
+ num_signif_fdr20 = np.sum(df['SIGNIF_FDR20_{}'.format(col)])
281
+
282
+ # standard bootstrap
283
+ if 'BLOCK' not in df.columns:
284
+ all_idx = np.array(range(ncell))
285
+ weight_vec_bs = weight_vec.copy()
286
+ for _ in nbs:
287
+ # bootstrap the cells
288
+ use_idx = np.random.choice(all_idx, size=ncell, replace=True)
289
+ if use_inv_var_wgt == True:
290
+ weight_vec_bs = inv_var_vec[use_idx] / np.sum(inv_var_vec[use_idx])
291
+ all_mean_stats_bs.append(np.sum(stats_vec[use_idx] * weight_vec_bs[use_idx]))
292
+ # block bootstrap
293
+ else:
294
+ block_val = df['BLOCK'].values
295
+ all_block = np.array(pd.unique(df['BLOCK']))
296
+ nblock = all_block.shape[0]
297
+ weight_vec_bs = weight_vec.copy()
298
+ # bootstrap the blocks
299
+ for _ in range(nbs):
300
+ use_block = np.random.choice(all_block, size=nblock, replace=True)
301
+ use_idx = np.concatenate([np.where(block_val == blk)[0] for blk in use_block])
302
+ if use_inv_var_wgt == True:
303
+ weight_vec_bs = inv_var_vec[use_idx] / np.sum(inv_var_vec[use_idx])
304
+ all_mean_stats_bs.append(np.sum(stats_vec[use_idx] * weight_vec_bs[use_idx]))
305
+
306
+ # get test statistics
307
+ se_mean_stats = np.std(all_mean_stats_bs)
308
+ z_mean_stats = mean_stats / (se_mean_stats + EPS)
309
+ p_mean_stats = (1-scipy.stats.norm.cdf(np.fabs(z_mean_stats)))*2.0
310
+
311
+ # create out dict
312
+ if count_signif == True:
313
+ out = {'NUM_SIGNIF_FDR5_{}'.format(col): [num_signif_fdr5],
314
+ 'NUM_SIGNIF_FDR10_{}'.format(col): [num_signif_fdr10],
315
+ 'NUM_SIGNIF_FDR20_{}'.format(col): [num_signif_fdr20],
316
+ 'MEAN_{}'.format(col): [mean_stats],
317
+ 'SE_MEAN_{}'.format(col): [se_mean_stats],
318
+ 'Z_MEAN_{}'.format(col): [z_mean_stats],
319
+ 'P_Z_MEAN_{}'.format(col): [p_mean_stats]}
320
+ else:
321
+ out = {'MEAN_{}'.format(col): [mean_stats],
322
+ 'SE_MEAN_{}'.format(col): [se_mean_stats],
323
+ 'Z_MEAN_{}'.format(col): [z_mean_stats],
324
+ 'P_Z_MEAN_{}'.format(col): [p_mean_stats]}
325
+
326
+ return out
327
+
328
+ def get_command_line():
329
+
330
+ # Create the parser
331
+ parser = argparse.ArgumentParser(description="Summarize the results")
332
+
333
+ # Input regulated command line arguments
334
+ parser.add_argument('--prefix', type=str, required=False,
335
+ help="""Used to specify a regular expression for the file names of the output """ \
336
+ """for individual cell neighborhoods from step 1.""")
337
+
338
+ parser.add_argument('--adata', type=str, required=False,
339
+ help="""Used to specify the same single-cell data used for obtaining scEPS statistics """ \
340
+ """at individual cell neighborhood level, with cell ID column specified by the --cell-id-col flag.""")
341
+
342
+ parser.add_argument('--cell-id-col', type=str, required=False, default='',
343
+ help="""Used to specify the name of the column that represents cell IDs in the adata.obs data """ \
344
+ """frame of the single-cell data. If left empty, scEPS will use what's in adata.obs.index as cell IDs.""")
345
+
346
+ parser.add_argument('--exclude-files-with-str', type=str, default=None, required=False,
347
+ help="""Used to filter out files with specific strings in their file names. This is """ \
348
+ """primarily used for debugging purposes.""")
349
+
350
+ parser.add_argument('--neighborhood-clusters', type=str, required=False, default='',
351
+ help="""Used to specify the text file from step 2, representing a pre-computed mapping of cell """ \
352
+ """neighborhoods to approximately independent blocks of cell neighborhoods""")
353
+
354
+ # Testing related command line argument
355
+ parser.add_argument('--cell-type-col', type=str, required=False, default='',
356
+ help="""Used to specify a list of column names (e.g., at different resolutions) in adata.obs """ \
357
+ """representing cell types. The list of column names need to be separated by commas. The tool will """ \
358
+ """calculate the average scEPS statistics for each cell type under each cell type column, in separate files. """ \
359
+ """If this flag is not specified, scEPS will aggregate results across all cell neighborhoods.""")
360
+
361
+ parser.add_argument('--use-neighborhoods', type=str, required=False, default='',
362
+ help="""Used to specify a text file containing a list of cell IDs representing cell neighborhoods. """ \
363
+ """By default, this is an empty string, and the tool aggregates results from all cell neighborhoods. """ \
364
+ """If this is non-empty, scEPS will calculate aggregated statistics using only the cell neighborhoods """ \
365
+ """listed in the text file.""")
366
+
367
+ parser.add_argument('--use-inverse-variance-weights', default=False, required=False, action='store_true',
368
+ help="""If specified (not recommended), the tool will use inverse variance weighted average as """ \
369
+ """the aggregated statistics for each cell type.""")
370
+
371
+ parser.add_argument('--use-sigma', default=False, required=False, action='store_true',
372
+ help="""If specified, the tool will aggregate the scEPS SIGMA statistics instead of the OMEGA """ \
373
+ """statistics. If this flag is specified, the --prefix flag should specify file names with """ \
374
+ """.sceps.sigma.txt.gz as suffix.""")
375
+
376
+ parser.add_argument('--block-bootstrap', type=str, required=False, default='sceps.neighborhood_cluster',
377
+ help="""Used to specify the column in that represents approximately independent cell """ \
378
+ """neighborhood blocks (sceps.neighborhood_cluster by default). If left empty, """ \
379
+ """the tool will fall back to regular bootstrap across individual (instead of blocks of) """ \
380
+ """cell neighborhoods.""")
381
+
382
+ parser.add_argument('--num-bootstrap', type=int, required=False, default=1000,
383
+ help="""Used to specify the number of bootstrap samples (1,000 by default).""")
384
+
385
+ parser.add_argument('--seed', type=int, required=False, default=0,
386
+ help="""Used to specify the seed for the random number generator (0 by default).""")
387
+
388
+ # Output related command line argument
389
+ parser.add_argument('--add-column', type=str, required=False, nargs=2, default=None,
390
+ help="""Used to specify the column name and a value for the additional column to add.""")
391
+
392
+ parser.add_argument('--out', type=str, required=False,
393
+ help="""Used to specify the prefix of the output file name.""")
394
+
395
+ # Execute the parse_args() method
396
+ args = parser.parse_args()
397
+
398
+ return args
399
+
400
+
401
+ if __name__ == '__main__':
402
+ main()
@@ -0,0 +1,122 @@
1
+ import argparse, sys, glob, os
2
+ import pandas as pd
3
+ import numpy as np
4
+ import scipy as sp
5
+ import scipy.stats
6
+ from tqdm import tqdm
7
+ import logging, random
8
+ import scanpy as sc
9
+ import anndata as ad
10
+ from sklearn.cluster import MiniBatchKMeans
11
+
12
+ from ..utils import EPS
13
+ from ..neighborhood import *
14
+
15
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
16
+
17
+ def main():
18
+
19
+ # get command line
20
+ args = get_command_line()
21
+
22
+ # set seed
23
+ random.seed(args.seed)
24
+ np.random.seed(args.seed)
25
+
26
+ # define clusters for bootstrap
27
+ logging.info("Loading single-cell data from {}".format(args.adata))
28
+ adata = sc.read_h5ad(args.adata)
29
+
30
+ # If args.cell_id_col not in adata.obs.columns, we use adata.obs.index as replacement
31
+ obs = adata.obs.copy()
32
+ if args.cell_id_col not in obs.columns:
33
+ if args.cell_id_col == '':
34
+ args.cell_id_col = 'sceps.cell_index'
35
+ obs[args.cell_id_col] = obs.index.copy()
36
+ adata.obs = obs
37
+
38
+ # define clusters for bootstrap
39
+ logging.info("Identifying independent groups of cell neighborhoods")
40
+ adata.obs['sceps.neighborhood_cluster'] = get_nbhood_clusters(args, adata)
41
+
42
+ # Create and save a data frame mapping cells to their assigned cluster
43
+ df_out = adata.obs[[args.cell_id_col, 'sceps.neighborhood_cluster']]
44
+ df_out.to_csv(args.out+'.txt.gz', sep='\t', index=False)
45
+
46
+
47
+ def get_nbhood_clusters(args, adata):
48
+
49
+ # check if neighborhood is already calculated
50
+ has_knn = False
51
+ av = ad.__version__
52
+ if type(av) == str:
53
+ av = version.parse(av)
54
+ if av < version.parse("0.7.2"):
55
+ if "neighbors" in adata.uns:
56
+ if "connectivities" in adata.uns["neighbors"]:
57
+ has_knn = True
58
+ else:
59
+ if "connectivities" in adata.obsp:
60
+ has_knn = True
61
+
62
+ # calculate knn only when necessary
63
+ if has_knn == False:
64
+ sc.pp.neighbors(adata, use_rep=args.neighbors_use_rep)
65
+
66
+ # calculate nam matrix
67
+ adj_mat = get_connectivity(adata)
68
+ trans_mat = get_transition_matrix(adj_mat)
69
+ _, nam_matrix = choose_random_walk_nsteps(args, trans_mat, adata.obs)
70
+ nam_matrix = nam_matrix.values
71
+ nam_matrix = (nam_matrix - nam_matrix.mean(axis=0)) / (nam_matrix.std(axis=0)+EPS)
72
+
73
+ # run kmeans
74
+ kmeans = MiniBatchKMeans(n_clusters=args.num_kmeans_cluster,
75
+ batch_size=int(0.05*adata.shape[0]), random_state=args.seed, n_init='auto')
76
+
77
+ return kmeans.fit(nam_matrix).labels_
78
+
79
+
80
+ def get_command_line():
81
+
82
+ # Create the parser
83
+ parser = argparse.ArgumentParser(description="This tool aggregates scEPS statistics across groups of cell neighborhoods.")
84
+
85
+ # Add the arguments
86
+ parser.add_argument('--adata', type=str, required=False,
87
+ help="""Used to specify the input single-cell RNA-seq data in h5ad format.""" \
88
+ """This should be same single-cell data as analyzed by scEPS. However, the user """ \
89
+ """may remove adata.X to reduce memory usage, as the clustering tool only requires k-NN """ \
90
+ """graph for the cells.""")
91
+
92
+ parser.add_argument('--cell-id-col', type=str, required=False, default='',
93
+ help="""Used to specify the name of the column that represents cell IDs in the adata.obs """ \
94
+ """data frame of the single-cell data. If left empty, scEPS will use what's in """ \
95
+ """adata.obs.index as cell IDs.""")
96
+
97
+ parser.add_argument('--donor-id-col', type=str, required=False, default='',
98
+ help="""Used to specify the name of the column that represents donor IDs in the adata.obs """ \
99
+ """data frame of the single-cell data.""")
100
+
101
+ parser.add_argument('--neighbors-use-rep', type=str, required=False, default='X_pca_harmony',
102
+ help="""Used to specify the cell embedding (e.g., PCA, scVI embeddings, etc.) used to """ \
103
+ """construct the k-NN graph.""")
104
+
105
+ parser.add_argument('--num-kmeans-cluster', type=int, required=False, default=50,
106
+ help="""Used to specify the desired number of clusters (i.e., approximately independent """ \
107
+ """blocks of cell neighborhoods).""")
108
+
109
+ parser.add_argument('--seed', type=int, required=False, default=0,
110
+ help="""Used to specify the seed for the random number generator. (This is set to 0, by default)""")
111
+
112
+ parser.add_argument('--out', type=str, required=False,
113
+ help="""Used to specify the output file name.""")
114
+
115
+ # Execute the parse_args() method
116
+ args = parser.parse_args()
117
+
118
+ return args
119
+
120
+
121
+ if __name__ == '__main__':
122
+ main()