OSCR

Recombinant dimeric PICK1 peptide inhibitors for long-term relief of chronic pain by AAV therapeutics.

Code ↔ Paper

4 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 4 matches
  1. [1] § STAR★Methods › Method details › Single nucleus RNA sequencing (snRNAseq) › snRNAseq data analysis ↔ R/conclass.R, lines 114–178 · score 0.76 · alignment strength, sample alignment, gene expression, preprocessing, appended, Conos
  2. [2] § STAR★Methods › Method details › Single nucleus RNA sequencing (snRNAseq) › snRNAseq data analysis ↔ src/scrublet/scrublet.py, lines 130–247 · score 0.71 · doublet scores, gene expression, UMIs, fewer, preprocessing, Scrublet
  3. [3] § STAR★Methods › Method details › Tandem mass tag (TMT) mass spectrometry (MS) › Phosphorylated peptides enrichment › Statistical methods (only MS data) ↔ R/helpers.R, lines 160–233 · score 0.54 · multiple hypothesis, FDR, enriched, enrichment, cutoff
  4. [4] § STAR★Methods › Method details › Tandem mass tag (TMT) mass spectrometry (MS) › Phosphorylated peptides enrichment › Statistical methods (only MS data) ↔ R/conclass.R, lines 342–411 · score 0.52 · fold change, hypothesis, log2, regulated, absolute, Graph

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

