diff --git a/backend/app/rest_api/rest.py b/backend/app/rest_api/rest.py index eb5ea411..b297a481 100644 --- a/backend/app/rest_api/rest.py +++ b/backend/app/rest_api/rest.py @@ -210,10 +210,230 @@ class CellsAPI(Resource): return make_payload(payload) +class ExpressionAPI(Resource): + @swagger.doc({ + 'summary': 'Json with gene list and expression data by cell, limited to first 40 cells', + 'tags': ['expression'], + 'parameters': [ + { + 'name': 'include_unexpressed_genes', + 'description': "Include genes that have 0 expression across all cells in set", + 'in': 'path', + 'type': 'bool', + } + ], + 'responses': { + '200': { + 'description': 'Json for heatmap', + 'examples': { + 'application/json': { + "data": { + "cells": [ + { + "cellname": "1/2-SBSRNA4", + "e": [0, 0, 214, 0, 0] + }, + ], + "genes": [ + "1001000173.G8", + "1001000173.D4", + "1001000173.B4", + "1001000173.A2", + "1001000173.E2" + ], + "nonzero_gene_count": 2857 + }, + "status": { + "error": False, + "errormessage": "" + } + } + } + } + } + }) + def get(self): + from app import data + expression_data = data.expression() + return make_payload(expression_data) + + @swagger.doc({ + 'summary': 'Json with gene list and expression data by cell', + 'tags': ['expression'], + 'parameters': [ + { + 'name': 'body', + 'in': 'body', + "schema": { + "example": { + "celllist": ["1001000173.G8", "1001000173.D4"], + "genelist": ["1/2-SBSRNA4", "A1BG", "A1BG-AS1", "A1CF", "A2LD1", "A2M", "A2ML1", "A2MP1", + "A4GALT"], + "include_unexpressed_genes": True, + } + + } + }, + ], + 'responses': { + '200': { + 'description': 'Json for expressiondata', + 'examples': { + 'application/json': { + "data": { + "cells": [ + { + "cellname": "1001000173.D4", + "e": [0, 0] + }, + { + "cellname": "1001000173.G8", + "e": [0, 0] + } + ], + "genes": [ + "ABCD4", + "ZWINT" + ], + "nonzero_gene_count": 2857 + }, + "status": { + "error": False, + "errormessage": "" + } + + } + } + }, + '400': { + 'description': 'Required parameter missing/incorrect', + } + } + }) + def post(self): + from app import data + args = request.get_json() + cell_list = args.get('celllist', []) + gene_list = args.get('genelist', []) + if not (cell_list) and not (gene_list): + return make_payload([], "must include celllist and/or genelist parameter", 400) + + expression_data = data.expression(cell_list, gene_list) + + if cell_list and len(expression_data['cells']) < len(cell_list): + return make_payload([], "Some cell ids not available", 400) + if gene_list and len(expression_data['genes']) < len(gene_list): + return make_payload([], "Some genes not available", 400) + + return make_payload(expression_data) + +class DifferentialExpressionAPI(Resource): + @swagger.doc({ + 'summary': 'Get the top expressed genes for two cell sets. Calculated using t-test', + 'tags': ['expression'], + 'parameters': [ + { + 'name': 'body', + 'in': 'body', + 'schema': { + "example": { + "celllist1": ["1001000176.C12", "1001000176.C7", "1001000177.F11"], + "celllist2": ["1001000012.D2", "1001000017.F10", "1001000033.C3", "1001000229.D4"], + "num_genes": 5, + "pval": 0.000001, + }, + } + } + ], + "responses": { + '200': { + 'description': 'top expressed genes for cellset1, cellset2', + 'examples': { + 'application/json': { + "data": { + "celllist1": { + "ave_diff": [ + 432.0132935431362, + 12470.5623982637, + 957.0246880086814 + ], + "mean_expression_cellset1": [ + 438.6185567010309, + 13315.536082474227, + 1076.5773195876288 + ], + "mean_expression_cellset2": [ + 6.605263157894737, + 844.9736842105264, + 119.55263157894737 + ], + "pval": [ + 3.8906598089944563e-35, + 1.9086226376018916e-25, + 7.847480544069826e-21 + ], + "topgenes": [ + "TMSB10", + "FTL", + "TMSB4X" + ] + }, + "celllist2": { + "ave_diff": [ + -6860.599158979924, + -519.1314432989691, + -10278.328269126423 + ], + "mean_expression_cellset1": [ + 2.8350515463917527, + 0.6185567010309279, + 23.09278350515464 + ], + "mean_expression_cellset2": [ + 6863.434210526316, + 519.75, + 10301.421052631578 + ], + "pval": [ + 4.662891833748732e-44, + 3.6278087029927103e-37, + 8.396825170618402e-35 + ], + "topgenes": [ + "SPARCL1", + "C1orf61", + "CLU" + ] + } + }, + "status": { + "error": False, + "errormessage": "" + } + } + } + } + } + }) + def post(self): + from app import data + args = request.get_json() + cell_list_1 = args.get('celllist1', []) + cell_list_2 = args.get('celllist2', []) + num_genes = args.get("num_genes", 7) + pval = args.get('pval', 0.5) + if not (cell_list_1 and cell_list_2): + return make_payload([], + "must include celllist1 and celllist2 parameters", + 400) + data = data.diffexp(cell_list_1, cell_list_2, pval, num_genes) + return make_payload(data) def get_api_resources(): bp = Blueprint('api', __name__, url_prefix='/api/v2.0') api = Api(bp, add_api_spec_resource=False) api.add_resource(InitializeAPI, "/initialize") api.add_resource(CellsAPI, "/cells") + api.add_resource(ExpressionAPI, "/expression") + api.add_resource(DifferentialExpressionAPI, "/diffexp") return api \ No newline at end of file diff --git a/backend/app/scanpy_engine/scanpy_engine.py b/backend/app/scanpy_engine/scanpy_engine.py index 06250cd0..7fae78f7 100644 --- a/backend/app/scanpy_engine/scanpy_engine.py +++ b/backend/app/scanpy_engine/scanpy_engine.py @@ -1,5 +1,6 @@ import scanpy.api as sc import numpy as np +from scipy import stats import os from ..util.schema_parse import parse_schema @@ -112,11 +113,80 @@ class ScanpyEngine(CXGDriver): return np.hstack((df.obs["cell_name"].values.reshape(len(df.obs.index), 1), normalized_graph)).tolist() - def diffexp(self, cells_iterator_1, cells_iterator_2): - pass + def diffexp(self, cell_list_1, cell_list_2, pval, num_genes): + cells_idx_1 = np.in1d(self.data.obs["cell_name"], cell_list_1) + cells_idx_2 = np.in1d(self.data.obs["cell_name"], cell_list_2) + expression_1 = self.data.X[cells_idx_1,:] + expression_2 = self.data.X[cells_idx_2,:] + diff_exp = stats.ttest_ind(expression_1, expression_2) + set1 = np.logical_and(diff_exp.pvalue < pval, diff_exp.statistic > 0) + set2 = np.logical_and(diff_exp.pvalue < pval, diff_exp.statistic < 0) + stat1 = diff_exp.statistic[set1] + stat2 = diff_exp.statistic[set2] + sort_set1 = np.argsort(stat1)[::-1] + sort_set2 = np.argsort(stat2) + pval1 = diff_exp.pvalue[set1][sort_set1] + pval2 = diff_exp.pvalue[set2][sort_set2] + mean_ex1_set1 = np.mean(expression_1[:, set1], axis=0)[sort_set1] + mean_ex2_set1 = np.mean(expression_2[:, set1], axis=0)[sort_set1] + mean_ex1_set2 = np.mean(expression_1[:, set2], axis=0)[sort_set2] + mean_ex2_set2 = np.mean(expression_2[:, set2], axis=0)[sort_set2] + mean_diff1 = mean_ex1_set1 - mean_ex2_set1 + mean_diff2 = mean_ex1_set2 - mean_ex2_set2 + genes_cellset_1 = self.data.var_names[set1][sort_set1] + genes_cellset_2 = self.data.var_names[set2][sort_set2] + return { + "celllist1": { + "topgenes": genes_cellset_1.tolist()[:num_genes], + "mean_expression_cellset1": mean_ex1_set1.tolist()[:num_genes], + "mean_expression_cellset2": mean_ex2_set1.tolist()[:num_genes], + "pval": pval1.tolist()[:num_genes], + "ave_diff": mean_diff1.tolist()[:num_genes] + }, + "celllist2": { + "topgenes": genes_cellset_2.tolist()[:num_genes], + "mean_expression_cellset1": mean_ex1_set2.tolist()[:num_genes], + "mean_expression_cellset2": mean_ex2_set2.tolist()[:num_genes], + "pval": pval2.tolist()[:num_genes], + "ave_diff": mean_diff2.tolist()[:num_genes] + }, + } + + def expression(self, cells=None, genes=None): + """ + :param df: + :return: + """ + if cells: + cells_idx = np.in1d(self.data.obs["cell_name"], cells) + else: + cells_idx = np.ones((self.cell_count,), dtype=bool) + if genes: + genes_idx = np.in1d(self.data.var_names, genes) + else: + genes_idx = np.ones((self.gene_count,), dtype=bool) + index = np.ix_(cells_idx, genes_idx) + expression = self.data.X[index] + + if not genes: + genes = self.data.var.index.tolist() + if not cells: + cells = self.data.obs["cell_name"].tolist() + + cell_data = [] + for idx, cell in enumerate(cells): + cell_data.append({ + "cellname": cell, + "e": list(expression[idx]), + }) + + return { + "genes": genes, + "cells": cell_data, + "nonzero_gene_count": int(np.sum(expression.any(axis=0))) + } + - def expression(self, ): - pass