scoreMarkers.js

  1. import * as gc from "./gc.js";
  2. import * as wasm from "./wasm.js";
  3. import * as utils from "./utils.js";
  4. /**
  5. * Wrapper around the marker scoring results on the Wasm heap, typically produced by {@linkcode scoreMarkers}.
  6. * @hideconstructor
  7. */
  8. export class ScoreMarkersResults {
  9. #id;
  10. #results;
  11. #has_median;
  12. #has_max;
  13. constructor(id, raw, has_median, has_max) {
  14. this.#id = id;
  15. this.#results = raw;
  16. this.#has_median = has_median;
  17. this.#has_max = has_max;
  18. }
  19. /**
  20. * @return {number} Number of groups in the results.
  21. */
  22. numberOfGroups() {
  23. return this.#results.num_groups();
  24. }
  25. /**
  26. * @param {number} group - Group of interest.
  27. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}.
  28. * @param {object} [options={}] - Optional parameters.
  29. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}.
  30. *
  31. * @return {?(Float64Array|Float64WasmArray)} Array of length equal to the number of genes,
  32. * containing the mean expression for the requested group in the requested block.
  33. */
  34. mean(group, options = {}) {
  35. const { copy = true, ...others } = options;
  36. utils.checkOtherOptions(others);
  37. return utils.possibleCopy(this.#results.mean(group), copy);
  38. }
  39. /**
  40. * @param {number} group - Group of interest.
  41. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}.
  42. * @param {object} [options={}] - Optional parameters.
  43. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}.
  44. *
  45. * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes,
  46. * containing the proportion of cells with detectable expression for the requested group in the requested block.
  47. */
  48. detected(group, options = {}) {
  49. const { copy = true, ...others } = options;
  50. utils.checkOtherOptions(others);
  51. return utils.possibleCopy(this.#results.detected(group), copy);
  52. }
  53. #check_forbidden(summary) {
  54. if ((summary == "maximum" && !(this.#has_max)) || (summary == "median" && !(this.#has_median))) {
  55. throw new Error("summary type '" + summary + "' not available");
  56. }
  57. }
  58. /**
  59. * @param {number} group - Group of interest.
  60. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}.
  61. * @param {object} [options={}] - Optional parameters.
  62. * @param {string} [options.summary="mean"] - Summary statistic to be computed from the Cohen's d values of all pairwise comparisons involving `group`.
  63. * This can be the `"minimum"` across comparisons, `"mean"` or `"min-rank"`.
  64. * If the relevant options are set in {@linkcode scoreMarkers}, `"maximum"` and `"median"` are also supported.
  65. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}.
  66. *
  67. * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes,
  68. * containing the summarized Cohen's d for the comparisons between `group` and all other groups.
  69. */
  70. cohensD(group, options = {}) {
  71. const { summary = "mean", copy = true, ...others } = options;
  72. utils.checkOtherOptions(others);
  73. this.#check_forbidden(summary);
  74. return utils.possibleCopy(wasm.call(_ => this.#results.cohens_d(group, summary)), copy);
  75. }
  76. /**
  77. * AUCs are only computed if `computeAuc = true` in {@linkcode scoreMarkers}.
  78. * If `false`, this method will throw an error.
  79. *
  80. * @param {number} group - Group of interest.
  81. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}.
  82. * @param {object} [options={}] - Optional parameters.
  83. * @param {string} [options.summary="mean"] - Summary statistic to be computed from the AUCs of all pairwise comparisons involving `group`.
  84. * This can be the `"minimum"` across comparisons, `"mean"` or `"min-rank"`.
  85. * If the relevant options are set in {@linkcode scoreMarkers}, `"maximum"` and `"median"` are also supported.
  86. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}.
  87. *
  88. * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes,
  89. * containing the summarized AUC for the comparisons between `group` and all other groups.
  90. */
  91. auc(group, options = {}) {
  92. const { summary = "mean", copy = true, ...others } = options;
  93. utils.checkOtherOptions(others);
  94. this.#check_forbidden(summary);
  95. return utils.possibleCopy(wasm.call(_ => this.#results.auc(group, summary)), copy);
  96. }
  97. /**
  98. * @param {number} group - Group of interest.
  99. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}.
  100. * @param {object} [options={}] - Optional parameters.
  101. * @param {string} [options.summary="mean"] - Summary statistic to be computed from the log-fold changes of all pairwise comparisons involving `group`.
  102. * This can be the `"minimum"` across comparisons, `"mean"` or `"min-rank"`.
  103. * If the relevant options are set in {@linkcode scoreMarkers}, `"maximum"` and `"median"` are also supported.
  104. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}.
  105. *
  106. * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes,
  107. * containing the summarized delta-mean for the comparisons between `group` and all other groups.
  108. * This can be interpreted as the log-fold change if log-expression values are used in {@linkcode scoreMarkers}.
  109. */
  110. deltaMean(group, options = {}) {
  111. const { summary = "mean", copy = true, ...others } = options;
  112. utils.checkOtherOptions(others);
  113. this.#check_forbidden(summary);
  114. return utils.possibleCopy(wasm.call(_ => this.#results.delta_mean(group, summary)), copy);
  115. }
  116. /**
  117. * @param {number} group - Group of interest.
  118. * Should be non-negative and less than {@linkcode ScoreMarkersResults#numberOfGroups numberOfGroups}.
  119. * @param {object} [options={}] - Optional parameters.
  120. * @param {string} [options.summary="mean"] - Summary statistic to be computed from the delta-detected values of all pairwise comparisons involving `group`.
  121. * This can be the `"minimum"` across comparisons, `"mean"` or `"min-rank"`.
  122. * If the relevant options are set in {@linkcode scoreMarkers}, `"maximum"` and `"median"` are also supported.
  123. * @param {boolean} [options.copy=true] - Whether to copy the results from the Wasm heap, see {@linkcode possibleCopy}.
  124. *
  125. * @return {Float64Array|Float64WasmArray} Array of length equal to the number of genes,
  126. * containing the summarized delta-detected for the comparisons between `group` and all other groups.
  127. */
  128. deltaDetected(group, options = {}) {
  129. const { summary = "mean", copy = true, ...others } = options;
  130. utils.checkOtherOptions(others);
  131. this.#check_forbidden(summary);
  132. return utils.possibleCopy(wasm.call(_ => this.#results.delta_detected(group, summary)), copy);
  133. }
  134. /**
  135. * @return Frees the memory allocated on the Wasm heap for this object.
  136. * This invalidates this object and all references to it.
  137. */
  138. free() {
  139. if (this.#results !== null) {
  140. gc.release(this.#id);
  141. this.#results = null;
  142. }
  143. return;
  144. }
  145. }
  146. /**
  147. * Score genes as potential markers for each group of cells.
  148. *
  149. * @param {ScranMatrix} x - Log-normalized expression matrix.
  150. * @param {(Int32WasmArray|Array|TypedArray)} groups - Array containing the group assignment for each cell.
  151. * 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.
  152. * @param {object} [options={}] - Optional parameters.
  153. * @param {?(Int32WasmArray|Array|TypedArray)} [options.block=null] - Array containing the block assignment for each cell.
  154. * 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.
  155. * This is used to segregate cells in order to perform comparisons within each block.
  156. * Alternatively, this may be `null`, in which case all cells are assumed to be in the same block.
  157. * @param {?number} [options.numberOfThreads=null] - Number of threads to use.
  158. * If `null`, defaults to {@linkcode maximumThreads}.
  159. * @param {number} [options.threshold=0] - Threshold on the magnitude of differences between groups, used when computing Cohen's d and AUC.
  160. * Large positive values favor markers with large differences over those with low variance.
  161. * For log-expression values in `x`, this can be interpreted as a minimum log-fold change.
  162. * @param {boolean} [options.computeAuc=true] - Whether to compute the AUCs as an effect size.
  163. * This can be set to `false` for greater speed and memory efficiency.
  164. * @param {boolean} [options.computeMedian=false] - Whether to compute the median effect sizes across all pairwise comparisons for each group.
  165. * This can be used as a more robust/less sensitive alternative to the mean.
  166. * @param {boolean} [options.computeMaximum=false] - Whether to compute the maximum effect size across all pairwise comparisons for each group.
  167. * This could be used to find uniquely downregulated genes.
  168. *
  169. * @return {ScoreMarkersResults} Object containing the marker scoring results.
  170. */
  171. export function scoreMarkers(x, groups, options = {}) {
  172. const { block = null, threshold = 0, computeAuc = true, computeMedian = false, computeMaximum = false , numberOfThreads = null, ...others } = options;
  173. utils.checkOtherOptions(others);
  174. var output;
  175. var block_data;
  176. var group_data;
  177. let nthreads = utils.chooseNumberOfThreads(numberOfThreads);
  178. try {
  179. group_data = utils.wasmifyArray(groups, "Int32WasmArray");
  180. if (group_data.length != x.numberOfColumns()) {
  181. throw new Error("length of 'groups' should be equal to number of columns in 'x'");
  182. }
  183. var bptr = 0;
  184. var use_blocks = false;
  185. if (block !== null) {
  186. block_data = utils.wasmifyArray(block, "Int32WasmArray");
  187. if (block_data.length != x.numberOfColumns()) {
  188. throw new Error("'block' must be of length equal to the number of columns in 'x'");
  189. }
  190. use_blocks = true;
  191. bptr = block_data.offset;
  192. }
  193. output = gc.call(
  194. module => module.score_markers(x.matrix, group_data.offset, use_blocks, bptr, threshold, computeAuc, computeMedian, computeMaximum, nthreads),
  195. ScoreMarkersResults,
  196. computeMedian,
  197. computeMaximum
  198. );
  199. } catch (e) {
  200. utils.free(output);
  201. throw e;
  202. } finally {
  203. utils.free(block_data);
  204. utils.free(group_data);
  205. }
  206. return output;
  207. }