R · 1,109 lines · 61 KB · GPL-3.0 · 2 matches

  1. #' @import ggrepel
  2. #' @import gridExtra
  3. #' @import leidenAlg
  4. #' @import R6
  5. #' @import Rtsne
  6. NULL
  7. ## exporting to inherit parameters below, leiden.community
  8. #' @export
  9. leidenAlg::leiden.community
  10. #' @title Conos R6 class
  11. #' @description The class encompasses sample collections, providing methods for calculating and visualizing joint graph and communities.
  12. #' @import methods
  13. #' @param x a named list of pagoda2 or Seurat objects (one per sample)
  14. #' @param n.cores numeric Number of cores to use (default=parallel::detectCores(logical=FALSE))
  15. #' @param verbose boolean Whether to provide verbose output (default=TRUE)
  16. #' @param clustering string Name of the clustering to use
  17. #' @param groups a factor on cells to use for coloring
  18. #' @param colors a color factor (named with cell names) use for cell coloring
  19. #' @param gene show expression of a gene
  20. #' @export Conos
  21. Conos <- R6::R6Class("Conos", lock_objects=FALSE,
  22. public = list(
  23. #' @field samples list of samples (Pagoda2 or Seurat objects)
  24. samples = list(),
  25. #' @field pairs pairwise alignment results
  26. pairs = list(),
  27. #' @field graph alignment graph
  28. graph = NULL,
  29. #' @field clusters list of clustering results named by clustering type
  30. clusters = list(),
  31. #' @field expression.adj adjusted expression values
  32. expression.adj = list(),
  33. #' @field embeddings list of joint embeddings
  34. embeddings = list(),
  35. #' @field embedding joint embedding
  36. embedding = NULL,
  37. #' @field n.cores number of cores
  38. n.cores = 1,
  39. #' @field misc list with unstructured additional info
  40. misc = list(),
  41. #' @field override.conos.plot.theme boolean Whether to override the conos plot theme
  42. override.conos.plot.theme = FALSE,
  43. #' @description initialize Conos class
  44. #'
  45. #' @param override.conos.plot.theme boolean Whether to reset plot settings to the ggplot2 default (default=FALSE)
  46. #' @param ... additional parameters upon initializing Conos
  47. #' @return a new 'Conos' object
  48. #' @examples
  49. #' con <- Conos$new(small_panel.preprocessed, n.cores=1)
  50. #'
  51. initialize=function(x, ..., n.cores=parallel::detectCores(logical=FALSE), verbose=TRUE, override.conos.plot.theme=FALSE) {
  52. self$n.cores <- n.cores
  53. self$override.conos.plot.theme <- override.conos.plot.theme
  54. if (missing(x)){
  55. return()
  56. }
  57. if ('Conos' %in% class(x)) { # copy constructor
  58. for(n in ls(x)) {
  59. if (!is.function(get(n, x))) assign(n, get(n, x), self)
  60. }
  61. } else {
  62. if (!is.list(x)) {
  63. stop("x is not a list of pagoda2 or Seurat objects")
  64. }
  65. if (inherits(x = x[[1]], what = c('Pagoda2', 'seurat', 'Seurat'))) {
  66. self$addSamples(x)
  67. } else {
  68. stop("Only Pagoda2 or Seurat result lists are currently supported")
  69. }
  70. }
  71. },
  72. #' @description Initialize or add a set of samples to the conos panel. Note: this will simply add samples, but will not update graph, clustering, etc.
  73. #'
  74. #' @param replace boolean Whether the existing samples should be purged before adding new ones (default=FALSE)
  75. #' @param verbose boolean Whether to provide verbose output (default=FALSE)
  76. #' @return invisible view of the full sample list
  77. addSamples=function(x, replace=FALSE, verbose=FALSE) {
  78. # check names
  79. if(is.null(names(x))) {
  80. stop("The sample list must be named")
  81. }
  82. if(replace || length(self$samples)==0) {
  83. if(length(x)<2) {
  84. stop("The provided list contains less than 2 samples; 2 required, >3 recommended")
  85. }
  86. }
  87. if(any(duplicated(names(self$samples)))) {
  88. stop("duplicate names found in the supplied samples")
  89. }
  90. if(any(names(x) %in% names(self$samples))) {
  91. stop("Some of the names in the provided sample list are identical to already existing samples")
  92. }
  93. # TODO: package-independent wrapper
  94. self$samples <- c(self$samples, x)
  95. },
  96. #' @description Build the joint graph that encompasses all the samples, establishing weighted inter-sample cell-to-cell links
  97. #'
  98. #' @param k integer integer Size of the inter-sample neighborhood (default=15)
  99. #' @param k.self integer Size of the with-sample neighborhoods (default=10).
  100. #' @param k.self.weight numeric Weight multiplier on the intra-sample edges relative to inter-sample edges (default=0.1)
  101. #' @param alignment.strength numeric Alignment strength (default=NULL will result in alignment.strength=0)
  102. #' @param space character Reduced expression space used to establish putative alignments between pairs of samples (default='PCA'). Currently supported spaces are:
  103. #' --- "CPCA" Common principal component analysis
  104. #' --- "JNMF" Joint NMF
  105. #' --- "genes" Gene expression space (log2 transformed)
  106. #' --- "PCA" Principal component analysis
  107. #' --- "CCA" Canonical correlation analysis
  108. #' --- "PMA" (Penalized Multivariate Analysis <https://cran.r-project.org/web/packages/PMA/index.html>)
  109. #' @param matching.method character Matching method (default='mNN'). Currently supported methods are "NN" (nearest neighbors) or "mNN" (mututal nearest neighbors).
  110. #' @param metric character Distance metric to measure similarity (default='angular'). Currenlty supported metrics are "angular" and "L2".
  111. #' @param k1 numeric Neighborhood radius for identifying mutually-matching neighbors (default=k). Note that k1 must be greater than or equal to k, i.e. k1>=k. Increasing k1 beyond k will lead to more aggressive alignment of distinct subpopulations (i.e. increased alignment strengths).
  112. #' @param data.type character Type of data type in the input pagoda2 objects within r.n (default='counts').
  113. #' @param l2.sigma numeric L2 distances get transformed as exp(-d/sigma) using this value (default=1e5)
  114. #' @param var.scale boolean Whether to use common variance scaling (default=TRUE). If TRUE, use geometric means for variance, as we're trying to focus on the common variance components. See scaledMatricesP2() code.
  115. #' @param ncomps integer Number of components (default=40)
  116. #' @param n.odgenes integer Number of overdispersed genes to be used in each pairwise alignment (default=2000)
  117. #' @param matching.mask an optional matrix explicitly specifying which pairs of samples should be compared (a symmetrical matrix of logical values with row and column names corresponding to sample names). (default=NULL). By default, comparisons between all paris are allowed. The argument can be used to exclude comparisons across certain pairs of samples (e.g. techincal replicates, which are expected to show very high similarity).
  118. #' @param exclude.samples optional list of sample names that should be excluded from the alignment and the resulting graph (default=NULL)
  119. #' @param common.centering boolean When calculating reduced expression space for a given sample pair, whether the expression of genes should be centered using the mean from both samples (TRUE) or using the mean within each sample (FALSE) (default=TRUE)
  120. #' @param base.groups an optional factor on cells specifying previously-obtained cell grouping to be used for adjusting the sample alignment (default: NULL). Specifically, cell clusters specfiieid by the base.groups can be used to i) calculate global expression axes which are appended to the overall set of eigenvectors, ii) adding decoy cells.
  121. #' @param append.global.axes boolean Whether to project samples on global expression axes, as defined by pre-defined (typically crude) set of cell subpopulations as specified by the base.gruops parameter (default=TRUE, but works only if base.groups is specified)
  122. #' @param append.decoys boolean Whether to use pre-defined cell groups (specified by base.groups) to append decoy cells to the samples which are otherwise lacking any of the pre-specified cell groups (default=TRUE, but works only if base.groups is specified). The decoy cells can reduce the number of erroneous matches in highly heterogeneous sample collections, where some of the samples lack entire cell subpopulations which are found in other samples. The approach only works if the base.groups (typically a crude clustering of top-level cell types) can be established with a reasonable confidence.
  123. #' @param decoy.threshold integer Minimal number of cells of a given cell type that should exist in a given sample (according to base.groups) to avoid addition of decoy cells to that sample for the purposes of alignment (default=1)
  124. #' @param n.decoys integer Number of decoy cells that should be added to a sample that had less than decoy.threshold cells of a given cell type (default=k*2)
  125. #' @param score.component.variance boolean Whether to score the amount of total variance explained by different components (default=FALSE as it takes extra time to calculate)
  126. #' @param snn boolean Whether to transform the joint graph by computing a shared nearest neighborhood graph (analogous to Seurat 3), further weighting the edges between two matched cells based on the similarity (measured by Jaccard coefficient) of all of their predicted neighbors (across all of the samples) (default: FALSE)
  127. #' @param snn.quantile numeric Specifies how the shared neighborhood graph transformation will determine final edge weights. If snn.quantile=NULL, the edge weight will be simply equal to the Jaccard coefficient of the neighborhoods. If snn.quantile is a vector of two numeric values (p1, p2), they will be treated as quantile probabilities, and quantile values (q1,q2) on the set of all Jaccard coefficients (for all edges) will be determiend. The edge weights will then be reset, so that edges with Jaccard coefficients below or equal to q1 will be set to 0, and those with coefficients >=q2 will be set to 1. The rest of the weights will be mapped uniformly from [q1,q2]->[0,1] range. If a single numeric value is supplied, it will be treated as a symmetric quantile probability (i.e. snn.quantile=0.8 is equivalent to specifying snn.quantile=c(1-0.8,0.8)). (default: 0.9)
  128. #' @param min.snn.jaccard numeric Minimum Jaccard coefficient required for a shared neighborhood graph edge (default: 0). The edges with Jaccard coefficients below this threshold will be removed (i.e. weight set to 0)
  129. #' @param min.snn.weight numeric Shared nearest neighbor procedure will adjust the weights of the edges, and even eliminate some of the edges (by setting their weight to zero). The min.snn.weight parameter allows to set a minimal adjusted edge weight, so that the edge weight is never reduced beyond this level (and hence never deleted) (default: 0 - no adjustments)
  130. #' @param snn.k.self integer Size of the within-sample neighorhood to be used in shared nearest neighbor calculations (default=k.self)
  131. #' @param balance.edge.weights boolean Whether to balance edge weights to control for a cell- or sample- specific factor (default=FALSE)
  132. #' @param balancing.factor.per.sample A covariate factor per sample that should be controlled for by adjusting edge weights in the joint graph (default=NULL)
  133. #' @param balancing.factor.per.cell A per-cell factor (discrete factor, named with cell names) specifying a design difference should be controlled for by adjusting edge weights in the joint graph (default=NULL)
  134. #' @param same.factor.downweight numeric Optional weighting factor for edges connecting cells with the same cell factor level per cell balancing (default=1.0)
  135. #' @param k.same.factor integer An neighborhood size that should be used when aligning samples of the same balancing.factor.per.sample level. Setting a value smaller than k will lead to reduction of alingment strenth within the sample batches (default=k)
  136. #' @return joint graph to be used for downstream analysis
  137. #' @examples
  138. #' con <- Conos$new(small_panel.preprocessed, n.cores=1)
  139. #' con$buildGraph(k=10, k.self=5, space='PCA', ncomps=10, n.odgenes=20, matching.method='mNN',
  140. #' metric='angular', score.component.variance=TRUE, verbose=TRUE)
  141. #'
  142. #'
  143. buildGraph=function(k=15, k.self=10, k.self.weight=0.1, alignment.strength=NULL, space='PCA', matching.method='mNN', metric='angular', k1=k, data.type='counts', l2.sigma=1e5, var.scale=TRUE, ncomps=40,
  144. n.odgenes=2000, matching.mask=NULL, exclude.samples=NULL, common.centering=TRUE, verbose=TRUE,
  145. base.groups=NULL, append.global.axes=TRUE, append.decoys=TRUE, decoy.threshold=1, n.decoys=k*2, score.component.variance=FALSE,
  146. snn=FALSE, snn.quantile=0.9, min.snn.jaccard=0, min.snn.weight=0, snn.k.self=k.self,
  147. balance.edge.weights=FALSE, balancing.factor.per.cell=NULL, same.factor.downweight=1.0, k.same.factor=k, balancing.factor.per.sample=NULL) {
  148. supported.spaces <- c("CPCA","JNMF","genes","PCA","PMA","CCA")
  149. if (!space %in% supported.spaces) {
  150. stop(paste0("only the following spaces are currently supported: [",paste(supported.spaces,collapse=' '),"]"))
  151. }
  152. supported.matching.methods <- c("mNN", "NN")
  153. if (!matching.method %in% supported.matching.methods) {
  154. stop(paste0("only the following matching methods are currently supported: ['",paste(supported.matching.methods,collapse="' '"),"']"))
  155. }
  156. supported.metrics <- c("L2","angular")
  157. if (!metric %in% supported.metrics) {
  158. stop(paste0("only the following distance metrics are currently supported: ['",paste(supported.metrics,collapse="' '"),"']"))
  159. }
  160. if (!is.null(snn.quantile) && !is.na(snn.quantile)) {
  161. if(length(snn.quantile)==1) {
  162. snn.quantile <- c(1-snn.quantile,snn.quantile)
  163. }
  164. snn.quantile <- sort(snn.quantile,decreasing=FALSE)
  165. if(snn.quantile[1]<0 | snn.quantile[2]>1) {
  166. stop("snn.quantile must be one or two numbers in the [0,1] range")
  167. }
  168. }
  169. if (!is.null(alignment.strength)) {
  170. alignment.strength %<>% max(0) %>% min(1)
  171. k1 <- sapply(self$samples, function(sample) ncol(getCountMatrix(sample))) %>% max() %>%
  172. `*`(alignment.strength ^ 2) %>% round() %>% max(k)
  173. } else {
  174. alignment.strength <- 0 # otherwise, estimation of cor.base uses NULL value
  175. }
  176. if (k1<k) { stop("k1 must be >= k") }
  177. # calculate or update pairwise alignments
  178. sn.pairs <- private$updatePairs(space=space, ncomps=ncomps, n.odgenes=n.odgenes, verbose=verbose, var.scale=var.scale, matching.mask=matching.mask, exclude.samples=exclude.samples, score.component.variance=score.component.variance)
  179. if (ncol(sn.pairs)<1) { stop("insufficient number of comparable pairs") }
  180. if (!is.null(base.groups)) {
  181. samf <- lapply(self$samples,getCellNames)
  182. base.groups <- as.factor(base.groups[names(base.groups) %in% unlist(samf)]) # clean up the group factor
  183. if (length(base.groups) < 2) stop("provided base.groups doesn't cover enough cells")
  184. # make a sample factor
  185. samf <- setNames(rep(names(samf),unlist(lapply(samf,length))),unlist(samf))
  186. if (append.global.axes) {
  187. cms.clust <- self$getClusterCountMatrices(groups=base.groups, common.genes=FALSE)
  188. global.proj <- projectSamplesOnGlobalAxes(self$samples, cms.clust, data.type, verbose, self$n.cores)
  189. }
  190. }
  191. if (snn){
  192. local.neighbors <- getLocalNeighbors(self$samples[! names(self$samples) %in% exclude.samples], snn.k.self, k.self.weight, metric, l2.sigma=l2.sigma, verbose, self$n.cores)
  193. } else {
  194. local.neighbors <- NULL
  195. }
  196. # determine inter-sample mapping
  197. if (verbose) message('inter-sample links using ',matching.method,' ')
  198. cached.pairs <- self$pairs[[space]]
  199. cor.base <- 1 + min(1, alignment.strength * 10) ## see convertDistanceToSimilarity()
  200. mnnres <- papply(1:ncol(sn.pairs), function(j) {
  201. # we'll look up the pair by name (possibly reversed), not to assume for the ordering of $pairs[[space]] to be the same
  202. i <- match(paste(sn.pairs[,j],collapse='.vs.'),names(cached.pairs))
  203. if(is.na(i)) { i <- match(paste(rev(sn.pairs[,j]),collapse='.vs.'),names(cached.pairs)) }
  204. if(is.na(i)) { stop(paste("unable to find alignment for pair",paste(sn.pairs[,j],collapse='.vs.'))) }
  205. k.cur <- k
  206. if (!is.null(balancing.factor.per.sample) && (balancing.factor.per.sample[sn.pairs[1,j]] == balancing.factor.per.sample[sn.pairs[2,j]])) {
  207. k.cur <- min(k.same.factor, k1) # It always should be less then k1, though never supposed to be set higher
  208. }
  209. if(space=='JNMF') {
  210. mnn <- getNeighborMatrix(cached.pairs[[i]]$rot1, cached.pairs[[i]]$rot2,
  211. k=k.cur, k1=k1, matching=matching.method, metric=metric, l2.sigma=l2.sigma, cor.base=cor.base)
  212. } else if (space %in% c("CPCA","GSVD","PCA")) {
  213. #common.genes <- Reduce(intersect,lapply(r.ns, getGenes))
  214. if(!is.null(cached.pairs[[i]]$CPC)) {
  215. # CPCA or PCA
  216. #od.genes <- intersect(rownames(cached.pairs[[i]]$CPC),common.genes)
  217. od.genes <- rownames(cached.pairs[[i]]$CPC)
  218. rot <- cached.pairs[[i]]$CPC[od.genes,]
  219. } else if(!is.null(cached.pairs[[i]]$o$Q)) {
  220. # GSVD
  221. rot <- cached.pairs[[i]]$o$Q
  222. od.genes <- rownames(rot) <- colnames(cached.pairs[[i]]$o$A)
  223. } else {
  224. stop("unknown reduction provided")
  225. }
  226. # TODO: a more careful analysis of parameters used to calculate the cached version
  227. if(ncomps > ncol(rot)) {
  228. warning(paste0("specified ncomps (",ncomps,") is greater than the cached version (",ncol(rot),")"))
  229. } else {
  230. rot <- rot[,1:ncomps,drop=FALSE]
  231. }
  232. mnn <- getPcaBasedNeighborMatrix(self$samples[sn.pairs[,j]], od.genes=od.genes, rot=rot, data.type=data.type,
  233. k=k.cur, k1=k1, matching.method=matching.method, metric=metric, l2.sigma=l2.sigma, cor.base=cor.base,
  234. var.scale=var.scale, common.centering=common.centering,
  235. base.groups=base.groups, append.decoys=append.decoys, samples=self$samples, samf=samf, decoy.threshold=decoy.threshold,
  236. n.decoys=n.decoys, append.global.axes=append.global.axes, global.proj=global.proj)
  237. } else if (space=='genes') { ## Overdispersed Gene space
  238. mnn <- getNeighborMatrix(as.matrix(cached.pairs[[i]]$genespace1), as.matrix(cached.pairs[[i]]$genespace2),
  239. k=k.cur, k1=k1, matching=matching.method, metric=metric, l2.sigma=l2.sigma, cor.base=cor.base)
  240. } else if(space=='PMA' || space=='CCA') {
  241. mnn <- getNeighborMatrix(cached.pairs[[i]]$u, cached.pairs[[i]]$v,
  242. k=k.cur, k1=k1, matching=matching.method, metric=metric, l2.sigma=l2.sigma, cor.base=cor.base)
  243. } else {
  244. stop("Unknown space: ", space)
  245. }
  246. if(snn) { # optionally, perform shared neighbor weighting, a la SeuratV3, scran
  247. m1 <- cbind(mnn,t(local.neighbors[[sn.pairs[1,j] ]]))
  248. m2 <- rbind(local.neighbors[[sn.pairs[2,j] ]], mnn)
  249. mnn1 <- mnn
  250. mnn1@x <- rep(1,length(mnn1@x))
  251. m1@x <- rep(1,length(m1@x))
  252. m2@x <- rep(1,length(m2@x))
  253. x <- ((m1 %*% m2) * mnn1) / pmax(outer(rowSums(m1),colSums(m2),FUN=pmin),1)
  254. # scale by Jaccard coefficient
  255. if(min.snn.jaccard>0) {
  256. x@x[x@x<min.snn.jaccard] <- 0
  257. }
  258. if(!is.null(snn.quantile) && !is.na(snn.quantile)) {
  259. xq <- quantile(x@x,p=c(snn.quantile[1],snn.quantile[2]))
  260. x@x <- pmax(0,pmin(1,(x@x-xq[1])/pmax(1,diff(xq))))
  261. }
  262. x <- drop0(x)
  263. if (min.snn.weight>0) {
  264. if (packageVersion("Matrix") >= '1.4.2'){
  265. mnn <- as(drop0(mnn*min.snn.weight + mnn*x),'TsparseMatrix')
  266. } else {
  267. mnn <- as(drop0(mnn*min.snn.weight + mnn*x),'dgTMatrix')
  268. }
  269. } else {
  270. if (packageVersion("Matrix") >= '1.4.2'){
  271. mnn <- as(drop0(mnn*x),'TsparseMatrix')
  272. } else {
  273. mnn <- as(drop0(mnn*x),'dgTMatrix')
  274. }
  275. }
  276. }
  277. if(verbose) cat(".")
  278. return(data.frame('mA.lab'=rownames(mnn)[mnn@i+1],'mB.lab'=colnames(mnn)[mnn@j+1],'w'=mnn@x, stringsAsFactors=FALSE))
  279. }, n.cores=self$n.cores,mc.preschedule=TRUE)
  280. if (verbose) message(" done")
  281. ## Merge the results into a edge table
  282. el <- do.call(rbind,mnnres)
  283. if (nrow(el)==0) {
  284. el = data.frame('mA.lab'=0,'mB.lab'=0,'w'=0, 'type'=1, stringsAsFactors=FALSE)
  285. } else {
  286. el$type <- 1 # encode connection type 1- intersample, 0- intrasample
  287. }
  288. # append local edges
  289. if(k.self>0) {
  290. if(is.null(local.neighbors) || snn.k.self != k.self) { # recalculate local neighbors
  291. local.neighbors <- getLocalNeighbors(self$samples[! names(self$samples) %in% exclude.samples], k.self, k.self.weight, metric, l2.sigma=l2.sigma, verbose, self$n.cores)
  292. }
  293. el <- rbind(el,getLocalEdges(local.neighbors))
  294. }
  295. if(verbose) message('building graph .')
  296. el <- el[el[,3]>0,]
  297. g <- graph_from_edgelist(as.matrix(el[,c(1,2)]), directed =FALSE)
  298. E(g)$weight <- el[,3]
  299. E(g)$type <- el[,4]
  300. if(verbose) cat(".")
  301. # collapse duplicate edges
  302. g <- simplify(g, edge.attr.comb=list(weight="sum", type = "first"))
  303. if(verbose) message('done')
  304. if (!is.null(balancing.factor.per.sample)) {
  305. if (is.null(balancing.factor.per.cell)) {
  306. sf <- self$getDatasetPerCell()
  307. balancing.factor.per.cell <- setNames(balancing.factor.per.sample[as.character(sf)], names(sf))
  308. } else {
  309. warning("Both balancing.factor.per.cell and balancing.factor.per.sample are provided. Used the former for balancing edge weights")
  310. }
  311. }
  312. if (balance.edge.weights || !is.null(balancing.factor.per.cell)) {
  313. if(verbose) message('balancing edge weights ')
  314. if (is.null(balancing.factor.per.cell)) {
  315. balancing.factor.per.cell <- self$getDatasetPerCell()
  316. }
  317. g <- igraph::as_adjacency_matrix(g, attr="weight") %>%
  318. adjustWeightsByCellBalancing(factor.per.cell=balancing.factor.per.cell, balance.weights=balance.edge.weights,
  319. same.factor.downweight=same.factor.downweight) %>%
  320. igraph::graph_from_adjacency_matrix(mode="undirected", weighted=TRUE)
  321. if(verbose) message('done')
  322. }
  323. self$graph <- g
  324. return(invisible(g))
  325. },
  326. #' @description Calculate genes differentially expressed between cell clusters. Estimates base mean, z-score, p-values, specificity, precision, expressionFraction, AUC (if append.auc=TRUE)
  327. #'
  328. #' @param groups a cell factor (a factor named with cell names) specifying clusters of cells to be compared (one against all). To compare two cell clusters against each other, simply pass a factor containing only two levels (default: NULL, see clustering)
  329. #' @param clustering character Name of the clustering to use (see names(con$clusters)) for the value of the groups factor (default: NULL - if groups are not specified, the first clustering will be used)
  330. #' @param z.threshold numeric Minimum absolute value of a Z score for which the genes should be reported (default=3.0).
  331. #' @param upregulated.only boolean If TRUE, will report only genes significantly upregulated in each cluster; otherwise both up- and down-regulated genes will be reported (default=FALSE)
  332. #' @param append.specificity.metrics boolean Whether to append specificity metrics (default=TRUE)
  333. #' @param append.auc boolean Whether to append AUC scores (default=TRUE)
  334. #' @return list of DE results; each is a data frame with rows corresponding to the differentially expressed genes, and columns listing log2 fold change (M), signed Z scores (both raw and adjusted for mulitple hypothesis using BH correction), optional specificty/sensitivity and AUC metrics.
  335. #'
  336. getDifferentialGenes=function(clustering=NULL, groups=NULL, z.threshold=3.0, upregulated.only=FALSE, verbose=TRUE, append.specificity.metrics=TRUE, append.auc=TRUE) {
  337. groups <- parseCellGroups(self, clustering, groups)
  338. groups %<>% as.factor() %>% droplevels()
  339. # TODO: add Seurat
  340. '%ni%' <- Negate('%in%')
  341. if ('Pagoda2' %ni% class(self$samples[[1]])) {
  342. stop("Only Pagoda2 objects are supported for marker genes")
  343. }
  344. de.genes <- getDifferentialGenesP2(self$samples, groups=groups, z.threshold=z.threshold, upregulated.only=upregulated.only, verbose=verbose, n.cores=self$n.cores)
  345. de.genes <- de.genes[levels(groups)]
  346. if (append.specificity.metrics) {
  347. if (verbose) message("Estimating specificity metrics")
  348. cm.merged <- self$getJointCountMatrix(raw=TRUE)
  349. groups.clean <- groups %>% .[!is.na(.)] %>% .[names(.) %in% rownames(cm.merged)]
  350. de.genes %<>% lapply(function(x) if ((length(x) > 0) && (nrow(x) > 0)) subset(x, complete.cases(x)) else x)
  351. de.genes %<>% names() %>% setNames(., .) %>%
  352. sccore::plapply(function(n) appendSpecificityMetricsToDE(de.genes[[n]], groups.clean, n, p2.counts=cm.merged, append.auc=append.auc),
  353. progress=verbose, n.cores=self$n.cores, fail.on.error=TRUE)
  354. }
  355. if (verbose) message("All done!")
  356. return(de.genes)
  357. },
  358. #' @description Find cell clusters (as communities on the joint graph)
  359. #'
  360. #' @param method community detection method (igraph syntax) (default=leiden.community)
  361. #' @param min.group.size numeric Minimal allowed community size (default=0)
  362. #' @param name character Optional name of the clustering result (will default to the algorithm name) (default=NULL will try to obtain the name from the community detection method, or will use 'community' as a default)
  363. #' @param test.stability boolean Whether to test stability of community detection (default=FALSE)
  364. #' @param stability.subsampling.fraction numeric Fraction of clusters to subset (default=0.95). Must be within range [0, 1].
  365. #' @param stability.subsamples integer Number of subsampling iterations (default=100)
  366. #' @param cls optional pre-calculated community result (may be useful for stability testing) (default: NULL)
  367. #' @param sr optional pre-calculated subsampled community results (useful for stability testing) (default: NULL)
  368. #' @param ... extra parameters are passed to the specified community detection method
  369. #' @return invisible list containing identified communities (groups) and the full community detection result (result); The results are stored in $clusters$name slot in the conos object. Each such slot contains an object with elements: $results which stores the raw output of the community detection method, and $groups which is a factor on cells describing the resulting clustering. The later can be used, for instance, in plotting: con$plotGraph(groups=con$clusters$leiden$groups). If test.stability==TRUE, then the result object will also contain a $stability slot.
  370. #' @examples
  371. #' con <- Conos$new(small_panel.preprocessed, n.cores=1)
  372. #' con$buildGraph(k=10, k.self=5, space='PCA', ncomps=10, n.odgenes=20, matching.method='mNN',
  373. #' metric='angular', score.component.variance=TRUE, verbose=TRUE)
  374. #' con$findCommunities(method = igraph::walktrap.community, steps=5)
  375. #'
  376. findCommunities=function(method=leiden.community, min.group.size=0, name=NULL, test.stability=FALSE, stability.subsampling.fraction=0.95, stability.subsamples=100, verbose=TRUE, cls=NULL, sr=NULL, ...) {
  377. if (is.null(cls)) {
  378. cls <- method(self$graph, ...)
  379. }
  380. if (is.null(name)) {
  381. name <- cls$algorithm
  382. if(is.null(name)) {
  383. name <- "community"
  384. }
  385. }
  386. ## Extract groups from this graph
  387. cls.mem <- membership(cls)
  388. if(suppressWarnings(any(is.na(as.numeric(cls.mem))))) {
  389. cls.groups <- as.factor(cls.mem)
  390. } else {
  391. cls.groups <- factor(setNames(as.character(cls.mem),names(cls.mem)),levels=sort(as.numeric(unique(as.character(cls.mem))),decreasing=FALSE))
  392. }
  393. cls.levs <- levels(cls.groups)
  394. res <- list(groups=cls.groups,result=cls)
  395. # test stability
  396. if(test.stability) {
  397. subset.clustering <- function(g,f=stability.subsampling.fraction,seed=NULL, ...) {
  398. if(!is.null(seed)) { set.seed(seed) }
  399. vi <- sample(1:length(V(g)),ceiling(length(V(g))*(f)))
  400. sg <- induced_subgraph(g,vi)
  401. method(sg,...)
  402. }
  403. if(verbose) { message("running ",stability.subsamples," subsampling iterations ... ")}
  404. if (is.null(sr)) {
  405. sr <- papply(1:stability.subsamples,function(i) subset.clustering(self$graph,f=stability.subsampling.fraction,seed=i),n.cores=self$n.cores)
  406. }
  407. if(verbose) { message("done")}
  408. if(verbose) message("calculating flat stability stats ... ")
  409. # Jaccard coefficient for each cluster against all, plus random expectation
  410. jc.stats <- do.call(rbind, papply(sr,function(o) {
  411. p1 <- membership(o)
  412. p2 <- cls.groups[names(p1)]
  413. p1 <- as.character(p1)
  414. #x <- tapply(1:length(p2),factor(p2,levels=cls.levs),function(i1) {
  415. x <- tapply(1:length(p2),p2,function(i1) {
  416. i2 <- which(p1==p1[i1[[1]]])
  417. length(intersect(i1,i2))/length(unique(c(i1,i2)))
  418. })
  419. }, n.cores=self$n.cores, mc.preschedule=TRUE))
  420. # based on the following C code from clues:
  421. # v0.2.4 on Feb. 3, 2009 by Weiliang Qiu, <https://github.com/cran/clues/blob/master/R/adjustedRand.R>
  422. # (1) moved some code out of the for loop
  423. #
  424. # cl1 --- partition 1 of the data set
  425. # cl2 --- partition 2 of the data set
  426. #
  427. # flag = 1 --- Rand index
  428. # flag = 2 --- Hubert and Arabie's adjusted Rand index
  429. # flag = 3 --- Morey and Agresti's adjusted Rand index
  430. # flag = 4 --- Fowlkes and Mallows's index
  431. # flag = 5 --- Jaccard index
  432. # modified for C++, 19 January 2022
  433. adjustedRand <- function(cl1, cl2, randMethod = c("Rand","HA", "MA", "FM", "Jaccard")){
  434. if(!is.vector(cl1)){
  435. stop("cl1 is not a vector!\n")
  436. }
  437. if(!is.vector(cl2)){
  438. stop("cl2 is not a vector!\n")
  439. }
  440. if(length(cl1) != length(cl2)){
  441. stop("Two vectors have different lengths!\n")
  442. }
  443. len <- length(randMethod)
  444. if(len == 0){
  445. stop("The argument 'randMethod' is empty!\n")
  446. }
  447. # unique values of elements in 'cl1'
  448. cl1u <- unique(cl1)
  449. # number of clusters in partition 1
  450. m1 <- length(cl1u)
  451. # unique values of elements in 'cl2'
  452. cl2u <- unique(cl2)
  453. # number of clusters in partition 2
  454. m2 <- length(cl2u)
  455. n <- length(cl1)
  456. randVec <- rep(0, len)
  457. names(randVec) <- randMethod
  458. for(i in 1:len){
  459. randMethod[i] <- match.arg(arg = randMethod[i],
  460. choices = c("Rand","HA", "MA", "FM", "Jaccard"))
  461. flag <- match(randMethod[i],
  462. c("Rand","HA", "MA", "FM", "Jaccard"))
  463. c.res <- adjustedRandcpp(as.integer(cl1),
  464. as.integer(cl1u),
  465. as.integer(cl2),
  466. as.integer(cl2u),
  467. as.integer(m1),
  468. as.integer(m2),
  469. as.integer(n),
  470. as.integer(flag))
  471. randVec[i] <- c.res
  472. }
  473. return(randVec)
  474. }
  475. # Adjusted rand index
  476. if(verbose) message("adjusted Rand ... ")
  477. ari <- unlist(papply(sr,function(o) { ol <- membership(o); adjustedRand(as.integer(ol),as.integer(cls.groups[names(ol)]),randMethod='HA') }, n.cores=self$n.cores))
  478. if(verbose) message("done")
  479. res$stability <- list(flat=list(jc=jc.stats,ari=ari))
  480. # hierarchical measures
  481. if(verbose) message("calculating hierarchical stability stats ... ")
  482. if(is.hierarchical(cls)) {
  483. # hierarchical to hierarchical stability analysis - cut reference
  484. # determine hierarchy of clusters (above the cut)
  485. t.get.walktrap.upper.merges <- function(res,n=length(unique(membership(res)))) {
  486. clm <- complete.dend(res,FALSE)
  487. x <- tail(clm,n-1)
  488. x <- x - 2*nrow(res$merges) + nrow(x)-1
  489. # now all >=0 ids are cut leafs and need to be reassigned ids according to their rank
  490. xp <- x+nrow(x)+1
  491. xp[x<=0] <- rank(-x[x<=0])
  492. xp
  493. }
  494. clm <- t.get.walktrap.upper.merges(cls)
  495. res$stability$upper.tree <- clm
  496. if(verbose) message("tree Jaccard ... ")
  497. jc.hstats <- do.call(rbind, papply(sr,function(z) bestClusterThresholds(z,cls.groups,clm)$threshold, n.cores=self$n.cores))
  498. } else {
  499. # compute cluster hierarchy based on cell mixing (and then something)
  500. # assess stability for that hierarchy (to visualize internal node stability)
  501. # for the original clustering and every subsample clustering,
  502. if(verbose) message("upper clustering ... ")
  503. cgraph <- getClusterGraph(self$graph,cls.groups,plot=FALSE,normalize=FALSE)
  504. chwt <- walktrap.community(cgraph,steps=9)
  505. clm <- complete.dend(chwt,FALSE)
  506. if(verbose) message("clusterTree Jaccard ... ")
  507. jc.hstats <- do.call(rbind, papply(sr,function(st1) {
  508. mf <- membership(st1)
  509. mf <- as.factor(setNames(as.character(mf),names(mf)))
  510. st1g <- getClusterGraph(self$graph,mf, plot=FALSE, normalize=TRUE)
  511. st1w <- walktrap.community(st1g, steps=8)
  512. #merges <- st1w$merge; leaf.factor <- mf; clusters <- cls.groups
  513. x <- bestClusterTreeThresholds(st1w,mf,cls.groups,clm)
  514. x$threshold
  515. }, n.cores=self$n.cores))
  516. }
  517. res$stability$upper.tree <- clm
  518. res$stability$sr <- sr
  519. res$stability$hierarchical <- list(jc=jc.hstats)
  520. if(verbose) message("done")
  521. }
  522. ## Filter groups
  523. if(min.group.size>0) {
  524. lvls.keep <- names(which(table(cls.groups) > min.group.size))
  525. cls.groups[! as.character(cls.groups) %in% as.character(lvls.keep)] <- NA
  526. cls.groups <- as.factor(cls.groups)
  527. }
  528. res$groups <- cls.groups
  529. self$clusters[[name]] <- res
  530. return(invisible(res))
  531. },
  532. #' @description Plot panel of individual embeddings per sample with joint coloring
  533. #'
  534. #' @param groups a cell factor (a factor named with cell names) specifying clusters of cells to be compared (one against all). To compare two cell clusters against each other, simply pass a factor containing only two levels (default=NULL, see clustering)
  535. #' @param clustering character Name of the clustering to use (see names(con$clusters)) for the value of the groups factor (default=NULL - if groups are not specified, the first clustering will be used)
  536. #' @param use.local.clusters boolean Whether clusters should be taken from the individual samples; otherwise joint clusters in the conos object will be used (see clustering) (default=FALSE).
  537. #' @param plot.theme string Theme for the plot, passed to plotSamples() (default=NULL)
  538. #' @param use.common.embedding boolean Whether a joint embedding in the conos object should be used (or embeddings determined for the individual samples) (default=FALSE)
  539. #' @param embedding (default=NULL) If a character value is passed, it is interpreted as an embedding name (a name of a joint embedding in conos when use.commmon.embedding=TRUE, or a name of an embedding within the individual objects when use.common.embedding=FALSE).
  540. #' If a matrix is passed, it is interpreted as an actual embedding (then first two columns are interpreted as x/y coordinates, row names must be cell names). If NULL, the default embedding will be used.
  541. #' @param adj.list an optional list of additional ggplot2 directions to apply (default=NULL)
  542. #' @param ... Additional parameters passed to plotSamples(), plotEmbeddings(), sccore::embeddingPlot().
  543. #' @return cowplot grid object with the panel of plots
  544. #'
  545. plotPanel=function(clustering=NULL, groups=NULL, colors=NULL, gene=NULL, use.local.clusters=FALSE, plot.theme=NULL, use.common.embedding=FALSE, embedding=NULL, adj.list=NULL, ...) {
  546. if (use.local.clusters) {
  547. if (is.null(clustering) && !(inherits(x = self$samples[[1]], what = c('seurat', 'Seurat')))) {
  548. stop("You have to provide 'clustering' parameter to be able to use local clusters")
  549. }
  550. groups <- lapply(self$samples, getClustering, clustering) %>%
  551. lapply(function(cls) setNames(as.character(cls), names(cls))) %>% Reduce(c, .)
  552. if (is.null(groups)) {
  553. stop(paste0("No clustering '", clustering, "' presented in the samples"))
  554. }
  555. } else if (is.null(groups) && is.null(colors) && is.null(gene)) {
  556. groups <- getClusteringGroups(self$clusters, clustering)
  557. }
  558. if (use.common.embedding) { # look up the embedding within the conos object
  559. ## if use.common.embedding, pass the Conos embedding to plotSamples
  560. embedding <- private$getEmbedding(embedding)
  561. adj.list <- c(ggplot2::lims(x=range(embedding[,1]), y=range(embedding[,2])), adj.list)
  562. }
  563. plotSamples(self$samples, groups=groups, colors=colors, gene=gene, plot.theme=private$adjustTheme(plot.theme), embedding.type=embedding, adj.list=adj.list, ...)
  564. },
  565. #' @description Generate an embedding of a joint graph
  566. #'
  567. #' @param method Embedding method (default='largeVis'). Currently 'largeVis' and 'UMAP' are supported.
  568. #' @param embedding.name character Optional name of the name of the embedding set by user to store multiple embeddings (default: method name)
  569. #' @param M numeric (largeVis) The number of negative edges to sample for each positive edge to be used (default=1)
  570. #' @param gamma numeric (largeVis) The strength of the force pushing non-neighbor nodes apart (default=1)
  571. #' @param alpha numeric (largeVis) Hyperparameter used in the default distance function, \eqn{1 / (1 + \alpha \dot ||y_i - y_j||^2)} (default=0.1). The function relates the distance
  572. #' between points in the low-dimensional projection to the likelihood that the two points are nearest neighbors. Increasing \eqn{\alpha} tends
  573. #' to push nodes and their neighbors closer together; decreasing \eqn{\alpha} produces a broader distribution. Setting \eqn{\alpha} to zero
  574. #' enables the alternative distance function. \eqn{\alpha} below zero is meaningless.
  575. #' @param perplexity (largeVis) The perplexity passed to largeVis (default=NA)
  576. #' @param sgd_batches (largeVis) The number of edges to process during SGD (default=1e8). Defaults to a value set based on the size of the dataset. If the parameter given is
  577. #' between \code{0} and \code{1}, the default value will be multiplied by the parameter.
  578. #' @param seed numeric Random seed for the largeVis algorithm (default=1)
  579. #' @param target.dims numeric Number of dimensions for the reduction (default=2). Higher dimensions can be used to generate embeddings for subsequent reductions by other methods, such as tSNE
  580. #' @param ... additional arguments, passed to UMAP embedding (run ?conos:::embedGraphUmap for more info)
  581. #'
  582. embedGraph=function(method='largeVis', embedding.name=method, M=1, gamma=1, alpha=0.1, perplexity=NA, sgd_batches=1e8, seed=1, verbose=TRUE, target.dims=2, ...) {
  583. supported.methods <- c('largeVis', 'UMAP')
  584. if(!method %in% supported.methods) {
  585. stop(paste0("Currently, only the following embeddings are supported: ",paste(supported.methods,collapse=' ')))
  586. }
  587. ## check if embedding.name already in list
  588. ## if so, throw warning
  589. if (length(self$embeddings)>0){
  590. ## check if embedding.name already created
  591. if (embedding.name %in% names(self$embeddings)){
  592. warning(paste0("Already created an embedding: ", embedding.name, ". Overwriting."))
  593. }
  594. }
  595. if (method == 'largeVis') {
  596. wij <- as_adj(self$graph,attr='weight')
  597. if(!is.na(perplexity)) {
  598. wij <- buildWijMatrix(wij,perplexity=perplexity, threads=self$n.cores)
  599. }
  600. coords <- projectKNNs(wij = wij, dim=target.dims, verbose = verbose,sgd_batches = sgd_batches,gamma=gamma, M=M, seed=seed, alpha=alpha, rho=1, threads=self$n.cores)
  601. colnames(coords) <- V(self$graph)$name
  602. self$embedding <- t(coords)
  603. embedding.result <- self$embedding
  604. } else {
  605. ## method == 'UMAP'
  606. if (!requireNamespace("uwot", quietly=TRUE)){
  607. stop("You need to install package 'uwot' to be able to use UMAP embedding. Please install it.")
  608. }
  609. self$embedding <- embedGraphUmap(self$graph, verbose=verbose, return.all=FALSE, n.cores=self$n.cores, target.dims=target.dims, ...)
  610. embedding.result <- self$embedding
  611. }
  612. self$embeddings[[embedding.name]] <- embedding.result
  613. self$embedding <- embedding.result # hang on to the latest embedding for backwards compatibility
  614. return(invisible(embedding.result))
  615. },
  616. #' @description Plot cluster stability statistics.
  617. #'
  618. #' @param clustering string Name of the clustering result to show (default=NULL)
  619. #' @param what string Show a specific plot (ari - adjusted rand index, fjc - flat Jaccard, hjc - hierarchical Jaccard, dend - cluster dendrogram, all - everything except 'dend') (default='all')
  620. #' @return cluster stability statistics
  621. plotClusterStability=function(clustering=NULL, what='all') {
  622. if(is.null(clustering)){
  623. clustering <- names(self$clusters)[[1]]
  624. }
  625. if(is.null(self$clusters[[clustering]])){
  626. stop(paste("clustering",clustering,"doesn't exist, run findCommunity() first"))
  627. }
  628. if(is.null(self$clusters[[clustering]]$stability)){
  629. stop(paste("clustering",clustering,"doesn't have stability info. Run findCommunity( ... , test.stability=TRUE) first"))
  630. }
  631. st <- self$clusters[[clustering]]$stability
  632. nclusters <- ncol(st$flat$jc)
  633. jitter.alpha <- 0.1
  634. if (what=='all' || what=='ari') {
  635. p.fai <- ggplot2::ggplot(data.frame(aRI=st$flat$ari), ggplot2::aes(x=1,y=aRI)) +
  636. ggplot2::geom_boxplot(notch=TRUE, outlier.shape=NA) +
  637. ggplot2::geom_point(shape=16, position = ggplot2::position_jitter(), alpha=jitter.alpha) +
  638. ggplot2::guides(color=FALSE) +
  639. ggplot2::geom_hline(yintercept=1, linetype="dashed", alpha=0.2) +
  640. ggplot2::ylim(c(0,1)) + ggplot2::labs(x=" ", y="adjusted Rand Index") +
  641. ggplot2::theme(legend.position="none", axis.ticks.x=ggplot2::element_blank(), axis.text.x=ggplot2::element_blank())
  642. if(what=='ari')
  643. return(p.fai)
  644. }
  645. if (what=='all' || what=='fjc') {
  646. df <- reshape2::melt(st$flat$jc)
  647. colnames(df) <- c('rep','cluster','jc')
  648. df$cluster <- factor(colnames(st$flat$jc)[df$cluster],levels=levels(self$clusters[[clustering]]$groups))
  649. p.fjc <- ggplot2::ggplot(df,aes(x=cluster,y=jc,color=cluster)) +
  650. ggplot2::geom_boxplot(aes(color=cluster), notch=TRUE, outlier.shape=NA) +
  651. ggplot2::geom_jitter(shape=16, position=position_jitter(0.2), alpha=jitter.alpha) +
  652. ggplot2::guides(color=FALSE) +
  653. ggplot2::geom_hline(yintercept=1, linetype="dashed", alpha=0.2) +
  654. ggplot2::ylab("Jaccard coefficient (flat)") + ggplot2::ylim(c(0,1))
  655. if(what=='fjc') return(p.fjc)
  656. }
  657. if (what=='all' || what=='hjc') {
  658. # hierarchical
  659. df <- reshape2::melt(st$hierarchical$jc[,1:nclusters])
  660. colnames(df) <- c('rep','cluster','jc')
  661. df$cluster <- factor(colnames(st$flat$jc)[df$cluster],levels=levels(self$clusters[[clustering]]$groups))
  662. p.hjc <- ggplot2::ggplot(df,aes(x=cluster,y=jc,color=cluster)) +
  663. ggplot2::geom_boxplot(aes(color=cluster), notch=TRUE, outlier.shape=NA) +
  664. ggplot2::geom_jitter(shape=16, position=ggplot2::position_jitter(0.2), alpha=jitter.alpha) +
  665. ggplot2::guides(color=FALSE) +
  666. ggplot2::geom_hline(yintercept=1, linetype="dashed", alpha=0.2) +
  667. ggplot2::ylab("Jaccard coefficient (hierarchical)") + ggplot2::ylim(c(0,1))
  668. if(what=='hjc') return(p.hjc)
  669. }
  670. if (what=='dend') {
  671. m <- st$upper.tree
  672. nleafs <- nrow(m)+1
  673. m[m<=nleafs] <- -1*m[m<=nleafs]
  674. m[m>0] <- m[m>0]-nleafs
  675. hc <- list(merge=m, height=1:nrow(m), labels=levels(self$clusters[[clustering]]$groups), order=c(1:nleafs))
  676. class(hc) <- 'hclust'
  677. # fix the ordering so that edges don't intersects
  678. hc$order <- order.dendrogram(as.dendrogram(hc))
  679. d <- as.dendrogram(hc) %>% dendextend::hang.dendrogram()
  680. # depth-first traversal of a merge matrix
  681. t.dfirst <- function(m,i=nrow(m)) {
  682. rl <- m[i,1]; if(rl<0) { rl <- abs(rl) } else { rl <- t.dfirst(m,rl) }
  683. rr <- m[i,2]; if(rr<0) { rr <- abs(rr) } else { rr <- t.dfirst(m,rr) }
  684. c(i+nrow(m)+1,rl,rr)
  685. }
  686. xy <- dendextend::get_nodes_xy(d)
  687. to <- t.dfirst(hc$merge)
  688. plot(d,las=2,axes=FALSE)
  689. # flat on the left
  690. #x <- apply(st$flat$jc,2,median)
  691. #text(xy,labels=round(x[to],2),col='blue',adj=c(-0.1,-1.24),cex=0.8)
  692. x <- apply(st$hierarchical$jc,2,median)
  693. text(xy,labels=round(x[to],2),col='red',adj=c(-0.1,-0.12),cex=0.8)
  694. return(NULL)
  695. }
  696. cowplot::plot_grid(plotlist=list(p.fai,p.fjc,p.hjc),nrow=1,rel_widths=c(4,nclusters,nclusters))
  697. },
  698. #' @description Plot joint graph
  699. #'
  700. #' @param groups a cell factor (a factor named with cell names) specifying clusters of cells to be compared (one against all). To compare two cell clusters against each other, simply pass a factor containing only two levels (default: NULL, see clustering)
  701. #' @param clustering a character name of the clustering to use (see names(con$clusters)) for the value of the groups factor (default: NULL - if groups are not specified, the first clustering will be used)
  702. #' @param color.by character A shortcut to color the plot by 'cluster' or by 'sample' (default: 'cluster'). If any other string is input, an error is thrown.
  703. #' @param embedding A character name of an embedding, or a matrix of the actual embedding (rownames should correspond to cells, first to columns to x/y coordinates). If NULL (default: NULL), the latest generated embedding will be used
  704. #' @param colors a color factor (named with cell names) use for cell coloring (default=NULL)
  705. #' @param gene Show expression of a gene (default=NULL)
  706. #' @param plot.theme Theme for the plot, passed to sccore::embeddingPlot() (default=NULL)
  707. #' @param subset A subset of cells to show (default: NULL - shows all the cells)
  708. #' @param ... Additional parameters passed to sccore::embeddingPlot()
  709. #' @return ggplot2 plot of joint graph
  710. #'
  711. plotGraph=function(color.by='cluster', clustering=NULL, embedding=NULL, groups=NULL, colors=NULL, gene=NULL, plot.theme=NULL, subset=NULL, ...) {
  712. embedding <- private$getEmbedding(embedding)
  713. if (!is.null(subset)) {
  714. embedding <- embedding[rownames(embedding) %in% subset,,drop=FALSE]
  715. }
  716. if (!is.null(gene)) {
  717. colors <- lapply(self$samples, getGeneExpression, gene) %>% Reduce(c, .)
  718. if(all(is.na(colors))) stop(paste("Gene", gene,"is not found in any of the samples"))
  719. }
  720. if(is.null(groups) && is.null(colors)) {
  721. if(color.by == 'cluster') {
  722. groups <- getClusteringGroups(self$clusters, clustering)
  723. } else if(color.by == 'sample') {
  724. groups <- self$getDatasetPerCell()
  725. } else {
  726. stop('Supported values of color.by are ("cluster" and "sample"); Use groups/colors parameters to explicitly pass factor/numeric data for coloring')
  727. }
  728. }
  729. return(embeddingPlot(embedding, groups=groups, colors=colors, plot.theme=private$adjustTheme(plot.theme), ...))
  730. },
  731. #' @description Smooth expression of genes to minimize the batch effect between samples
  732. #' Use diffusion of expression on graph with the equation dv = exp(-a * (v + b))
  733. #'
  734. #' @param genes List of genes to be smooothed smoothing (default=NULL will smooth top n.od.genes overdispersed genes)
  735. #' @param n.od.genes numeric If 'genes' is NULL, top n.od.genes of overdispersed genes are taken across all samples (default=500)
  736. #' @param fading numeric Level of fading of expression change from distance on the graph (parameter 'a' of the equation) (default=10)
  737. #' @param fading.const numeric Minimal penalty for each new edge during diffusion (parameter 'b' of the equation) (default=0.5)
  738. #' @param max.iters numeric Maximal number of diffusion iterations (default=15)
  739. #' @param tol numeric Tolerance after which the diffusion stops (default=5e-3)
  740. #' @param name string Name to save the correction (default='diffusion')
  741. #' @param verbose boolean Verbose mode (default=TRUE)
  742. #' @param count.matrix Alternative gene count matrix to correct (rows: genes, columns: cells; has to be dense matrix). Default: joint count matrix for all datasets.
  743. #' @param normalize boolean Whether to normalize values (default=TRUE)
  744. #' @return smoothed expression of the input genes
  745. #'
  746. correctGenes=function(genes=NULL, n.od.genes=500, fading=10.0, fading.const=0.5, max.iters=15, tol=5e-3, name='diffusion', verbose=TRUE, count.matrix=NULL, normalize=TRUE) {
  747. edges <- igraph::as_edgelist(self$graph)
  748. edge.weights <- igraph::edge.attributes(self$graph)$weight
  749. if (is.null(count.matrix)) {
  750. if (is.null(genes)) {
  751. genes <- getOdGenesUniformly(self$samples, n.genes=n.od.genes)
  752. }
  753. cms <- lapply(self$samples, getCountMatrix, transposed=TRUE)
  754. genes <- Reduce(intersect, lapply(cms, colnames)) %>% intersect(genes)
  755. count.matrix <- Reduce(rbind, lapply(cms, function(x) x[, genes])) %>% as.matrix()
  756. } else {
  757. count.matrix <- t(count.matrix)
  758. }
  759. vn <- V(self$graph)$name
  760. if(!all(rownames(count.matrix)==vn)) { # subset to a common set of genes
  761. if(!all(vn %in% rownames(count.matrix))) {
  762. stop("count.matrix does not provide values for all the vertices in the alignment graph!")
  763. }
  764. count.matrix <- count.matrix[vn,]
  765. }
  766. ## Wrapper to make is.label.fixed optional
  767. smoothMatrixOnGraph <- function(edges, edge.weights, matrix, is.label.fixed=logical(), ...) {
  768. smooth_count_matrix(edges, edge.weights, matrix, is_label_fixed=is.label.fixed, ...)
  769. }
  770. cm <- smoothMatrixOnGraph(edges, edge.weights, count.matrix, max_n_iters=max.iters, diffusion_fading=fading,
  771. diffusion_fading_const=fading.const, verbose=verbose, normalize=normalize)
  772. return(invisible(self$expression.adj[[name]] <<- cm))
  773. },
  774. #' @description Estimate labeling distribution for each vertex, based on a partial labeling of the cells.
  775. #' There are two methods used for the propagation to calculate the distribution of labels: "solver" and "diffusion".
  776. #' * "diffusion" (default) will estimate the labeling distribution for each vertex, based on provided labels using a random walk.
  777. #' * "solver" will propagate labels using the algorithm described by Zhu, Ghahramani, Lafferty (2003) <http://mlg.eng.cam.ac.uk/zoubin/papers/zgl.pdf>
  778. #' Confidence values are then calculated by taking the maximum value from this distribution of labels, for each cell.
  779. #'
  780. #' @param labels Input labels
  781. #' @param method type of propagation. Either 'diffusion' or 'solver'. 'solver' gives better result
  782. #' but has bad asymptotics, so is inappropriate for datasets > 20k cells. (default='diffusion')
  783. #' @param ... additional arguments for conos:::propagateLabels* functions
  784. #' @return list with three fields:
  785. #' * labels = matrix with distribution of label probabilities for each vertex by rows.
  786. #' * uncertainty = 1 - confidence values
  787. #' * label.distribution = the distribution of labels calculated using either the methods "diffusion" or "solver"
  788. #'
  789. propagateLabels=function(labels, method="diffusion", ...) {
  790. if (method == "solver") {
  791. label.dist <- propagateLabelsSolver(self$graph, labels, ...)
  792. } else if (method == "diffusion") {
  793. label.dist <- propagateLabelsDiffusion(self$graph, labels, ...)
  794. } else {
  795. stop("Unknown method: ", method, ". Only 'solver' and 'diffusion' are supported.")
  796. }
  797. labels <- colnames(label.dist)[apply(label.dist, 1, which.max)] %>%
  798. setNames(rownames(label.dist))
  799. confidence <- apply(label.dist, 1, max) %>% setNames(rownames(label.dist))
  800. return(list(labels=labels, uncertainty=(1 - confidence), label.distribution=label.dist))
  801. },
  802. #' @description Calculate pseudo-bulk expression matrices for clusters (by adding up, for each gene, all of the molecules detected for all cells in a given cluster in a given sample)
  803. #'
  804. #' @param common.genes boolean Whether to bring individual sample matrices to a common gene list (default=TRUE)
  805. #' @param omit.na.cells boolean If set to FALSE, the resulting matrices will include a first column named 'NA' that will report total molecule counts for all of the cells that were not covered by the provided factor. (default=TRUE)
  806. #' @return a list of per-sample uniform dense matrices with rows being genes, and columns being clusters
  807. #'
  808. getClusterCountMatrices=function(clustering=NULL, groups=NULL, common.genes=TRUE, omit.na.cells=TRUE) {
  809. if(is.null(groups)) {
  810. groups <- getClusteringGroups(self$clusters, clustering)
  811. }
  812. groups <- as.factor(groups)
  813. matl <- lapply(self$samples,function(s) {
  814. m <- getRawCountMatrix(s,trans=TRUE) # rows are cells
  815. cl <- factor(groups[match(rownames(m),names(groups))],levels=levels(groups));
  816. tc <- colSumByFactor(m,cl)
  817. if(omit.na.cells) { tc <- tc[-1,,drop=FALSE] }
  818. t(tc)
  819. })
  820. # bring to a common gene space
  821. if(common.genes) {
  822. gs <- unique(unlist(lapply(matl,rownames)))
  823. matl <- lapply(matl,function(m) {
  824. nm <- matrix(0,nrow=length(gs),ncol=ncol(m))
  825. colnames(nm) <- colnames(m)
  826. rownames(nm) <- gs
  827. mi <- match(rownames(m),gs)
  828. nm[mi,] <- m
  829. nm
  830. })
  831. }
  832. matl
  833. },
  834. #' @description applies 'getCellNames()' on all samples
  835. #' @return list of cellnames for all samples
  836. #' @examples
  837. #' con <- Conos$new(small_panel.preprocessed, n.cores=1)
  838. #' con$getDatasetPerCell()
  839. #'
  840. getDatasetPerCell=function() {
  841. getSampleNamePerCell(self$samples)
  842. },
  843. #' @description Retrieve joint count matrices
  844. #'
  845. #' @param raw boolean If TRUE, return merged "raw" count matrices, using function getRawCountMatrix(). Otherwise, return the merged count matrices, using getCountMatrix(). (default=FALSE)
  846. #' @return list of merged count matrices
  847. #' @examples
  848. #' con <- Conos$new(small_panel.preprocessed, n.cores=1)
  849. #' con$getJointCountMatrix()
  850. #'
  851. getJointCountMatrix=function(raw=FALSE) {
  852. lapply(self$samples, (if (raw) getRawCountMatrix else getCountMatrix), transposed=TRUE) %>%
  853. mergeCountMatrices(transposed=TRUE)
  854. }
  855. ),
  856. ## Private functions
  857. private = list(
  858. adjustTheme=function(theme) {
  859. if (is.null(theme)) {
  860. theme <- ggplot2::theme()
  861. }
  862. main.theme <- ggplot2::theme_bw() + ggplot2::theme(
  863. legend.background=ggplot2::element_rect(fill=ggplot2::alpha("white", 0.6)),
  864. plot.margin=ggplot2::margin()
  865. )
  866. if (self$override.conos.plot.theme) {
  867. return(main.theme + ggplot2::theme_get() + theme)
  868. }
  869. return(main.theme + theme)
  870. },
  871. # a utility function to look up an embedding by name or accept an actual embedding data
  872. getEmbedding=function(embedding) {
  873. if (!is.null(embedding)) {
  874. if (class(embedding) %in% c('matrix')) { # actuall embedding was passed
  875. # check validity?
  876. } else if (inherits(embedding, 'character')) { # look up embedding by name
  877. ## check if embedding.name exists in list
  878. if (embedding %in% names(self$embeddings)) {
  879. ## embedding to plot
  880. embedding <- self$embeddings[[embedding]]
  881. } else {
  882. ## embedding.name not in list of self$embeddings, so the user is confused
  883. ## throw error
  884. stop(paste0("No embedding named '", embedding, "' found. Please generate this with embedGraph()."))
  885. }
  886. } else {
  887. stop('embedding must be either a character name of the embedding, or an actual matrix of embedding coordinates')
  888. }
  889. } else {
  890. if(!is.null(self$embedding)) { # use the latest
  891. embedding <- self$embedding
  892. } else { # pick one from the list
  893. if(is.null(self$embeddings) || length(self$embeddings)<1) stop("no joint embeddings have been generated; use embedGraph() first")
  894. embedding <- self$embedding[length(self$embedding)] # by default, pick last-named embedding
  895. }
  896. }
  897. return(embedding)
  898. },
  899. updatePairs=function(space='PCA', data.type='counts', ncomps=50, n.odgenes=1e3, var.scale=TRUE, matching.mask=NULL, exclude.samples=NULL, score.component.variance=FALSE, verbose=FALSE) {
  900. # make a list of all pairs
  901. sample.names <- names(self$samples)
  902. if(!is.null(exclude.samples)) {
  903. mi <- sample.names %in% exclude.samples
  904. if(verbose) { message("excluded ", sum(mi), " out of ", length(sample.names), " samples, based on supplied exclude.samples") }
  905. sample.names <- sample.names[!mi]
  906. }
  907. # TODO: add random subsampling for very large panels
  908. if(!is.null(matching.mask)) { # remove pairs that shouldn't be compared directly
  909. tryCatch(matching.mask <- matching.mask[sample.names, sample.names],
  910. error=function(e) stop("matching.mask should have the same row- and colnames as provided samples. Error:", e))
  911. matching.mask <- matching.mask | t(matching.mask)
  912. selected.ids <- which(lower.tri(matching.mask) & matching.mask) - 1
  913. sn.pairs <- sample.names[selected.ids %/% length(sample.names) + 1] %>%
  914. cbind(sample.names[selected.ids %% length(sample.names) + 1]) %>%
  915. t()
  916. if(verbose) message("Use ", ncol(sn.pairs), " pairs, based on the passed exclude.pairs")
  917. } else {
  918. sn.pairs <- combn(sample.names, 2)
  919. }
  920. # determine the pairs that need to be calculated
  921. if (is.null(self$pairs[[space]])) {
  922. self$pairs[[space]] <- list()
  923. }
  924. mi <- rep(NA,ncol(sn.pairs))
  925. nm <- match(apply(sn.pairs,2,paste,collapse='.vs.'),names(self$pairs[[space]]))
  926. mi[which(!is.na(nm))] <- na.omit(nm)
  927. # try reverse match as well
  928. nm <- match(apply(sn.pairs[c(2,1),,drop=FALSE],2,paste,collapse='.vs.'),names(self$pairs[[space]]));
  929. mi[which(!is.na(nm))] <- na.omit(nm)
  930. if(verbose) message('found ',sum(!is.na(mi)),' out of ',length(mi),' cached ',space,' space pairs ... ')
  931. if(any(is.na(mi))) { # some pairs are missing
  932. if(verbose) message('running ',sum(is.na(mi)),' additional ',space,' space pairs ')
  933. xl2 <- sccore::plapply(which(is.na(mi)), function(i) {
  934. if (space=='CPCA') {
  935. xcp <- quickCPCA(self$samples[sn.pairs[,i]],data.type=data.type,ncomps=ncomps,n.odgenes=n.odgenes,verbose=FALSE,var.scale=var.scale, score.component.variance=score.component.variance)
  936. } else if(space=='JNMF') {
  937. xcp <- quickJNMF(self$samples[sn.pairs[,i]],data.type=data.type,n.comps=ncomps,n.odgenes=n.odgenes,var.scale=var.scale,verbose=FALSE,max.iter=3e3)
  938. } else if (space == 'genes') {
  939. xcp <- quickNULL(p2.objs = self$samples[sn.pairs[,i]], data.type=data.type, n.odgenes=n.odgenes, var.scale = var.scale, verbose = FALSE)
  940. } else if (space == 'PCA') {
  941. xcp <- quickPlainPCA(self$samples[sn.pairs[,i]], data.type=data.type,ncomps=ncomps,n.odgenes=n.odgenes,verbose=FALSE,var.scale=var.scale, score.component.variance=score.component.variance)
  942. } else if (space == 'CCA' || space=='PMA') {
  943. xcp <- quickCCA(self$samples[sn.pairs[,i]],data.type=data.type,ncomps=ncomps,n.odgenes=n.odgenes,verbose=FALSE,var.scale=var.scale, score.component.variance=score.component.variance,PMA=(space=='PMA'))
  944. }
  945. if(verbose) cat(".")
  946. xcp
  947. }, n.cores=self$n.cores, mc.preschedule=(space=='PCA'), progress=FALSE, fail.on.error=TRUE)
  948. names(xl2) <- apply(sn.pairs[,which(is.na(mi)),drop=FALSE],2,paste,collapse='.vs.')
  949. xl2 <- xl2[!unlist(lapply(xl2,is.null))]
  950. self$pairs[[space]] <- c(self$pairs[[space]],xl2)
  951. }
  952. # re-do the match and order
  953. mi <- rep(NA,ncol(sn.pairs))
  954. nm <- match(apply(sn.pairs,2,paste,collapse='.vs.'),names(self$pairs[[space]]))
  955. mi[which(!is.na(nm))] <- na.omit(nm)
  956. nm <- match(apply(sn.pairs[c(2,1),,drop=FALSE],2,paste,collapse='.vs.'),names(self$pairs[[space]]))
  957. mi[which(!is.na(nm))] <- na.omit(nm)
  958. if(any(is.na(mi))) {
  959. warning("unable to get complete set of pair comparison results")
  960. sn.pairs <- sn.pairs[,!is.na(mi),drop=FALSE]
  961. }
  962. if(verbose) message(" done")
  963. return(invisible(sn.pairs))
  964. }
  965. )
  966. )

