UNPKG

scran.js

Version:

Single cell RNA-seq analysis in Javascript

283 lines (257 loc) 13.3 kB
import * as gc from "./gc.js"; import * as wasm from "./wasm.js"; import * as utils from "./utils.js"; /** * Wrapper around the marker scoring results on the Wasm heap, typically produced by {@linkcode scoreMarkers}. * @hideconstructor */ export class ScoreMarkersResults { #id; #results; #has_median; #has_max; constructor(id, raw, has_median, has_max) { this.#id = id; this.#results = raw; this.#has_median = has_median; this.#has_max = has_max; } /** * @return {number} Number of groups in the results. */ numberOfGroups() { return this.#results.num_groups(); } /** * @param {number} group - Group of interest. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}. * @param {object} [options={}] - Optional parameters. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}. * * @return {?(Float64Array|Float64WasmArray)} Array of length equal to the number of genes, * containing the mean expression for the requested group in the requested block. */ mean(group, options = {}) { const { copy = true, ...others } = options; utils.checkOtherOptions(others); return utils.possibleCopy(this.#results.mean(group), copy); } /** * @param {number} group - Group of interest. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}. * @param {object} [options={}] - Optional parameters. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}. * * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes, * containing the proportion of cells with detectable expression for the requested group in the requested block. */ detected(group, options = {}) { const { copy = true, ...others } = options; utils.checkOtherOptions(others); return utils.possibleCopy(this.#results.detected(group), copy); } #check_forbidden(summary) { if ((summary == "maximum" && !(this.#has_max)) || (summary == "median" && !(this.#has_median))) { throw new Error("summary type '" + summary + "' not available"); } } /** * @param {number} group - Group of interest. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}. * @param {object} [options={}] - Optional parameters. * @param {string} [options.summary="mean"] - Summary statistic to be computed from the Cohen's d values of all pairwise comparisons involving `group`. * This can be the `"minimum"` across comparisons, `"mean"` or `"min-rank"`. * If the relevant options are set in {@linkcode scoreMarkers}, `"maximum"` and `"median"` are also supported. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}. * * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes, * containing the summarized Cohen's d for the comparisons between `group` and all other groups. */ cohensD(group, options = {}) { const { summary = "mean", copy = true, ...others } = options; utils.checkOtherOptions(others); this.#check_forbidden(summary); return utils.possibleCopy(wasm.call(_ => this.#results.cohens_d(group, summary)), copy); } /** * AUCs are only computed if `computeAuc = true` in {@linkcode scoreMarkers}. * If `false`, this method will throw an error. * * @param {number} group - Group of interest. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}. * @param {object} [options={}] - Optional parameters. * @param {string} [options.summary="mean"] - Summary statistic to be computed from the AUCs of all pairwise comparisons involving `group`. * This can be the `"minimum"` across comparisons, `"mean"` or `"min-rank"`. * If the relevant options are set in {@linkcode scoreMarkers}, `"maximum"` and `"median"` are also supported. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}. * * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes, * containing the summarized AUC for the comparisons between `group` and all other groups. */ auc(group, options = {}) { const { summary = "mean", copy = true, ...others } = options; utils.checkOtherOptions(others); this.#check_forbidden(summary); return utils.possibleCopy(wasm.call(_ => this.#results.auc(group, summary)), copy); } /** * @param {number} group - Group of interest. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}. * @param {object} [options={}] - Optional parameters. * @param {string} [options.summary="mean"] - Summary statistic to be computed from the log-fold changes of all pairwise comparisons involving `group`. * This can be the `"minimum"` across comparisons, `"mean"` or `"min-rank"`. * If the relevant options are set in {@linkcode scoreMarkers}, `"maximum"` and `"median"` are also supported. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}. * * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes, * containing the summarized delta-mean for the comparisons between `group` and all other groups. * This can be interpreted as the log-fold change if log-expression values are used in {@linkcode scoreMarkers}. */ deltaMean(group, options = {}) { const { summary = "mean", copy = true, ...others } = options; utils.checkOtherOptions(others); this.#check_forbidden(summary); return utils.possibleCopy(wasm.call(_ => this.#results.delta_mean(group, summary)), copy); } /** * @param {number} group - Group of interest. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}. * @param {object} [options={}] - Optional parameters. * @param {string} [options.summary="mean"] - Summary statistic to be computed from the delta-detected values of all pairwise comparisons involving `group`. * This can be the `"minimum"` across comparisons, `"mean"` or `"min-rank"`. * If the relevant options are set in {@linkcode scoreMarkers}, `"maximum"` and `"median"` are also supported. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}. * * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes, * containing the summarized delta-detected for the comparisons between `group` and all other groups. */ deltaDetected(group, options = {}) { const { summary = "mean", copy = true, ...others } = options; utils.checkOtherOptions(others); this.#check_forbidden(summary); return utils.possibleCopy(wasm.call(_ => this.#results.delta_detected(group, summary)), copy); } /** * @return Frees the memory allocated on the Wasm heap for this object. * This invalidates this object and all references to it. */ free() { if (this.#results !== null) { gc.release(this.#id); this.#results = null; } return; } } /** * Score genes as potential markers for each group of cells. * * @param {ScranMatrix} x - Log-normalized expression matrix. * @param {(Int32WasmArray|Array|TypedArray)} groups - Array containing the group assignment for each cell. * This should have length equal to the number of cells and contain all values from 0 to `n - 1` at least once, where `n` is the number of groups. * @param {object} [options={}] - Optional parameters. * @param {?(Int32WasmArray|Array|TypedArray)} [options.block=null] - Array containing the block assignment for each cell. * This should have length equal to the number of cells and contain all values from 0 to `n - 1` at least once, where `n` is the number of blocks. * This is used to segregate cells in order to perform comparisons within each block. * Alternatively, this may be `null`, in which case all cells are assumed to be in the same block. * @param {?number} [options.numberOfThreads=null] - Number of threads to use. * If `null`, defaults to {@linkcode maximumThreads}. * @param {number} [options.threshold=0] - Threshold on the magnitude of differences between groups, used when computing Cohen's d and AUC. * Large positive values favor markers with large differences over those with low variance. * For log-expression values in `x`, this can be interpreted as a minimum log-fold change. * @param {boolean} [options.computeAuc=true] - Whether to compute the AUCs as an effect size. * This can be set to `false` for greater speed and memory efficiency. * @param {boolean} [options.computeMedian=false] - Whether to compute the median effect sizes across all pairwise comparisons for each group. * This can be used as a more robust/less sensitive alternative to the mean. * @param {boolean} [options.computeMaximum=false] - Whether to compute the maximum effect size across all pairwise comparisons for each group. * This could be used to find uniquely downregulated genes. * * @return {ScoreMarkersResults} Object containing the marker scoring results. */ export function scoreMarkers(x, groups, options = {}) { const { block = null, threshold = 0, computeAuc = true, computeMedian = false, computeMaximum = false , numberOfThreads = null, ...others } = options; utils.checkOtherOptions(others); var output; var block_data; var group_data; let nthreads = utils.chooseNumberOfThreads(numberOfThreads); try { group_data = utils.wasmifyArray(groups, "Int32WasmArray"); if (group_data.length != x.numberOfColumns()) { throw new Error("length of 'groups' should be equal to number of columns in 'x'"); } var bptr = 0; var use_blocks = false; if (block !== null) { block_data = utils.wasmifyArray(block, "Int32WasmArray"); if (block_data.length != x.numberOfColumns()) { throw new Error("'block' must be of length equal to the number of columns in 'x'"); } use_blocks = true; bptr = block_data.offset; } output = gc.call( module => module.score_markers(x.matrix, group_data.offset, use_blocks, bptr, threshold, computeAuc, computeMedian, computeMaximum, nthreads), ScoreMarkersResults, computeMedian, computeMaximum ); } catch (e) { utils.free(output); throw e; } finally { utils.free(block_data); utils.free(group_data); } return output; } /** * Choose the top marker genes, typically from the statistics from {@linkcode scoreMarkers}. * * @param {Float64Array} stats - Array of length equal to the number of genes, containing marker statistics from, e.g., {@linkcode scoreMarkers#cohensD ScoreMarkers.cohensD}. * @param {number} number - Number of top markers to report. * @param {object} [options={}] Further options. * @param {boolean} [options.useLargest=true] - Whether to choose the genes with the largest values in `stats`. * If `false`, the smallest values are chosen instead. * @param {?number} [options.threshold=null] - Threshold to apply to the statistics in `stats`. * If provided, genes with smaller (if `useLargest = true`) or larger values (otherwise) will not be chosen, even if they are among the top `number` genes. * * @return {Array} Array of indices specifying the top markers according to their `stats`. */ export function chooseTopMarkers(stats, number, options = {}) { let { useLargest = true, threshold = null, ...others } = options; utils.checkOtherOptions(others); let output = []; if (number == 0) { return output; } else if (number >= stats.length) { for (var i = 0; i < stats.length; i++) { output.push(i); } return output; } let copy = new Float64Array(stats); copy.sort(); if (useLargest) { let actual_threshold = copy[copy.length - number]; if (threshold !== null) { actual_threshold = Math.max(actual_threshold, threshold); } stats.forEach((x, i) => { if (x >= actual_threshold) { output.push(i); } }); } else { let actual_threshold = copy[number - 1]; if (threshold !== null) { actual_threshold = Math.min(actual_threshold, threshold); } stats.forEach((x, i) => { if (x <= actual_threshold) { output.push(i); } }); } return output; }