conclass.R at commit e656464, under GPL-3.0 · at the source

Overview

Authors: Gith Noes-Holt1, Kathrine L Jensen1, Raquel Comaposada-Baró1, Sara E Jager1, Mette Richner2, Carolyn M Goddard1, Marco BK Kowenicki3, Line Sivertsen1, Lucía Jiménez-Fernández1, Rita C Andersen1, Jamila H Lilja1, Andreas H Larsen1, Sofie P Boesgaard1, Grace A Houser1, Nikolaj R Christensen1, Antonio Marino4, Anke Tappe-Theodor5, Michael Wierer4, Christian B Vægter2, Rohini Kuner5, Kenneth L Madsen1, Andreas T Sørensen1
  1. Molecular Neuropharmacology and Genetics Laboratory, Department of Neuroscience, Faculty of Health and Medical Sciences, University of Copenhagen, 2200 Copenhagen, Denmark
  2. Dandrite, Department of Biomedicine, Aarhus University, 8000 Aarhus, Denmark
  3. Zyneyro, 2200 Copenhagen, Denmark
  4. Proteomics Research Infrastructure, Center for Core Facilities, Faculty of Health and Medical Sciences, University of Copenhagen, 2200 Copenhagen, Denmark
  5. Institute of Pharmacology, Medical Faculty Heidelberg, Heidelberg University, 69120 Heidelberg, Germany
Institutions: University of Copenhagen (Denmark); Aarhus University (Denmark); Heidelberg University (Germany)
Journal: Cell reports. Medicine, volume 7, issue 6, article 102800
Dates: received 10 October 2024; accepted 15 April 2026; published online 13 May 2026; in print June 2026
Type: Research article · Language: English
License: CC BY-NC
Identifiers: DOI 10.1016/j.xcrm.2026.102800 · PMID 42134332 · PMCID PMC13293952 · OpenAlex W7161168513
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), mouse (organism), pain (population)
Methods: Spectral & time-frequency, Statistics, Smoothing, state filtering, decompositions, Machine learning
Keywords: PICK1, protein interacting with C-kinase 1, neuropathic pain, pain treatment, neuropathic pain treatment, gene therapy, AAV therapeutics
MeSH: Carrier Proteins*, Chronic Pain*, Dependovirus*, Nuclear Proteins*, Peptides*, Animals, Cell Cycle Proteins, Disease Models, Animal, Ganglia, Spinal, Gene Therapy Agents, Genetic Therapy, Genetic Vectors, Humans, Hyperalgesia, Male, Mice, Neuralgia, Neurons, Recombinant Proteins (* major topic)
Topic: Pain Mechanisms and Treatments (Physiology, Medicine), according to OpenAlex
Funding: Innovation Fund Denmark (9122-00012B); Lundbeck Foundation (R344-2020-1063, R389-2021-1596, R347-2020-2339, R433-2023-1585, R322-2019-1816); Augustinus Foundation (17-3517); Danmarks Frie Forskningsfond (2025-00028B); Novo Nordisk Fonden (NNF19SA0059305); AP Møller and Chastine McKinney Møller Foundation (18-L-0213, L-2021-0031); Københavns Universitet
Citations: not cited yet (Europe PMC); 77 references in the paper
Research resources: Chicken anti-NF200 RRID:AB_11212161, RRID:AB_2074426, Mouse monoclonal anti-PICK1 [L20/8] RRID:AB_2164544, RRID:AB_2191072, RFP Antibody Pre-adsorbed RRID:AB_2209751, Rabbit anti-mCherry RRID:AB_2552323, RRID:AB_2562010, RRID:AB_2563458, RRID:AB_2563667, RRID:AB_2716852, Mouse monoclonal anti-PSD95 [K28/43] RRID:AB_2750929, RRID:AB_2800630, Rabbit monoclonal anti-GCN4 [C11L34] RRID:AB_3095603, RRID:AB_312798, Purified anti-mouse CD16/32 [93] RRID:AB_312801, FITC anti-mouse CD45 [30-F11] RRID:AB_312973, PE anti-mouse TCR β chain [H57-597] RRID:AB_313430, APC anti-mouse CD19 [6D5] RRID:AB_313646, Goat anti-CGRP RRID:AB_725807, pUCmini-iCAP-PHP.eB RRID:Addgene_103005, pUCmini-iCAP-PHP.S RRID:Addgene_103006, pAAV2/8 RRID:Addgene_112864, pAdDeltaF6 RRID:Addgene_112867, RRID:IMSR_JAX:007914, RRID:IMSR_JAX:032536, Mouse: Hoxb8tm2.1(cre)Mrc/J RRID:IMSR_JAX:035978, IB4-647 RRID:SCR_014365

Abstract

Chronic neuropathic pain’s impact, persistence, and limited treatments render it relevant for gene therapy. Here, we describe the development and application of self-assembling dimeric peptide inhibitors of the pain-associated scaffolding protein PICK1 (protein interacting with C-kinase 1), delivered by adeno-associated viral (AAV) vectors. In mice, these peptides prevent mechanical allodynia in inflammatory and neuropathic pain models and reverse neuropathic pain for up to 1 year. Targeting somatosensory pathways relieves pain without overt side effects, while selective transduction of dorsal root ganglion (DRG) neurons is sufficient to provide pain relief. Using proteomic and phosphoproteomic analysis of DRG tissue, we identify regulation of protein kinase C alpha (PRKCA) as a candidate that potentially shapes this pain-relieving phenotype. We finally confirm PICK1 expression and peptide target engagement in human donor tissue, supporting the potential of AAV-encoded PICK1 inhibitors as a clinically meaningful strategy for neuropathic pain conditions.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 4 matches between paragraphs and lines of code.

AllonKleinLab/scrublet

License: MIT
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 88d6efe9b3bc34acc33067c82312164b287334cf, 3 July 2019
Languages: Python (8), Jupyter (6)
Size: 20 files, 14 scripts
Software Heritage: not archived
Found in: the resources table
Holds: README, license file, environment (requirements.txt, setup.py, old_versions/v0.1/requirements.txt, old_versions/v0.1/setup.py), 6 notebooks
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: Matplotlib (12 files), NumPy (10 files), SciPy (9 files), scikit-learn (8 files), pandas (4 files), UMAP (4 files), NetworkX (2 files), scikit-image (2 files), Numba (1 file), Scanpy (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
16 files

kharchenkolab/pagoda2

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 35f71aba4349354c77a8beb208796dbfd5df616b, 1 April 2026
Languages: JavaScript (42), R (16), C++ (9), C/C++ (5), Shell (1)
Size: 198 files, 73 scripts
Software Heritage: archived
Found in: the resources table
Holds: README, environment (DESCRIPTION, docker/Dockerfile), tests, continuous integration, documentation, 2 notebooks
Not found: license file, CITATION.cff
Tools: igraph (6 files), ggplot2 (4 files), tidyverse (3 files), UMAP (2 files), data.table (1 file), mgcv (1 file), pheatmap (1 file), WGCNA (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
74 files

kharchenkolab/conos

License: GPL-3.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: e656464d7063550340647f11230ce066cddf3868, 25 April 2026
Languages: R (22), C++ (14), C/C++ (3), Jupyter (1)
Size: 161 files, 40 scripts
Software Heritage: archived
Found in: the resources table
Holds: README, license file, environment (DESCRIPTION, docker/Dockerfile), tests, continuous integration, documentation, 5 notebooks
Not found: CITATION.cff
Tools: cowplot (8 files), igraph (8 files), tidyverse (6 files), ggplot2 (5 files), reshape2 (4 files), Seurat (4 files), UMAP (2 files), ComplexHeatmap (1 file), DESeq2 (1 file), pandas (1 file), Scanpy (1 file), SciPy (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
42 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 127 scripts, each with its path and the digest of its content;
  • 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Other data links

Data availability

The MS proteomics data have been deposited to the ProteomeXchange Consortium (https://proteomecentral.proteomexchange.org) (http://proteomecentral.proteomexchange.org) via the PRIDE repository, for the global proteome (PRIDE: PXD074049 (https://proteomecentral.proteomexchange.org/cgi/GetDataset?ID=PXD074049)) and for the phosphorylation enrichment (PRIDE: PXD073925 (https://proteomecentral.proteomexchange.org/cgi/GetDataset?ID=PXD073925)) (https://www.ebi.ac.uk/pride/archive). Each deposit datasets comprise two technical replicates, using either deuterated or non-deuterated labels. After confirming replication consistency, only samples with non-deuterated labels were retained for further analysis.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Data Availability Statement

Raw data from snRNA-seq have been deposited in the SRA database SRA: SRP645687 (https://trace.ncbi.nlm.nih.gov/Traces/?study=SRP645687) (SRA: SRX31117391 (https://www.ncbi.nlm.nih.gov/sra/SRX31117391[accn]), SRX31117392 (https://www.ncbi.nlm.nih.gov/sra/SRX31117392[accn])), whereas processed snRNA-seq data are accessible at the Zenodo repository (Zenodo: 17629531 (https://zenodo.org/records/17629531)). MS proteomic (PRIDE: PXD074049 (https://proteomecentral.proteomexchange.org/cgi/GetDataset?ID=PXD074049)) and phosphoproteomic (PRIDE: PXD073925 (https://proteomecentral.proteomexchange.org/cgi/GetDataset?ID=PXD073925)) datasets are accessible at the PRIDE repository. The paper does not report original code. Any additional information required to reanalyze the data reported in this work is available from the lead contact upon request.

The raw single nucleus RNA sequencing data have been deposited to The Sequence Read Archive (SRA) (https://www.ncbi.nlm.nih.gov/sra) under SRA study SRP645687 (https://trace.ncbi.nlm.nih.gov/Traces/?study=SRP645687) (SRA: SRX31117391 (https://www.ncbi.nlm.nih.gov/sra/SRX31117391[accn]), SRX31117392 (https://www.ncbi.nlm.nih.gov/sra/SRX31117392[accn])), and the processed data to Zenodo (https://zenodo.org/) (Zenodo: 17629531 (https://zenodo.org/records/17629531)).

The MS proteomics data have been deposited to the ProteomeXchange Consortium (https://proteomecentral.proteomexchange.org) (http://proteomecentral.proteomexchange.org) via the PRIDE repository, for the global proteome (PRIDE: PXD074049 (https://proteomecentral.proteomexchange.org/cgi/GetDataset?ID=PXD074049)) and for the phosphorylation enrichment (PRIDE: PXD073925 (https://proteomecentral.proteomexchange.org/cgi/GetDataset?ID=PXD073925)) (https://www.ebi.ac.uk/pride/archive). Each deposit datasets comprise two technical replicates, using either deuterated or non-deuterated labels. After confirming replication consistency, only samples with non-deuterated labels were retained for further analysis.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 22 authors, 7 keywords, 19 MeSH terms, 7 funders, 77 references, 27 RRIDs.

Cite

This paper

Noes-Holt, G., Jensen, K. L., Comaposada-Baró, R., Jager, S. E., Richner, M., Goddard, C. M., Kowenicki, M. B., Sivertsen, L., Jiménez-Fernández, L., Andersen, R. C., Lilja, J. H., Larsen, A. H., Boesgaard, S. P., Houser, G. A., Christensen, N. R., Marino, A., Tappe-Theodor, A., Wierer, M., Vægter, C. B., . . . Sørensen, A. T. (2026). Recombinant dimeric PICK1 peptide inhibitors for long-term relief of chronic pain by AAV therapeutics. Cell reports. Medicine, 7(6), 102800. https://doi.org/10.1016/j.xcrm.2026.102800

BibTeX

@article{noesholt2026recombinant,
author = {Noes-Holt, Gith and Jensen, Kathrine L and Comaposada-Baró, Raquel and Jager, Sara E and Richner, Mette and Goddard, Carolyn M and Kowenicki, Marco BK and Sivertsen, Line and Jiménez-Fernández, Lucía and Andersen, Rita C and Lilja, Jamila H and Larsen, Andreas H and Boesgaard, Sofie P and Houser, Grace A and Christensen, Nikolaj R and Marino, Antonio and Tappe-Theodor, Anke and Wierer, Michael and Vægter, Christian B and Kuner, Rohini and Madsen, Kenneth L and Sørensen, Andreas T},
title = {{Recombinant dimeric PICK1 peptide inhibitors for long-term relief of chronic pain by AAV therapeutics}},
journal = {Cell reports. Medicine},
year = {2026},
month = may,
volume = {7},
number = {6},
pages = {102800},
publisher = {Elsevier},
issn = {2666-3791},
doi = {10.1016/j.xcrm.2026.102800},
url = {https://doi.org/10.1016/j.xcrm.2026.102800},
pmid = {42134332},
pmcid = {PMC13293952}
}

RIS

TY - JOUR
AU - Noes-Holt, Gith
AU - Jensen, Kathrine L
AU - Comaposada-Baró, Raquel
AU - Jager, Sara E
AU - Richner, Mette
AU - Goddard, Carolyn M
AU - Kowenicki, Marco BK
AU - Sivertsen, Line
AU - Jiménez-Fernández, Lucía
AU - Andersen, Rita C
AU - Lilja, Jamila H
AU - Larsen, Andreas H
AU - Boesgaard, Sofie P
AU - Houser, Grace A
AU - Christensen, Nikolaj R
AU - Marino, Antonio
AU - Tappe-Theodor, Anke
AU - Wierer, Michael
AU - Vægter, Christian B
AU - Kuner, Rohini
AU - Madsen, Kenneth L
AU - Sørensen, Andreas T
TI - Recombinant dimeric PICK1 peptide inhibitors for long-term relief of chronic pain by AAV therapeutics
T2 - Cell reports. Medicine
J2 - Cell Rep Med
PY - 2026
DA - 2026/05/13
VL - 7
IS - 6
SP - 102800
SN - 2666-3791
PB - Elsevier
DO - 10.1016/j.xcrm.2026.102800
UR - https://doi.org/10.1016/j.xcrm.2026.102800
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.xcrm.2026.102800",
"type": "article-journal",
"title": "Recombinant dimeric PICK1 peptide inhibitors for long-term relief of chronic pain by AAV therapeutics",
"container-title": "Cell reports. Medicine",
"author": [
{
"family": "Noes-Holt",
"given": "Gith"
},
{
"family": "Jensen",
"given": "Kathrine L"
},
{
"family": "Comaposada-Baró",
"given": "Raquel"
},
{
"family": "Jager",
"given": "Sara E"
},
{
"family": "Richner",
"given": "Mette"
},
{
"family": "Goddard",
"given": "Carolyn M"
},
{
"family": "Kowenicki",
"given": "Marco BK"
},
{
"family": "Sivertsen",
"given": "Line"
},
{
"family": "Jiménez-Fernández",
"given": "Lucía"
},
{
"family": "Andersen",
"given": "Rita C"
},
{
"family": "Lilja",
"given": "Jamila H"
},
{
"family": "Larsen",
"given": "Andreas H"
},
{
"family": "Boesgaard",
"given": "Sofie P"
},
{
"family": "Houser",
"given": "Grace A"
},
{
"family": "Christensen",
"given": "Nikolaj R"
},
{
"family": "Marino",
"given": "Antonio"
},
{
"family": "Tappe-Theodor",
"given": "Anke"
},
{
"family": "Wierer",
"given": "Michael"
},
{
"family": "Vægter",
"given": "Christian B"
},
{
"family": "Kuner",
"given": "Rohini"
},
{
"family": "Madsen",
"given": "Kenneth L"
},
{
"family": "Sørensen",
"given": "Andreas T"
}
],
"container-title-short": "Cell Rep Med",
"volume": "7",
"issue": "6",
"page": "102800",
"DOI": "10.1016/j.xcrm.2026.102800",
"PMID": "42134332",
"PMCID": "PMC13293952",
"ISSN": "2666-3791",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.xcrm.2026.102800",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
13
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: WGCNA, UMAP, igraph, 18 other tools, 1 reference
[2] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: WGCNA, UMAP, igraph, 16 other tools, mouse
[3] doi:10.1016/j.xcrm.2026.102651 [code]
Integrative CSF profiling identifies disease-specific immune responses in leptomeningeal disease.
Journal: Cell reports. Medicine
In common: UMAP, igraph, Numba, 16 other tools
[4] doi:10.1093/bioinformatics/btag592 [code]
Network-based stratification of allele-specific expression reveals patient subgroups in Huntington's disease.
Journal: Bioinformatics (Oxford, England)
In common: mgcv, WGCNA, UMAP, 15 other tools
[5] doi:10.1038/s41586-026-10629-x [code]
Whole-genome duplication shaped cell-type evolution in the vertebrate brain.
Journal: Nature
In common: UMAP, igraph, Numba, 14 other tools, mouse, 1 reference
[6] doi:10.1038/s41467-026-76675-1 [code]
Long-read proteogenomic atlas of human neuronal differentiation reveals isoform diversity informing neurodevelopmental risk mechanisms.
Journal: Nature communications
In common: WGCNA, UMAP, igraph, 14 other tools
[7] doi:10.1002/imt2.70163 [code]
Spatial multi-omics unveils sphingolipid metabolic reprogramming within the retinal pathological niche.
Journal: iMeta
In common: igraph, DESeq2, Scanpy, 14 other tools, mouse
[8] doi:10.1038/s44318-026-00818-9 [code]
FAM134B-mediated ER-phagy degrades APP and suppresses Alzheimer's disease pathology.
Journal: The EMBO journal
In common: UMAP, igraph, Numba, 13 other tools, mouse
[9] doi:10.1126/sciadv.aeg3223 [code]
The extreme diversity of retinal amacrine cells has deep evolutionary roots.
Journal: Science advances
In common: WGCNA, igraph, DESeq2, 13 other tools
[10] doi:10.1038/s41586-026-10214-2 [code]
Multidimensional profiling of heterogeneity in supratentorial ependymomas.
Journal: Nature
In common: igraph, DESeq2, Scanpy, 13 other tools, mouse

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.