OSCR

Comprehensive RNA velocity by modeling the cascade of gene regulation, transcription, and splicing from single-cell RNA sequencing data with TSvelo.

Code ↔ Paper

8 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 8 matches
  1. [1] § Results › TSvelo can model 3D gene dynamics and predict cell fate on pancreas dataset ↔ FIG8_paper.r, lines 41–130 · score 0.68 · Ngn3 low EP, Ngn3 high EP, Ductal, Beta, pancreas, Alpha
  2. [2] § Methods › Lineages segmentation and pseudotime initialization ↔ TSvelo/TSvelo_branch.py, lines 27–47 · score 0.62 · PAGA graph, detect lineages, shortest, Edges, cluster
  3. [3] § Results › TSvelo can capture gene dynamics well and predict cell fate on mouse brain data ↔ TSvelo/TSvelo_pp.py, lines 16–50 · score 0.59 · Radial Glia, mouse brain, IPCs, RNA
  4. [4] § Results › Estimate RNA velocity with TSVelo ↔ functions.R, lines 753–786 · score 0.59 · analytical solutions, degradation rate, splicing rate, transcription rate, trajectory, ODE
  5. [5] § Methods › Preprocessing for scRNA-seq data ↔ dataSimulationScVelo.r, lines 43–107 · score 0.58 · highly variable genes, variance, scVelo, seq, clustering, cell
  6. [6] § Methods › Optimizing global time and Neural ODE in EM framework ↔ TSvelo_run.py, lines 37–77 · score 0.58 · ChEA, Neural ODE, ENCODE, databases, TF, trained
  7. [7] § Results › TSvelo can predict cell fate and model lineage-specific gene dynamics for multi-lineage tasks ↔ functions.R, lines 944–985 · score 0.54 · degradation rates, splicing rates, scVelo, transcriptional rates, trajectory, branches
  8. [8] § Methods › Optimizing global time and Neural ODE in EM framework ↔ TSvelo/TSvelo_model.py, lines 127–187 · score 0.53 · Adam, gradient, trained, optimizer, loss, zero

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,298 lines · 58 KB · no license · 2 matches

  1. # ------------- ANALYTIC FUNCTIONS ----------------
  2. # -------------------------------------------------------------------------
  3. # Function: u
  4. # Purpose:
  5. # Computes the amount of *unspliced RNA* for multiple cells for a fixed gene,
  6. # based on RNA velocity ODE's model
  7. #
  8. # Parameters:
  9. # t : Current time (scalar or vector).
  10. # t0_off : Initial time when the gene switches to the "off" state.
  11. # t0_on : Initial time when the gene switches to the "on" state (default = 0).
  12. # u0_off : Initial unspliced RNA amount in the "off" state.
  13. # u0_on : Initial unspliced RNA amount in the "on" state (optional).
  14. # If NA, it is set to the OFF steady-state value alpha_off/beta.
  15. # k : scalar or vector of same length of t indicating the transcriptional state
  16. # for each cell (0 = off, 2 = on).
  17. # alpha : Two-element vector of transcription rates:
  18. # alpha[1] = rate when gene is off,
  19. # alpha[2] = rate when gene is on.
  20. # beta : Splicing rate constant (scalar). Equal to 1 by default.
  21. #
  22. # Returns:
  23. # A numeric vector of the same length of t containing the unspliced RNA values
  24. # for each cell at time t.
  25. # -------------------------------------------------------------------------
  26. u <- function(t, t0_off, t0_on = 0, u0_off, u0_on = NA, k, alpha, beta = 1) {
  27. # If not provided, initialize u0_on as the steady-state level for the "off" state
  28. if (any(is.na(u0_on))) {
  29. u0_on <- alpha[1] / beta
  30. }
  31. n_cells <- length(k) # number of cells
  32. # Compute elapsed time (tau) depending on cell state
  33. tau <- ifelse(k == 2, t - t0_on, t - t0_off)
  34. # Precompute exponential decay terms for both states
  35. expBetaOFF <- exp(-beta * tau[k == 0])
  36. expBetaON <- exp(-beta * tau[k == 2])
  37. # Initialize result vector
  38. res <- rep(NA, n_cells)
  39. # Assign appropriate initial unspliced RNA based on cell state
  40. u0 <- ifelse(k == 2, u0_on, u0_off)
  41. # Compute unspliced RNA
  42. res[k == 0] <- u0[k == 0] * expBetaOFF + alpha[1]/beta * (1 - expBetaOFF)
  43. res[k == 2] <- u0[k == 2] * expBetaON + alpha[2]/beta * (1 - expBetaON)
  44. return(res)
  45. }
  46. # -------------------------------------------------------------------------
  47. # Function: s
  48. # Purpose:
  49. # Computes the amount of *spliced RNA* for multiple cells for a fixed gene,
  50. # based on RNA velocity ODE's model
  51. #
  52. # Parameters:
  53. # t : Current time (scalar or vector).
  54. # t0_off : Initial time when the gene switches to the "off" state.
  55. # t0_on : Initial time when the gene switches to the "on" state (default = 0).
  56. # u0_off : Initial unspliced RNA amount in the "off" state.
  57. # u0_on : Initial unspliced RNA amount in the "on" state (optional).
  58. # If NA, it is set to the OFF steady-state value alpha_off/beta.
  59. # s0_off : Initial spliced RNA amount in the "off" state.
  60. # s0_on : Initial spliced RNA amount in the "on" state (optional).
  61. # If NA, it is set to the OFF steady-state value alpha_off/gamma.
  62. # k : scalar or vector of same length of t indicating the transcriptional state
  63. # for each cell (0 = off, 2 = on).
  64. # alpha : Two-element vector of transcription rates:
  65. # alpha[1] = rate when gene is off,
  66. # alpha[2] = rate when gene is on.
  67. # beta : Splicing rate constant (scalar). Equal to 1 by default.
  68. # gamma : Degradation rate constant (scalar).
  69. #
  70. # Returns:
  71. # A numeric vector of the same length of t containing the spliced RNA values
  72. # for each cell at time t.
  73. # -------------------------------------------------------------------------
  74. s <- function(t, t0_off, t0_on = 0, u0_off, u0_on = NA, s0_off, s0_on = NA, k, alpha, beta = 1, gamma){
  75. # If not provided, initialize u0_on and s0_on as the steady-state level for the "off" state
  76. if(is.na(u0_on)){
  77. u0_on = alpha[1]/beta
  78. }
  79. if(is.na(s0_on)){
  80. s0_on = alpha[1]/gamma
  81. }
  82. n_cells <- length(k) # number of cells
  83. # Compute elapsed time (tau) depending on cell state
  84. tau <- ifelse(k == 2, t - t0_on, t - t0_off)
  85. # Precompute exponential decay terms for both states
  86. exp_gammaOFF <- exp(-gamma*tau[k == 0])
  87. exp_gammaON <- exp(-gamma*tau[k == 2])
  88. exp_betaOFF <- exp(-beta*tau[k == 0])
  89. exp_betaON <- exp(-beta*tau[k == 2])
  90. # Initialize result vector
  91. res <- rep(NA, n_cells)
  92. # Assign appropriate initial unspliced and spliced RNA based on cell state
  93. u0 <- ifelse(k == 2, u0_on, u0_off)
  94. s0 <- ifelse(k == 2, s0_on, s0_off)
  95. # Compute unspliced RNA
  96. res[k == 0] <- s0[k == 0]*exp_gammaOFF + alpha[1]/gamma*(1-exp_gammaOFF)+ (alpha[1]-beta*u0[k == 0])/(gamma-beta)*(exp_gammaOFF - exp_betaOFF)
  97. res[k == 2] <- s0[k == 2]*exp_gammaON + alpha[2]/gamma*(1-exp_gammaON) + (alpha[2]-beta*u0[k == 2])/(gamma-beta)*(exp_gammaON - exp_betaON)
  98. return(res)
  99. }
  100. # -------------------------------------------------------------------------
  101. # Function: s_di_u
  102. # Purpose:
  103. # Computes the amount of *spliced RNA* for multiple cells for a fixed gene,
  104. # given the amount of the *unspliced RNA*.
  105. # Note that this formula holds when beta is different from gamma.
  106. #
  107. # Parameters:
  108. # u : Current value of unspliced RNA for he different cells (scalar or vector).
  109. # u0_off : Initial unspliced RNA amount in the "off" state.
  110. # u0_on : Initial unspliced RNA amount in the "on" state (optional).
  111. # If NA, it is set to the OFF steady-state value alpha_off/beta.
  112. # k : scalar or vector of same length of t indicating the transcriptional state
  113. # for each cell (0 = off, 2 = on).
  114. # alpha : Two-element vector of transcription rates:
  115. # alpha[1] = rate when gene is off,
  116. # alpha[2] = rate when gene is on.
  117. # beta : Splicing rate constant (scalar). Equal to 1 by default.
  118. # gamma : Degradation rate constant (scalar).
  119. #
  120. # Returns:
  121. # A numeric vector of the same length of t containing the spliced RNA values
  122. # for each cell with unspliced RNA u.
  123. # -------------------------------------------------------------------------
  124. s_di_u <- function(u, u0_off, s0_off, u0_on = NA, s0_on = NA, k, alpha, beta = 1, gamma){
  125. # check that beta not equal to gamma
  126. if(beta == gamma){
  127. warning("WARNING: this formula holds for beta not equal gamma, so in this case you can not use it!")
  128. }
  129. # If not provided, initialize u0_on and s0_on as the steady-state level for the "off" state
  130. if(is.na(u0_on)){
  131. u0_on = alpha[1]/beta
  132. }
  133. if(is.na(s0_on)){
  134. s0_on = alpha[1]/gamma
  135. }
  136. # set the parameters depending of the state
  137. if(k == 0){
  138. a <- alpha[1]
  139. u0 <- u0_off
  140. s0 <- s0_off
  141. }else{
  142. a <- alpha[2]
  143. u0 <- u0_on
  144. s0 <- s0_on
  145. }
  146. # compute the spliced RNA
  147. res <- (s0 - a/gamma + (a-beta*u0)/(gamma-beta))*((beta*u-a)/(beta*u0 - a))^(gamma/beta) - (a*beta)/(gamma*(gamma-beta)) + beta/(gamma-beta)*u
  148. return(res)
  149. }
  150. # -------------------------------------------------------------------------
  151. # Function: u0
  152. # Purpose:
  153. # Computes the amount of u-coordinate of the initial point (for on or off phase) for a fixed gene.
  154. #
  155. # Parameters:
  156. # t0_off : Initial time when the gene switches to the "off" state (optional).
  157. # If NA, it is set to Infinity, meaning that we reach the upper steady state and do not enter in the repressive phase.
  158. # t0_on : Initial time when the gene switches to the "on" state (default = 0).
  159. # u0_off : Initial unspliced RNA amount in the "off" state (optional).
  160. # If NA and k == 2, it is set to the ON steady-state value alpha_on/beta.
  161. # u0_on : Initial unspliced RNA amount in the "on" state (optional).
  162. # If NA and k == 0, it is set to the OFF steady-state value alpha_off/beta.
  163. # k : scalar indicating if we want to compute the initial point for the on (k = 2) or
  164. # off (k = 0) phase.
  165. # alpha : Two-element vector of transcription rates:
  166. # alpha[1] = rate when gene is off,
  167. # alpha[2] = rate when gene is on.
  168. # beta : Splicing rate constant (scalar). Equal to 1 by default.
  169. #
  170. # Returns:
  171. # A scalar with the computed u0.
  172. # -------------------------------------------------------------------------
  173. u0 <- function(t0_off = NA, t0_on = 0, u0_off = NA, u0_on = NA, k, alpha, beta){
  174. # we want to compute the initial u-coordinate for the repressive phase
  175. if(k == 0){
  176. if(any(is.na(u0_on))){
  177. u0_on <- alpha[1]/beta # the initial u-coordinate for the inductive phase coincides with the lower steady state
  178. }
  179. if(any(is.na(t0_off))){
  180. t <- rep(NA, length(k))
  181. t[which(is.na(t0_off))] <- rep(Inf, length(which(is.na(t0_off)))) # the cells do not switch and remain in the inductive state, such that the upper steady state is reached
  182. }else{
  183. t <- t0_off # the upper steady state is not reached and the dynamic switch to the repressive phase
  184. }
  185. res <- u(t = t, t0_off = t0_off, t0_on = t0_on, u0_off = NA, u0_on = u0_on, k = rep(2, length(t)), alpha = alpha, beta = beta)
  186. }
  187. # we want to compute the initial u-coordinate for the inductive phase
  188. if(k == 2){
  189. if(any(is.na(u0_off))){
  190. u0_off <- alpha[2]/beta # the initial u-coordinate for the inductive phase coincides with the lower steady state
  191. }
  192. res <- u(t = Inf, t0_off = 0, t0_on = NA, u0_off = u0_off, u0_on = NA, k = rep(0, length(t)), alpha = alpha, beta = beta)
  193. }
  194. return(res)
  195. }
  196. # -------------------------------------------------------------------------
  197. # Function: s0
  198. # Purpose:
  199. # Computes the amount of s-coordinate of the initial point (for on or off phase) for a fixed gene.
  200. #
  201. # Parameters:
  202. # t0_off : Initial time when the gene switches to the "off" state (optional).
  203. # If NA, it is set to Infinity, meaning that we reach the upper steady state and do not enter in the repressive phase.
  204. # t0_on : Initial time when the gene switches to the "on" state (default = 0).
  205. # u0_off : Initial unspliced RNA amount in the "off" state (optional).
  206. # If NA and k == 2, it is set to the ON steady-state value alpha_on/beta.
  207. # If NA and k == 0, the initial point is computed with the function u0()
  208. # u0_on : Initial unspliced RNA amount in the "on" state (optional).
  209. # If NA and k == 0, it is set to the OFF steady-state value alpha_off/beta.
  210. # s0_off : Initial spliced RNA amount in the "off" state (optional).
  211. # If NA and k == 2, it is set to the ON steady-state value alpha_on/gamma
  212. # s0_on : Initial spliced RNA amount in the "on" state (optional).
  213. # If NA and k == 0, it is set to the OFF steady-state value alpha_off/gamma
  214. # k : scalar indicating if we want to compute the initial point for the on (k = 2) or
  215. # off (k = 0) phase.
  216. # alpha : Two-element vector of transcription rates:
  217. # alpha[1] = rate when gene is off,
  218. # alpha[2] = rate when gene is on.
  219. # beta : Splicing rate constant (scalar). Equal to 1 by default.
  220. # gamma : Degradation rate constant (scalar).
  221. # Returns:
  222. # A scalar with the computed s0.
  223. # -------------------------------------------------------------------------
  224. s0 <- function(t0_off = NA, t0_on = 0, u0_off = NA, u0_on = NA, s0_off = NA, s0_on = NA, k, alpha, beta, gamma){
  225. if(k == 0){
  226. if(any(is.na(s0_on))){
  227. s0_on <- alpha[1]/gamma
  228. }
  229. if(any(is.na(u0_on))){
  230. u0_on <- alpha[1]/beta
  231. }
  232. if(any(is.na(t0_off))){
  233. t <- rep(NA, length(k))
  234. t[which(is.na(t0_off))] <- rep(Inf, length(which(is.na(t0_off)))) # the cells do not switch and remain in the inductive state, such that the upper steady state is reached
  235. }else{
  236. t <- t0_off # the upper steady state is not reached and the dynamic switch to the repressive phase
  237. }
  238. if(any(is.na(u0_off))){
  239. # compute the initial u-coordinate
  240. u0_off <- u0(t0_off = t0_off, t0_on = t0_on, u0_off = NA, u0_on = u0_on, k = 0, alpha = alpha, beta = beta)
  241. }
  242. # compute s0
  243. res <- s(t = t, t0_off = t0_off, t0_on = t0_on, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = rep(2, length(t)), alpha, beta, gamma)
  244. }
  245. if(k == 2){
  246. if(any(is.na(s0_off))){
  247. s0_off <- alpha[2]/gamma
  248. }
  249. if(any(is.na(u0_off))){
  250. u0_off <- alpha[2]/beta
  251. }
  252. res <- s(rep(Inf, length(t0_on)), t0_off = 0, t0_on = NA, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = rep(0, length(t0_on)), alpha = alpha, beta = beta, gamma = gamma)
  253. }
  254. return(res)
  255. }
  256. # -------------------------------------------------------------------------
  257. # Function: u_and_s_withTauMCMC
  258. # Purpose:
  259. # Compute unspliced (u) and spliced (s) RNA levels for multiple genes,
  260. # cells and MCMC samples (or other 3D structure) given per-cell elapsed
  261. # times (tau) and kinetic parameters. This implements the closed-form
  262. # solutions of linear ODEs for the 2-state transcription model
  263. # (states: off (k==0) and on (k==2)).
  264. #
  265. # Inputs (expected shapes):
  266. # tau : array (dim: [subtype, n_genes, n_samples] or similar)
  267. # elapsed time for each subgroup, gene and MCMC sample.
  268. # u0_off : array with initial unspliced RNA in the "off" state
  269. # u0_on : array with initial unspliced RNA in the "on" state.
  270. # s0_off : array with initial spliced RNA in the "off" state.
  271. # s0_on : array with initial spliced RNA in the "o" state.
  272. # k : integer array with same dim as tau indicating
  273. # transcriptional state per element (0 = off, 2 = on).
  274. # alpha : numeric array of transcription rates. Expected to
  275. # have dimensions [genes, state_index(1/2), samples]
  276. # (uses alpha[,1,] for off and alpha[,2,] for on).
  277. # beta : array for splicing rate ([n_genes, n_samples]).
  278. # gamma : array for degradation rate ([n_genes, n_samples]).
  279. # subtypeCell : vector indicating subtype index for each cell
  280. # (used to assign off-state initial values by subtype).
  281. # typeCellT0_off : vector indicating switching time labels
  282. #
  283. # Returns:
  284. # A list with:
  285. # $u : array of same shape as tau with computed unspliced RNA values.
  286. # $s : array of same shape as tau with computed spliced RNA values.
  287. # -------------------------------------------------------------------------
  288. u_and_s_withTauMCMC <- function(tau, u0_off, u0_on = NA, s0_off, s0_on = NA, k, alpha, beta, gamma, subtypeCell, typeCellT0_off){
  289. # Set inductive initial values equal to off steady state coordinates, if not assigned.
  290. if(any(is.na(u0_on))){
  291. u0_on <- alpha[, 1, ]/beta
  292. }
  293. if(any(is.na(s0_on))){
  294. s0_on <- alpha[, 1, ]/gamma
  295. }
  296. # expand the elements, such that they will have the same dimension of tau
  297. u0_on = sweep(k == 2, MARGIN = c(2, 3), STATS = u0_on, FUN = "*")
  298. s0_on = sweep(k == 2, MARGIN = c(2, 3), STATS = s0_on, FUN = "*")
  299. # expand also the off initial coordinates, such that they will have the same dimension of tau
  300. u0_off_new <- array(NA, dim = dim(tau))
  301. s0_off_new <- array(NA, dim = dim(tau))
  302. for(sty in unique(subtypeCell)){
  303. # assign the corresponding off-state initial arrays to all elements
  304. # in each subtypes
  305. tyT0_off <- typeCellT0_off[which(subtypeCell == sty)[1]]
  306. u0_off_new[sty, , ] = u0_off[tyT0_off, , ]
  307. s0_off_new[sty, , ] = s0_off[tyT0_off, , ]
  308. }
  309. # Compose the full initial conditions: choose on vs off initial values
  310. # depending on k (state indicator).
  311. u0 = u0_on * (k == 2) + u0_off_new * (k == 0)
  312. s0 = s0_on * (k == 2) + s0_off_new * (k == 0)
  313. # upper-steady state u-coordinate
  314. uSS_on <- alpha[,2,]/beta
  315. # Exponential decay factors for splicing (beta) and degradation (gamma) rates.
  316. expBeta <- exp(-sweep(tau, MARGIN = c(2, 3), STATS = beta, FUN = "*"))
  317. expGamma <- exp(-sweep(tau, MARGIN = c(2, 3), STATS = gamma, FUN = "*"))
  318. p2 <- sweep(k == 0, MARGIN = c(2, 3), STATS = alpha[, 1, ]/beta, FUN = "*")*(1-expBeta)
  319. # Transcription rate depending on the state
  320. alpha <- sweep(k == 2, MARGIN = c(2, 3), STATS = alpha[, 2, ], FUN = "*") + sweep(k == 0, MARGIN = c(2, 3), STATS = alpha[, 1, ], FUN = "*")
  321. # unspliced and spliced values
  322. resS <- s0*expGamma + sweep(alpha, MARGIN = c(2, 3), STATS = gamma, FUN = "/")*(1-expGamma) + sweep((alpha-sweep(u0, MARGIN = c(2, 3), STATS = beta, FUN = "*")), MARGIN = c(2, 3), STATS = (gamma-beta), FUN = "/")*(expGamma - expBeta)
  323. resU <- u0*expBeta + p2 + sweep(k==2, MARGIN = c(2, 3), STATS = uSS_on, FUN = "*")*(1-expBeta)
  324. return(list(u = resU, s = resS))
  325. }
  326. # -------------------------------------------------------------------------
  327. # Function: u0_MCMC
  328. # Purpose:
  329. # Compute initial unspliced RNA (u0) for MCMC-shaped arrays.
  330. #
  331. # Arguments:
  332. # t0_off : 3D array [n_switching_clusters, n_genes, n_samples] with time of OFF-switch.
  333. # t0_on : scalar or array; default 0, for the starting time of the on phase.
  334. # k : scalar or vector of states (0 or 2).
  335. # alpha : array [n_genes, 2, n_samples] for transcription rate (alpha[,1,] = off, alpha[,2,] = on).
  336. # beta : scalar or array [n_genes, n_samples] for splicing rate. By default equal to 1.
  337. #
  338. # Returns:
  339. # Array of same shape as t0_off with u0 values for each element.
  340. # Note that we can compute just one between u0_off and u0_on (for different genes) for each call of this function.
  341. # -------------------------------------------------------------------------
  342. u0_MCMC <- function(t0_off = NA, t0_on = 0, k, alpha, beta = 1){
  343. # If multiple k values provided, keep only unique values.
  344. if(length(k) > 1){
  345. k <- unique(k)
  346. }
  347. # initial point for repressive phase
  348. if(k == 0){
  349. # transform u0_on according to the dimension of t0_off
  350. u0_on <- alpha[, 1, ]/beta
  351. u0_on <- array(rep(as.vector(u0_on),dim(t0_off)[1]), dim = c(dim(t0_off)[2], dim(t0_off)[3], dim(t0_off)[1]))
  352. u0_on <- aperm(u0_on, c(3, 1, 2))
  353. t <- t0_off # we assume the switch from on to off phase occurs
  354. t0_on <- array(t0_on, dim = dim(u0_on))
  355. tau <- t - t0_on # elapsed time in the dynamic
  356. # coordinate of the on steady state
  357. uSS_ON <- alpha[, 2,]/beta
  358. uSS_ON <- array(rep(as.vector(uSS_ON), dim(t0_off)[1]), dim = c(dim(t0_off)[2], dim(t0_off)[3], dim(t0_off)[1]))
  359. uSS_ON <- aperm(uSS_ON, c(3, 1, 2))
  360. # modify the splicing rate accordingly to the dimension of t0:off
  361. beta <- array(rep(as.vector(beta), dim(t0_off)[1]), dim = c(dim(t0_off)[2], dim(t0_off)[3], dim(t0_off)[1]))
  362. beta <- aperm(beta, c(3, 1, 2))
  363. expBetaOFF <- exp(-beta*tau)
  364. # u0_off
  365. res <- u0_on*expBetaOFF + uSS_ON*(1-expBetaOFF)
  366. }
  367. # initial point for inductive phase
  368. if(any(k == 2)){
  369. res <- alpha[,1,]/beta
  370. }
  371. return(res)
  372. }
  373. # -------------------------------------------------------------------------
  374. # Function: s0_MCMC
  375. # Purpose:
  376. # Compute initial spliced RNA (s0) for MCMC-shaped arrays.
  377. #
  378. # Arguments:
  379. # t0_off : 3D array [n_switching_clusters, n_genes, n_samples] with time of OFF-switch.
  380. # t0_on : scalar or array; default 0, for the starting time of the on phase.
  381. # k : scalar or vector of states (0 or 2).
  382. # alpha : array [n_genes, 2, n_samples] for transcription rate (alpha[,1,] = off, alpha[,2,] = on).
  383. # beta : scalar or array [n_genes, n_samples] for splicing rate. By default equal to 1.
  384. # gamma : scalar or array [n_genes, n_samples] for degradation rate.
  385. #
  386. # Returns:
  387. # Array of same shape as t0_off with s0 values for each element.
  388. # Note that we can compute just one between s0_off and s0_on (for different genes) for each call of this function.
  389. # -------------------------------------------------------------------------
  390. s0_MCMC <- function(t0_off = NA, t0_on = 0, k, alpha, beta, gamma){
  391. # If multiple k values provided, keep only unique values.
  392. if(length(k) > 1){
  393. k <- unique(k)
  394. }
  395. # initial point for repressive phase
  396. if(k == 0){
  397. # transform s0_on according to the dimension of t0_off
  398. s0_on = alpha[, 1, ]/gamma
  399. s0_on <- array(rep(as.vector(s0_on),dim(t0_off)[1]), dim = c(dim(t0_off)[2], dim(t0_off)[3], dim(t0_off)[1]))
  400. s0_on <- aperm(s0_on, c(3, 1, 2))
  401. # transform s0_on according to the dimension of t0_off
  402. u0_on = alpha[,1,]/beta
  403. u0_on <- array(rep(as.vector(u0_on),dim(t0_off)[1]), dim = c(dim(t0_off)[2], dim(t0_off)[3], dim(t0_off)[1]))
  404. u0_on <- aperm(u0_on, c(3, 1, 2))
  405. t <- t0_off # we assume the switch from on to off phase occurs
  406. t0_on <- array(t0_on, dim = dim(u0_on))
  407. tau <- t - t0_on # elapsed time in the dynamic
  408. # coordinate of the on steady state
  409. sSS_ON <- alpha[,2,]/gamma
  410. sSS_ON <- array(rep(as.vector(sSS_ON), dim(t0_off)[1]), dim = c(dim(t0_off)[2], dim(t0_off)[3], dim(t0_off)[1]))
  411. sSS_ON <- aperm(sSS_ON, c(3, 1, 2))
  412. # transform the rates according to the dimension of t0:off
  413. beta <- array(rep(as.vector(beta), dim(t0_off)[1]), dim = c(dim(t0_off)[2], dim(t0_off)[3], dim(t0_off)[1]))
  414. beta <- aperm(beta, c(3, 1, 2))
  415. gamma <- array(rep(as.vector(gamma), dim(t0_off)[1]), dim = c(dim(t0_off)[2], dim(t0_off)[3], dim(t0_off)[1]))
  416. gamma <- aperm(gamma, c(3, 1, 2))
  417. alpha2 <- array(rep(as.vector(alpha[,2,]), dim(t0_off)[1]), dim = c(dim(t0_off)[2], dim(t0_off)[3], dim(t0_off)[1]))
  418. alpha2 <- aperm(alpha2, c(3, 1, 2))
  419. exp_gammaOFF <- exp(-gamma*tau)
  420. exp_betaOFF <- exp(-beta*tau)
  421. res <- s0_on*exp_gammaOFF + sSS_ON*(1-exp_gammaOFF)+ (alpha2-beta*u0_on)/(gamma-beta)*(exp_gammaOFF - exp_betaOFF)
  422. }
  423. # initial point for inductive phase
  424. if(any(k == 2)){
  425. res <- alpha[,1,]/gamma
  426. }
  427. return(res)
  428. }
  429. # ------------- LOAD THE DATA ----------------
  430. # -------------------------------------------------------------------------
  431. # Function: nameGenes
  432. # Purpose:
  433. # Load the name of the considered genes for the real data.
  434. #
  435. # Parameters:
  436. # pathData: path where the file (named as "var.csv") with the name of the genes is stored.
  437. # Returns:
  438. # A vector with the name of the genes.
  439. # -------------------------------------------------------------------------
  440. nameGenes <- function(pathData){
  441. var <- read.csv(paste(pathData, "/var.csv", sep = ""))
  442. nameG <- var$index
  443. return(nameG)
  444. }
  445. # -------------------------------------------------------------------------
  446. # Function: nameCells
  447. # Purpose:
  448. # Load the name of the considered cells for the real data.
  449. #
  450. # Parameters:
  451. # pathData: path where the file (named as "obs.csv") with the name of the cells is stored.
  452. # Returns:
  453. # A vector with the name of the cells.
  454. # -------------------------------------------------------------------------
  455. nameCells <- function(pathData){
  456. obs <- read.csv(paste(pathData, "/obs.csv", sep = ""))
  457. nameC<- obs$index
  458. return(nameC)
  459. }
  460. # -------------------------------------------------------------------------
  461. # Function: loadRealData
  462. # Purpose:
  463. # Load the values of spliced and unspliced RNA for the real data.
  464. #
  465. # Parameters:
  466. # typeProcessing: type of pre-processing that have been previously applied to the data.
  467. # It can be "filter", "filter_and_normalize", "filter_and_normalize_noLog", filter_and_normalize moments"
  468. # pathData: path where the data are stored.
  469. # names: boolean if we want to keep the name of cells and genes or not. By default it is FALSE.
  470. # sparse: boolean if we want to transform the loaded matrices in sparse objects or not. By default it is FALSE.
  471. # Returns:
  472. # A list with
  473. # -"unspliced": matrix unspliced RNA values.
  474. # -"spliced" : matrix spliced RNA values.
  475. # -"typeCell" : vector with the group label associated to each cell.
  476. # -------------------------------------------------------------------------
  477. loadRealData <- function(typeProcessing, pathData, names = FALSE, sparse = FALSE){
  478. if(is.null(pathData)){
  479. error("Provide the directory where the real data are stored")
  480. }
  481. pathData <- paste0(pathData, "/", typeProcessing)
  482. if(typeProcessing == "moments"){ # import pre-processed continuous data
  483. unspliced <- loadMu(pathData, names, sparse)
  484. spliced <- loadMs(pathData, names, sparse)
  485. }else{ # import raw counts
  486. unspliced <- loadUnspliced(pathData, names, sparse)
  487. spliced <- loadSpliced(pathData, names, sparse)
  488. }
  489. # import the type of cells
  490. typeCell <- loadObs(pathData)$clusters
  491. res <- list("unspliced" = unspliced, "spliced" = spliced, "typeCell" = typeCell)
  492. return(res)
  493. }
  494. # -------------------------------------------------------------------------
  495. # Function: loadVar
  496. # Purpose:
  497. # Load the var.csv file, containing the name of the genes for the real data.
  498. #
  499. # Parameters:
  500. # pathData: path where the file (named as "var.csv") is stored.
  501. #
  502. # Returns: a matrix with the content of the var file.
  503. # -------------------------------------------------------------------------
  504. loadVar <- function(pathData){
  505. var <- read.csv(file = paste(pathData, '/var.csv', sep =""))
  506. return(var)
  507. }
  508. # -------------------------------------------------------------------------
  509. # Function: loadObs
  510. # Purpose:
  511. # Load the obs.csv file, containing the name of the cells and the type of cells'labels for the real data.
  512. #
  513. # Parameters:
  514. # pathData: path where the file (named as "obs.csv") is stored.
  515. #
  516. # Returns: a matrix with the content of the obs file.
  517. # -------------------------------------------------------------------------
  518. loadObs <- function(pathData){
  519. obs <- read.csv(file = paste(pathData, '/obs.csv', sep = ""))
  520. return(obs)
  521. }
  522. # -------------------------------------------------------------------------
  523. # Function: loadUnspliced
  524. # Purpose:
  525. # Load the matrix with unspliced RNA counts.
  526. #
  527. # Parameters:
  528. # pathData: path where the file with unspliced counts is stored.
  529. # names: boolean if wee want to store the name of cells and genes. By default it is FALSE.
  530. # sparse: boolean if we want to transform the loaded matrix in sparse object or not. By default it is FALSE.
  531. # Returns: a matrix with the content of the unspliced data.
  532. # -------------------------------------------------------------------------
  533. loadUnspliced <- function(pathData, names = FALSE, sparse = FALSE){
  534. # import unspliced raw data and transform them into a sparse matrix, if required
  535. unspliced <- read.csv(file = paste(pathData, '/unspliced.csv', sep = ""))
  536. if(names){
  537. nameG <- nameGenes(pathData)
  538. nameC <- nameCells(pathData)
  539. colnames(unspliced) <- nameG
  540. rownames(unspliced) <- nameC
  541. }
  542. unspliced <- as(unspliced, "matrix")
  543. if(sparse){
  544. unspliced <- as(unspliced, "dgCMatrix")
  545. }
  546. return(unspliced)
  547. }
  548. # -------------------------------------------------------------------------
  549. # Function: loadSpliced
  550. # Purpose:
  551. # Load the matrix with spliced RNA counts.
  552. #
  553. # Parameters:
  554. # pathData: path where the file with spliced counts is stored.
  555. # names: boolean if wee want to store the name of cells and genes. By default it is FALSE.
  556. # sparse: boolean if we want to transform the loaded matrix in sparse object or not. By default it is FALSE.
  557. # Returns: a matrix with the content of the spliced data.
  558. # -------------------------------------------------------------------------
  559. loadSpliced <- function(pathData, names = FALSE, sparse = FALSE){
  560. # import spliced raw data and transform them into a sparse matrix, if required
  561. spliced <- read.csv(file = paste(pathData, '/spliced.csv', sep = ""))
  562. if(names){
  563. nameG <- nameGenes(pathData)
  564. nameC <- nameCells(pathData)
  565. colnames(unspliced) <- nameG
  566. rownames(unspliced) <- nameC
  567. }
  568. spliced <- as(spliced, "matrix")
  569. if(sparse){
  570. spliced <- as(spliced, "dgCMatrix")
  571. }
  572. return(spliced)
  573. }
  574. # -------------------------------------------------------------------------
  575. # Function: loadMu
  576. # Purpose:
  577. # Load the matrix with unspliced RNA moments (previously computed by scVelo)
  578. #
  579. # Parameters:
  580. # pathData: path where the file with unspliced moments is stored.
  581. # names: boolean if wee want to store the name of cells and genes. By default it is FALSE.
  582. # sparse: boolean if we want to transform the loaded matrix in sparse object or not. By default it is FALSE.
  583. # Returns: a matrix with the the unspliced moments.
  584. # -------------------------------------------------------------------------
  585. loadMu <- function(pathData, names = FALSE, sparse = FALSE){
  586. # import unspliced pre-processed moments and transform them into a sparse matrix, if required
  587. Mu <- read.csv(file = paste(pathData, '/Mu.csv', sep = ""))
  588. if(names){
  589. nameG <- nameGenes(pathData)
  590. nameC <- nameCells(pathData)
  591. colnames(Mu) <- nameG
  592. rownames(Mu) <- nameC
  593. }
  594. Mu <- as(Mu, "matrix")
  595. if(sparse){
  596. Mu <- as(Mu, "dgCMatrix")
  597. }
  598. return(Mu)
  599. }
  600. # -------------------------------------------------------------------------
  601. # Function: loadMs
  602. # Purpose:
  603. # Load the matrix with spliced RNA moments (previously computed by scVelo)
  604. #
  605. # Parameters:
  606. # pathData: path where the file with spliced moments is stored.
  607. # names: boolean if wee want to store the name of cells and genes. By default it is FALSE.
  608. # sparse: boolean if we want to transform the loaded matrix in sparse object or not. By default it is FALSE.
  609. # Returns: a matrix with the the spliced moments.
  610. # -------------------------------------------------------------------------
  611. loadMs <- function(pathData, names = FALSE, sparse = FALSE){
  612. # import spliced pre-processed moments and transform them into a spare matrix, if required
  613. Ms <- read.csv(file = paste(pathData, '/Ms.csv', sep = ""))
  614. if(names){
  615. nameG <- nameGenes(pathData)
  616. nameC <- nameCells(pathData)
  617. colnames(Ms) <- nameG
  618. rownames(Ms) <- nameC
  619. }
  620. Ms <- as(Ms, "matrix")
  621. if(sparse){
  622. Ms <- as(Ms, "dgCMatrix")
  623. }
  624. return(Ms)
  625. }
  626. # -------------------------------------------------------------------------
  627. # Function: nameReal_Pancreas
  628. # Purpose:
  629. # Convert the name of thee simulations into the names used in Table 5 of bayVel's paper
  630. #
  631. # Parameters:
  632. # name: name of the considered simulation
  633. # Returns: string with the corresponding name used in Table 5.
  634. # -------------------------------------------------------------------------
  635. nameReal_Pancreas <- function(name){
  636. label <- ""
  637. if(grepl("SW1", name)){
  638. label <- paste0(label,"K = 1,$")
  639. }else{
  640. label <- paste0(label,"K = 8,$")
  641. }
  642. if(grepl("T1", name)){
  643. label <- paste(label,"$R = 1$", sep = "-")
  644. }else if(grepl("T2", name)){
  645. label <- paste(label,"$R = 9$", sep = "-")
  646. }else if(grepl("T3", name)){
  647. label <- paste(label,"$R = 38$", sep = "-")
  648. }
  649. return(label)
  650. }
  651. # ------------- PLOT THE DATA ----------------
  652. # -------------------------------------------------------------------------
  653. # Function: plot_sVSu
  654. # Purpose:
  655. # Plot the phase trajectory (s vs u) for a single gene using the
  656. # analytical solutions of the ODE model (functions `u()` and `s()`).
  657. # The function draws both the dynamic when the switch occurs and the
  658. # potential dynamic when the switch does not occur. The function admit to
  659. # overlay observed points for cell types.
  660. #
  661. # Arguments:
  662. # t0_off : numeric scalar or Inf, time of the OFF switch (if Inf,
  663. # the system stays in the ON steady-state before switching).
  664. # t0_on : numeric scalar (default 0), time when ON state starts.
  665. # alpha : transcription-rate vector.
  666. # beta : splicing rate, equal to 1 by default.
  667. # gamma : degradation rate.
  668. # pos_u : vector of u positions (one per cell/subgroup). Omit if you want just the model dynamic.
  669. # pos_s : vector of s positions (one per cell/subgroup). Omit if you want just the model dynamic.
  670. # subGrLabels : vector of subgroup-labels for each cell.
  671. # g : gene identifier (used in the plot title).
  672. # add : boolean; if FALSE create a new ggplot, if TRUE add to `gg`. By default it is equal to FALSE.
  673. # gg : an existing ggplot object to add layers to when add = TRUE.
  674. # colCell : colors for subgroup points (vector or NA to auto-pick).
  675. # colDyn : color for the model dynamic (optional, default "red").
  676. # xlim, ylim : numeric scalars for axis limits (optional, if NA they are not set).
  677. # axisTitle.size, axisText.size, title.size : sizes for theme elements.
  678. # lineSize : line width for model dynamic.
  679. # shapePoint : shape of subgroup points (optional).
  680. # sizePoint : size of subgroup points (optional).
  681. #
  682. # Returns:
  683. # A ggplot object with the s vs u phase plot for the given gene.
  684. # -------------------------------------------------------------------------
  685. plot_sVSu <- function(
  686. t0_off,
  687. t0_on = 0,
  688. alpha,
  689. beta = 1,
  690. gamma,
  691. pos_u,
  692. pos_s,
  693. subGrLabels,
  694. g,
  695. add = FALSE,
  696. gg = NA,
  697. colCell = NA,
  698. colDyn = "red",
  699. xlim = NA,
  700. ylim = NA,
  701. axisTitle.size = NA,
  702. axisText.size = NA,
  703. title.size = NA,
  704. lineSize = 1,
  705. shapePoint = 21,
  706. sizePoint = 2,
  707. ...
  708. ){
  709. # Compute the initial points of the on and of the off phase
  710. u0_off <- u0(k = 0, alpha = alpha, beta = beta)
  711. u0_on <- u0(k = 2, alpha = alpha, beta = beta)
  712. s0_off <- s0(k = 0, alpha = alpha, beta = beta, gamma = gamma)
  713. s0_on <- s0(k = 2, alpha = alpha, beta = beta, gamma = gamma)
  714. # Compute the points lying on the on branch of the dynamic. We assume here the switching point is infinite, in order to plot the potential behavior up to the upper steady state.
  715. t_seq <- seq(t0_on, 500, 0.1)
  716. u_plot <- u(t_seq, t0_off = Inf, t0_on = t0_on, u0_off = u0_off, u0_on = u0_on, k = rep(2, length(t_seq)), alpha, beta)
  717. s_plot <- s(t_seq, t0_off = Inf, t0_on = t0_on, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = rep(2, length(t_seq)), alpha, beta, gamma)
  718. df_on <- data.frame(s_plot = s_plot, u_plot = u_plot)
  719. # Title for the plot
  720. main <- paste("Gene", g)
  721. if(length(unique(subGrLabels)) == 1){
  722. main <- paste(main, ", typeCell", unique(subGrLabels)) # we are plotting just the position of one subgroup
  723. }
  724. # Initialize or extend ggplot with the ON branch (dashed line)
  725. if(!add){
  726. gg <- ggplot(data = df_on, aes(x = s_plot, y = u_plot)) +
  727. geom_path(lty = 2, col = colDyn, linewidth = lineSize) +
  728. xlab("s") +
  729. ylab("u") +
  730. labs(title = main)
  731. }else{
  732. gg <- gg +
  733. geom_path(data = df_on, aes(x = s_plot, y = u_plot), lty = 2, col = colDyn, linewidth = lineSize)
  734. }
  735. # adjust graphical parameters
  736. if(is.numeric(axisText.size)){
  737. gg <- gg + theme(axis.text = element_text(size = axisText.size))
  738. }
  739. if(is.numeric(axisTitle.size)){
  740. gg <- gg + theme(axis.title = element_text(size = axisTitle.size))
  741. }
  742. if(is.numeric(title.size)){
  743. gg <- gg + theme(plot.title = element_text(hjust = 0.5, size = title.size))
  744. }
  745. if(is.numeric(xlim)){
  746. gg <- gg + xlim(0, xlim)
  747. }
  748. if(is.numeric(ylim)){
  749. gg <- gg + ylim(0, ylim)
  750. }
  751. # Compute the points lying on the off branch of the dynamic, describing the potential behavior from to the upper steady state.
  752. u_plot <- u(t_seq, t0_off = 0, t0_on = t0_on, u0_off = u0_off, u0_on = u0_on, k = rep(0, length(t_seq)), alpha, beta)
  753. s_plot <- s(t_seq, t0_off = 0, t0_on = t0_on, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = rep(0, length(t_seq)), alpha, beta, gamma)
  754. df_off <- data.frame(s_plot = s_plot, u_plot = u_plot)
  755. gg <- gg + geom_path(data = df_off, aes(x = s_plot, y = u_plot), lty = 2, col = colDyn, linewidth = lineSize)
  756. # If a finite t0_off is provided, compute the transient branches:
  757. # - ON branch from t0_on to t0_off (solid line)
  758. # - OFF branch from t0_off to t0_off + 500 (solid line)
  759. # Else (t0_off == Inf) we reuse the previously computed branch(s).
  760. if(t0_off != Inf){
  761. # compute initial conditions at the switching times
  762. u0_off <- u0(t0_off = t0_off, k = 0, alpha = alpha, beta = beta)
  763. u0_on <- u0(t0_on = t0_on, k = 2, alpha = alpha, beta = beta)
  764. s0_off <- s0(t0_off = t0_off, k = 0, alpha = alpha, beta = beta, gamma = gamma)
  765. s0_on <- s0(t0_on = t0_on, k = 2, alpha = alpha, beta = beta, gamma = gamma)
  766. # ON transient: from t0_on up to t0_off (solid line)
  767. t_on_trans <- seq(t0_on, t0_off, 0.1)
  768. u_plot <- u(t_on_trans, t0_off = t0_off, t0_on = t0_on, u0_off = u0_off, u0_on = u0_on, k = rep(2, length(t_on_trans)), alpha, beta)
  769. s_plot <- s(t_on_trans, t0_off = t0_off, t0_on = t0_on, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = rep(2, length(t_on_trans)), alpha, beta, gamma)
  770. df_on_trans <- data.frame(s_plot = s_plot, u_plot = u_plot)
  771. gg <- gg +
  772. geom_path(data = df_on_trans, aes(x = s_plot, y = u_plot), lty = 1, col = colDyn, linewidth = lineSize)
  773. # OFF transient: from t0_off onward (solid line)
  774. t_off_trans <- seq(t0_off, t0_off + 500, 0.1)
  775. u_plot <- u(t_off_trans, t0_off = t0_off, t0_on = t0_on, u0_off = u0_off, u0_on = u0_on, k = rep(0, length(t_off_trans)), alpha, beta)
  776. s_plot <- s(t_off_trans, t0_off = t0_off, t0_on = t0_on, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = rep(0, length(t_off_trans)), alpha, beta, gamma)
  777. df_off_trans <- data.frame(s_plot = s_plot, u_plot = u_plot)
  778. gg <- gg +
  779. geom_path(data = df_off_trans, aes(x = s_plot, y = u_plot), lty = 1, col = colDyn, linewidth = lineSize)
  780. }else{
  781. # If infinite off-time: overlay the earlier computed branches.
  782. gg <- gg +
  783. geom_path(data = df_on, aes(x = s_plot, y = u_plot), lty = 1, col = colDyn, linewidth = lineSize) +
  784. geom_path(data = df_off, aes(x = s_plot, y = u_plot), lty = 1, col = colDyn, linewidth = lineSize)
  785. }
  786. # If pos_* vectors are provided (no NAs) add points (pos_s, pos_u) for each subgroup
  787. # Colors are auto-chosen if colCell is NA.
  788. subGrC <- unique(subGrLabels)
  789. if(sum(is.na(pos_s)) == 0){
  790. # If lengths already match unique types, use directly; otherwise pick
  791. # the first observed position for each subtype.
  792. if(length(pos_s) == length(subGrC)){
  793. df <- data.frame(s = pos_s, u = pos_u)
  794. }else{
  795. tmpS <- c()
  796. tmpU <- c()
  797. for(i in 1:length(subGrC)){
  798. x <- subGrC[i]
  799. tmpS <- c(tmpS, pos_s[which(subGrLabels == x)[1]])
  800. tmpU <- c(tmpU, pos_u[which(subGrLabels == x)[1]])
  801. }
  802. df <- data.frame(s = tmpS, u = tmpU)
  803. }
  804. # Choose palette if colCell not provided
  805. if(sum(is.na(colCell)) == 1){
  806. if(length(subGrC) >= 3){
  807. colCell <- brewer.pal(length(subGrC), "Spectral")
  808. }else if(length(subGrC) == 2){
  809. colCell <- c("darkgreen", "blue")
  810. }else if(length(subGrC) == 1){
  811. colCell <- c("darkgreen")
  812. }
  813. }
  814. df$colCell <- colCell
  815. # Add points to the plot
  816. gg <- gg + geom_point(data = df, aes(x = s, y = u, fill = colCell), fill = colCell, col = colDyn, size = sizePoint, shape = shapePoint)
  817. }
  818. return(gg)
  819. }
  820. # -------------------------------------------------------------------------
  821. # Function: plot_sVSu_scVelo
  822. # Purpose:
  823. # Plot the scVelo-style s vs u phase trajectory for a single gene using
  824. # analytical ODE solutions (functions u() and s()). It takes as input
  825. # the output of scVelo's inference. The function draws:
  826. # - ON and OFF branches (dashed)
  827. # - transient branches (solid) if fit_t0_off is finite
  828. # - cell positions computed from fit_t (scaled/translated to model fit)
  829. #
  830. # Notes:
  831. # - scVelo uses beta_scaled = fit_beta * fit_scaling; alpha_off is assumed 0 and
  832. # alpha_on = fit_alpha
  833. # - Trajectories are rescaled and translated with fit_scaling, fit_u0_offset,
  834. # fit_s0_offset to match scVelo plotting conventions.
  835. #
  836. # Arguments:
  837. # fit_t0_off : numeric scalar or Inf, fitted switch time to OFF state.
  838. # fit_t0_on : numeric scalar (default 0), fitted ON start time.
  839. # fit_alpha : on transcription rate.
  840. # fit_beta : splicing rate.
  841. # fit_gamma : degradation rate-
  842. # fit_scaling : scalar used to scale u (beta is multiplied by this).
  843. # fit_u0_offset : scalar added to u after scaling (translation).
  844. # fit_s0_offset : scalar added to s (translation).
  845. # fit_t : numeric vector of per-cell times used to place cells on the curve.
  846. # subGrLabels : vector of group labels for each element of fit_t (used for colors).
  847. # g : gene identifier used in the plot title.
  848. # add : boolean; if FALSE create new ggplot, if TRUE add to gg.
  849. # gg : existing ggplot object to add layers to (when add = TRUE).
  850. # colCell : color for points (vector or NA to auto-pick).
  851. # colDyn : color for the model dynamic and points border (optional, default "red").
  852. # xlim, ylim : numeric scalars for axis limits (optional, if NA they are not set).
  853. # axisTitle.size, axisText.size, title.size : sizes for theme elements.
  854. # lineSize : line width for model dynamic.
  855. # shapePoint : shape of points (optional).
  856. # sizePoint : size of points (optional).
  857. #
  858. # Returns:
  859. # A ggplot object with the s vs u phase plot for the given gene, accordingg to scVelo plots.
  860. # -------------------------------------------------------------------------
  861. plot_sVSu_scVelo <- function(
  862. fit_t0_off,
  863. fit_t0_on = 0,
  864. fit_alpha,
  865. fit_beta,
  866. fit_gamma,
  867. fit_scaling,
  868. fit_u0_offset,
  869. fit_s0_offset,
  870. fit_t,
  871. subGrLabels,
  872. g,
  873. add = FALSE,
  874. gg = NA,
  875. colCell = NA,
  876. colDyn = "red",
  877. xlim = NA,
  878. ylim = NA,
  879. axisTitle.size = NA,
  880. axisText.size = NA,
  881. title.size = NA,
  882. lineSize = 1,
  883. shapePoint = 21,
  884. sizePoint = 1,
  885. ...
  886. ){
  887. # Prepare kinetic parameters according to scVelo convention
  888. # scVelo uses beta_scaled = fit_beta * fit_scaling
  889. # alpha is c(0, fit_alpha) so that alpha[1] = 0 (off), alpha[2] = fit_alpha (on)
  890. beta <- fit_beta * fit_scaling
  891. alpha <- c(0, fit_alpha)
  892. # Compute the initial points of the on and of the off phase
  893. u0_off <- u0(k = 0, alpha = alpha, beta = beta)
  894. u0_on <- u0(k = 2, alpha = alpha, beta = beta)
  895. s0_off <- s0(k = 0, alpha = alpha, beta = beta, gamma = fit_gamma)
  896. s0_on <- s0(k = 2, alpha = alpha, beta = beta, gamma = fit_gamma)
  897. # Compute the points lying on the on branch of the dynamic. We assume here the switching point is infinite, in order to plot the potential behavior up to the upper steady state.
  898. t_on_seq <- seq(fit_t0_on, 500, 0.1)
  899. u_plot <- u(t_on_seq, t0_off = Inf, t0_on = 0, u0_off = u0_off, u0_on = 0, k = rep(2, length(t_on_seq)), alpha, beta)
  900. s_plot <- s(t_on_seq, t0_off = Inf, t0_on = 0, u0_off = u0_off, u0_on = 0, s0_off = s0_off, s0_on = 0, k = rep(2, length(t_on_seq)), alpha, beta, fit_gamma)
  901. # Apply scaling and translation to match scVelo plotting
  902. u_plot <- u_plot * fit_scaling + fit_u0_offset
  903. s_plot <- s_plot + fit_s0_offset
  904. df_on <- data.frame(s_plot = s_plot, u_plot = u_plot)
  905. # Title for the plot
  906. main <- paste("Gene", g)
  907. if(length(unique(subGrLabels))== 1){
  908. main <- paste(main, ", typeCell", unique(subGrLabels))
  909. }
  910. # Initialize or extend ggplot with the ON branch (dashed line)
  911. if(!add){
  912. gg <- ggplot(data = df_on, aes(x = s_plot, y = u_plot)) +
  913. geom_path(lty = 2, col = colDyn, linewidth = lineSize) +
  914. xlab("s") +
  915. ylab("u") +
  916. labs(title = main)
  917. # adjust graphical parameters
  918. if(is.numeric(axisText.size)){
  919. gg <- gg + theme(axis.text = element_text(size = axisText.size))
  920. }
  921. if(is.numeric(axisTitle.size)){
  922. gg <- gg + theme(axis.title = element_text(size = axisTitle.size))
  923. }
  924. if(is.numeric(title.size)){
  925. gg <- gg + theme(plot.title = element_text(hjust = 0.5, size = title.size))
  926. }
  927. if(is.numeric(xlim)){
  928. gg <- gg + xlim(0, xlim)
  929. }
  930. if(is.numeric(ylim)){
  931. gg <- gg + ylim(0, ylim)
  932. }
  933. }else{
  934. gg <- gg +
  935. geom_path(data = df_on, aes(x = s_plot, y = u_plot), lty = 2, col = colDyn, linewidth = lineSize)
  936. }
  937. # Compute the points lying on the off branch of the dynamic, describing the potential behavior from to the upper steady state.
  938. t_off_seq <- seq(0, 5000, 0.05)
  939. u_plot <- u(t_off_seq, t0_off = 0, t0_on = 0, u0_off = u0_off, u0_on = u0_on, k = rep(0, length(t_off_seq)), alpha, beta)
  940. s_plot <- s(t_off_seq, t0_off = 0, t0_on = 0, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = rep(0, length(t_off_seq)), alpha, beta, fit_gamma)
  941. # Apply scaling and translation to match scVelo plotting
  942. u_plot <- u_plot * fit_scaling + fit_u0_offset
  943. s_plot <- s_plot + fit_s0_offset
  944. df_off <- data.frame(s_plot = s_plot, u_plot = u_plot)
  945. gg <- gg + geom_path(data = df_off, aes(x = s_plot, y = u_plot), lty = 2, col = colDyn, linewidth = lineSize)
  946. # If a finite t0_off_real is provided, compute the transient branches:
  947. # - ON branch from fit_t0_on to fit_t0_off (solid line)
  948. # - OFF branch from fit_t0_off to fit_t0_off + 5000 (solid line)
  949. # Else (t0_off_real == Inf) we reuse the previously computed branch(s).
  950. if(fit_t0_off != Inf){
  951. # compute initial conditions at the switching times
  952. u0_off <- u0(t0_off = fit_t0_off, k = 0, alpha = alpha, beta = beta)
  953. u0_on <- u0(t0_on = fit_t0_on, k = 2, alpha = alpha, beta = beta)
  954. s0_off <- s0(t0_off = fit_t0_off, k = 0, alpha = alpha, beta = beta, gamma = fit_gamma)
  955. s0_on <- s0(t0_on = fit_t0_on, k = 2, alpha = alpha, beta = beta, gamma = fit_gamma)
  956. # ON transient: from fit_t0_on up to fit_t0_off (solid line)
  957. t_on_trans <- seq(fit_t0_on, fit_t0_off, 0.01)
  958. u_plot <- u(t_on_trans, t0_off = fit_t0_off, t0_on = fit_t0_on, u0_off = u0_off, u0_on = u0_on, k = rep(2, length(t_on_trans)), alpha, beta)
  959. s_plot <- s(t_on_trans, t0_off = fit_t0_off, t0_on = fit_t0_on, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = rep(2, length(t_on_trans)), alpha, beta, fit_gamma)
  960. # Apply scaling and translation to match scVelo plotting
  961. u_plot <- u_plot * fit_scaling + fit_u0_offset
  962. s_plot <- s_plot + fit_s0_offset
  963. df_on_trans <- data.frame(s_plot = s_plot, u_plot = u_plot)
  964. gg <- gg + geom_path(data = df_on_trans, aes(x = s_plot, y = u_plot), lty = 1, col = colDyn, linewidth = lineSize)
  965. # OFF transient: from fit_t0_off onward (solid line)
  966. t_off_trans <- seq(fit_t0_off, fit_t0_off + 5000, 0.01)
  967. u_plot <- u(t_off_trans, t0_off = fit_t0_off, t0_on = fit_t0_on, u0_off = u0_off, u0_on = u0_on, k = rep(0, length(t_off_trans)), alpha, beta)
  968. s_plot <- s(t_off_trans, t0_off = fit_t0_off, t0_on = fit_t0_on, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = rep(0, length(t_off_trans)), alpha, beta, fit_gamma)
  969. # Apply scaling and translation to match scVelo plotting
  970. u_plot = u_plot * fit_scaling + fit_u0_offset
  971. s_plot = s_plot + fit_s0_offset
  972. df_off_trans <- data.frame(s_plot = s_plot, u_plot = u_plot)
  973. gg <- gg +
  974. geom_path(data = df_off_trans, aes(x = s_plot, y = u_plot), lty = 1, col = colDyn, linewidth = lineSize)
  975. }else{
  976. # If infinite off-time: overlay the earlier computed branches.
  977. gg <- gg +
  978. geom_path(data = df_on, aes(x = s_plot, y = u_plot), lty = 1, col = colDyn, linewidth = lineSize) +
  979. geom_path(data = df, aes(x = s_plot, y = u_plot), lty = 1, col = colDyn, linewidth = lineSize)
  980. }
  981. # If fit_t is provided (no NAs) add points (pos_s, pos_u)
  982. # Colors are auto-chosen if colCell is NA.
  983. n_cells <- length(fit_t)
  984. pos_u <- rep(NA, n_cells)
  985. pos_s <- rep(NA, n_cells)
  986. k <- rep(0, n_cells)
  987. k[which(fit_t < fit_t0_off)] <- 2
  988. pos_u <- u(fit_t, t0_off = fit_t0_off, t0_on = 0, u0_off = u0_off, u0_on = u0_on, k = k, alpha = alpha, beta = beta)
  989. pos_s <- s(fit_t, t0_off = fit_t0_off, t0_on = 0, u0_off = u0_off, u0_on = u0_on, s0_off = s0_off, s0_on = s0_on, k = k, alpha = alpha, beta = beta, gamma = fit_gamma)
  990. # Apply scaling and translation to match scVelo plotting
  991. pos_u <- pos_u * fit_scaling + fit_u0_offset
  992. pos_s <- pos_s + fit_s0_offset
  993. df <- data.frame(s = pos_s, u = pos_u)
  994. # Choose palette if colCell not provided
  995. subGrC <- unique(subGrLabels)
  996. if(sum(is.na(colCell)) == 1){
  997. colCell <- brewer.pal(length(subGrC), "Spectral")
  998. }
  999. color <- c()
  1000. for(i in 1:length(subGrC)){
  1001. x <- subGrC[i]
  1002. color <- c(color, rep(colCell[i], length(which(subGrLabels == x))))
  1003. }
  1004. if(nrow(df) == length(color)){
  1005. df$colCell <- color
  1006. }else{
  1007. df$colCell <- color[1:nrow(df)]
  1008. }
  1009. # Add points to the plot
  1010. gg <- gg + geom_point(data = df, aes(x = s, y = u, fill = colCell), fill = df$colCell, col = colDyn, size = sizePoint, shape = shapePoint)
  1011. return(gg)
  1012. }
  1013. # -------------------------------------------------------------------------
  1014. # Function: plot_GeneDynamic_withNotes
  1015. # Purpose:
  1016. # Draw the s-vs-u phase diagram for a single gene with annotation for Figure 1
  1017. #
  1018. # Arguments:
  1019. # t0_off : numeric scalar; time of OFF switch (use Inf if no switch before SS)
  1020. # u0_off : numeric scalar; u-coordinate of the switching point (u^omega)
  1021. # s0_off : numeric scalar; s-coordinate of the switching point (s^omega)
  1022. # t0_on : numeric scalar; time when ON phase starts (default 0)
  1023. # alpha : numeric vector length 2 with transcription rates: c(alpha_off, alpha_on)
  1024. # beta : numeric scalar for splicing rate
  1025. # gamma : numeric scalar fore degradation rate
  1026. # r : optional label used in title (default NA)
  1027. # g : gene identifier (used in title; may be NA)
  1028. #
  1029. # Returns:
  1030. # A ggplot object (s vs u) with annotations.
  1031. # -------------------------------------------------------------------------
  1032. plot_GeneDynamic_withNotes <- function(
  1033. t0_off,
  1034. u0_off,
  1035. s0_off,
  1036. t0_on = 0,
  1037. alpha,
  1038. beta,
  1039. gamma,
  1040. r = NA,
  1041. g
  1042. ){
  1043. # compute the coordinate of the steady states
  1044. u_SS_off <- alpha[1]/beta
  1045. u_SS_on <- alpha[2]/beta
  1046. s_SS_off <- alpha[1]/gamma
  1047. s_SS_on <- alpha[2]/gamma
  1048. # Compute the points lying on the on branch of the dynamic. We assume here the switching point is infinite, in order to plot the potential behavior up to the upper steady state.
  1049. u_plotON <- seq(u_SS_off, u_SS_on, 0.01)
  1050. s_plotON <- s_di_u(u_plotON, u_SS_on, s_SS_on, u_SS_off, s_SS_off, k = 2, alpha, beta, gamma)
  1051. # Compute the points lying on the off branch of the dynamic, describing the potential behavior from to the upper steady state.
  1052. u_plotOFF <- seq(u_SS_off, u_SS_on, 0.01)
  1053. s_plotOFF <- s_di_u(u_plotOFF, u_SS_on, s_SS_on, u_SS_off, s_SS_off, k = 0, alpha, beta, gamma)
  1054. df <- data.frame(uON_SS = as.vector(u_plotON), sON_SS = as.vector(s_plotON), uOFF_SS = as.vector(u_plotOFF), sOFF_SS = as.vector(s_plotOFF), gene = g)
  1055. # Plot on and off branches
  1056. pl <- ggplot(df) +
  1057. geom_path(aes(x = sON_SS, y = uON_SS, color = "red"), linetype = "dotted", linewidth = 3) +
  1058. geom_path(aes(x = sOFF_SS, y = uOFF_SS, color = "blue"), linetype = "dotted", linewidth = 3)
  1059. # If a finite t0_off_real is provided, compute the transient branches.
  1060. if(t0_off != Inf){
  1061. # ON transient: from the lower steady state to the switching point.
  1062. u_plotON_switch <- seq(u_SS_off, u0_off, 0.001)
  1063. s_plotON_switch <- s_di_u(u_plotON_switch, u0_off, s0_off, u_SS_off, s_SS_off, k = 2, alpha, beta, gamma)
  1064. # OFF transient: from the switching point to the lower steady state
  1065. u_plotOFF_switch <- seq(u_SS_off, u0_off, 0.001)
  1066. s_plotOFF_switch <- s_di_u(u_plotOFF_switch, u0_off, s0_off, u_SS_off, s_SS_off, k = 0, alpha, beta, gamma)
  1067. dfSwitchON <- data.frame(uON_switch = u_plotON_switch, sON_switch = s_plotON_switch)
  1068. dfSwitchOFF <- data.frame(uOFF_switch = u_plotOFF_switch, sOFF_switch = s_plotOFF_switch)
  1069. maxSoff <- which.max(s_plotOFF_switch)
  1070. # add the two switching branches
  1071. pl <- pl +
  1072. geom_path(data = dfSwitchON, aes(x = sON_switch, y = uON_switch, color = "red"), linewidth = 3) +
  1073. geom_path(data = dfSwitchOFF, aes(x = sOFF_switch, y = uOFF_switch, color = "blue"), linewidth = 3)
  1074. }else{
  1075. # If infinite off-time: overlay the earlier computed branches.
  1076. pl <- pl +
  1077. geom_path(aes(x = sON_SS, y = uON_SS, color = "red"), linewidth = 3) +
  1078. geom_path(aes(x = sOFF_SS, y = uOFF_SS, color = "blue"), linewidth = 3)
  1079. }
  1080. # Title for the plot
  1081. if(is.na(g)){
  1082. g <- "g"
  1083. }
  1084. main <- ""
  1085. if(!is.na(r)){
  1086. main <- paste(main, " for group r")
  1087. }
  1088. # Annotations: steady-state and switching point labels
  1089. pl <- pl +
  1090. theme(legend.position = "none") +
  1091. annotate(geom = "text", y = alpha[1]*0.88, x = (alpha[1]/gamma)*0.9, label = TeX("$SS^{off}$", output = "character"), size = 35, parse = TRUE, family = "serif") +
  1092. annotate(geom = "text", y = alpha[2]*1.05, x = (alpha[2]/gamma)*1.02, label = TeX("$SS^{on}$", output = "character"), size = 35, parse = TRUE, family = "serif") +
  1093. annotate(geom = "text", y = u0_off*1.05, x = s0_off*0.92,
  1094. label =TeX(r"($(s^{omega}, u^{omega})$)", output = "character"),
  1095. size = 35, parse = TRUE, family = "serif") +
  1096. annotate(geom = "text", x = min(df$sON_SS) + min(df$sON_SS)*0.45, y = min(df$uON_SS) + (max(df$uON_SS) - min(df$uON_SS))/2, label = "Induction", size = 35, angle = 62, family = "serif") +
  1097. annotate(geom = "text", x = max(df$sON_SS)*0.82, y = min(df$uON_SS) + (max(df$uON_SS) - min(df$uON_SS))/2 -0.1, label = "Repression", size = 35, angle = 56, family = "serif") +
  1098. theme(plot.title = element_text(family = "serif", size=70, hjust = 0.5)) +
  1099. labs(x = "", y = "")
  1100. # add arrows for direction of transient induction branch
  1101. ind_on_trans <- which.min(abs(dfSwitchON$uON_switch - (min(dfSwitchON$uON_switch) + (max(dfSwitchON$uON_switch) - min(dfSwitchON$uON_switch))/2)))
  1102. arr_on_u1 <- dfSwitchON$uON_switch[ind_on_trans]
  1103. arr_on_s1 <- dfSwitchON$sON_switch[which(dfSwitchON$uON_switch == arr_on_u1)[1]]
  1104. arr_on_u2 <- dfSwitchON$uON_switch[ind_on_trans + 1]
  1105. arr_on_s2 <- dfSwitchON$sON_switch[which(dfSwitchON$uON_switch == arr_on_u2)[1]]
  1106. pl <- pl +
  1107. geom_segment(aes(color = "red"), x = arr_on_s1, y = arr_on_u1, xend = arr_on_s2, yend = arr_on_u2, arrow = arrow( length = unit(0.3, "inches")), size = 3)
  1108. # add arrows for direction of transient repressive branch
  1109. ind_off_trans <- which.min(abs(dfSwitchOFF$uOFF_switch - (min(dfSwitchOFF$uOFF_switch) + (max(dfSwitchOFF$uOFF_switch) - min(dfSwitchOFF$uOFF_switch))/2)))
  1110. arr_off_u1 <- dfSwitchOFF$uOFF_switch[ind_off_trans]
  1111. arr_off_s1 <- dfSwitchOFF$sOFF_switch[which(dfSwitchOFF$uOFF_switch == arr_off_u1)[1]]
  1112. arr_off_u2 <- dfSwitchOFF$uOFF_switch[ind_off_trans + 1]
  1113. arr_off_s2 <- dfSwitchOFF$sOFF_switch[which(dfSwitchOFF$uOFF_switch == arr_off_u2)[1]]
  1114. pl <- pl + geom_segment(aes(color = "blue"), x = arr_off_s2, y = arr_off_u2, xend = arr_off_s1, yend = arr_off_u1, arrow = arrow( length = unit(0.3, "inches")), size = 3)
  1115. # add arrows for direction of not-transient repressive branch
  1116. ind_off <- which.min(abs(df$uOFF_SS - (min(df$uOFF_SS) + (max(df$uOFF_SS) - min(df$uOFF_SS))/2)))
  1117. arr_off_u1 <- df$uOFF_SS[ind_off]
  1118. arr_off_s1 <- df$sOFF_SS[which(df$uOFF_SS == arr_off_u1)[1]]
  1119. arr_off_u2 <- df$uOFF_SS[ind_off + 5]
  1120. arr_off_s2 <- df$sOFF[which(df$uOFF_SS == arr_off_u2)[1]]
  1121. pl <- pl + geom_segment(aes(colour = "blue"), x = arr_off_s2, y = arr_off_u2, xend = arr_off_s1, yend = arr_off_u1, arrow = arrow(length = unit(0.3, "inches")), size = 3)
  1122. pl <- pl +
  1123. coord_cartesian(xlim = c(min(rbind(df$sON_SS, df$sOFF_SS)) - 0.5, max(rbind(df$sON_SS, df$sOFF_SS)) + 0.5), ylim = c(min(rbind(df$uON_SS, df$uOFF_SS)) - 0.2, max(rbind(df$uON_SS, df$uOFF_SS)) + 0.2)) + theme(axis.ticks.y = element_blank(), axis.ticks.x = element_blank(), axis.text.y = element_blank(), axis.text.x = element_blank())
  1124. # draw cartesian axes on the side
  1125. pl <- pl + geom_segment(aes(x=3.7, y=0.85, xend=4.5, yend=0.85), arrow = arrow(length=unit(.5, 'cm')), color='black', linewidth=2) +
  1126. geom_segment(aes(x=3.7, y=0.846, xend=3.7, yend=1.35), arrow = arrow(length=unit(.5, 'cm')), color='black', linewidth=2) +
  1127. annotate(geom = "text", y = 0.78, x = 4.1, label = TeX("$s$", output = "character"), size = 30, parse = TRUE, family = "serif") +
  1128. annotate(geom = "text", y = 1.046, x = 3.55, label = TeX("$u$", output = "character"), size = 30, parse = TRUE, family = "serif")
  1129. return(pl)
  1130. }

functions.R at commit 73b6f0b, no license · at the source

Overview

Authors: Jiachen Li1, Zhe Wang1, Hong-Bin Shen2, Ye Yuan1,2
ORCID iDs: Jiachen Li, Ye Yuan
  1. State Key Laboratory of Biopharmaceutical Preparation and Delivery, Institute of Process Engineering, Chinese Academy of Sciences Beijing China
  2. Institute of Image Processing and Pattern Recognition, Shanghai Jiao Tong University, and Key Laboratory of System Control and Information Processing, Ministry of Education of China Shanghai China
Journal: eLife, volume 14, article RP108950
Dates: published online 15 September 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.7554/elife.108950 · PMID 42742132 · PMCID PMC13577664 · OpenAlex W4416668627
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), mouse (organism), cellular / molecular (subfield)
Methods: Smoothing, state filtering, decompositions, Machine learning, Connectivity
Keywords: RNA velocity, cell trajectory, scRNA-seq analysis, Mouse
MeSH: Gene Expression Regulation*, RNA*, RNA Splicing*, Sequence Analysis, RNA*, Single-Cell Analysis*, Transcription, Genetic*, Animals, Mice, Single-Cell Gene Expression Analysis (* major topic)
Journal subjects: Computational and Systems Biology
Topic: Single-cell and spatial transcriptomics (Molecular Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: National Key Research and Development Program of China (2023YFF1204500); National Natural Science Foundation of China (62503452)
Citations: not cited yet (Europe PMC); 64 references in the paper

Abstract

RNA velocity approaches fit gene dynamics and infer cell fate by modeling the splicing process using single-cell RNA sequencing (scRNA-seq) data. However, due to the short time scale of splicing, high noise, and large complexity of data, existing RNA velocity methods often fail to precisely capture the complex velocity dynamics for individual genes and single cells, which makes their downstream analysis less reliable and less robust. We propose TSvelo, a comprehensive RNA velocity mathematics framework that can model the cascade of gene regulation, Transcription and Splicing using highly interpretable neural ordinary differential equations. TSvelo can precisely capture the transcription–unspliced–spliced 3D dynamics of all genes simultaneously, infer unified latent time shared by genes within a single cell, and be applied to multi-lineage datasets. Experiments on six scRNA-seq datasets, including two multi-lineage datasets, demonstrate TSvelo’s superiority.

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

Repositories

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

lijc0804/TSvelo

License: MIT
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 6e0ab8264e02aa511576278219fd7c1c65efec80, 8 September 2026
Languages: Python (7), Jupyter (2)
Size: 15 files, 9 scripts
Software Heritage: not archived
Found in: “Code availability statement”
Holds: README, license file, 2 notebooks
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: anndata (9 files), Matplotlib (9 files), NumPy (9 files), pandas (9 files), Scanpy (9 files), scVelo (4 files), SciPy (3 files), NetworkX (1 file), PyTorch (1 file), seaborn (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
11 files

elenasabbioni/bayvel_notebooks

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 73b6f0b05eaeddad1d155d1b1dce7b8f1aa88a4f, 26 October 2025
Languages: R (21), Jupyter (3), Julia (1)
Size: 144 files, 25 scripts
Software Heritage: not archived
Found in: “Data availability”
Holds: README, environment (Manifest.toml, Project.toml), 3 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: ggplot2 (9 files), data.table (5 files), NumPy (3 files), pandas (3 files), Scanpy (3 files), SciPy (3 files), scVelo (3 files), anndata (1 file), cowplot (1 file), Distributions.jl (1 file), Matplotlib (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
26 files

Code availability statement

TSvelo is implemented in Python. The source code can be downloaded from the GitHub repository, https://github.com/lijc0804/TSvelo (copy archived at Li, 2026).

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

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:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 34 scripts, each with its path and the digest of its content;
  • 8 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

Data availability

The pancreatic endocrinogenesis dataset comprises the single-cell RNA-seq (10X) data of pancreatic epithelial and Ngn3-Venus fusion cells sampled from mouse embryonic day 15.5, which could be loaded using scVelo's package scvelo.datasets.pancreas(). The gastrulation erythroid dataset, which is selected from the transcriptional profiles of mouse embryos39, which could be loaded using scVelo's package scvelo.datasets.pancreas(). 10x embryonic mouse brain dataset is provided at the 10x website at https://www.10xgenomics.com/resources/datasets/fresh-embryonic-e-18-mouse-brain-5-k-1-standard-1-0-0. The data preprocessed by Multivelo is utilized in this study, (https://multivelo.readthedocs.io/en/latest/MultiVelo_Fig2.html). The dentate gyrus neurogenesis data is available at http://pklab.med.harvard.edu/velocyto/DentateGyrus/DentateGyrus.loom. The LARRY dataset has been shared by pyrovelocity, which could be accessed at https://figshare.com/articles/dataset/larry_invitro_adata_sub_raw_h5ad/20780344. The raw data of Hindbrain (pons) of adolescent mice is from https://pklab.med.harvard.edu/ruslan/velocity/oligos/. The ENCODE TF-target database website: https://maayanlab.cloud/Harmonizome/dataset/ENCODE+Transcription+Factor+Targets. The ChEA TF–target database website: https://maayanlab.cloud/Harmonizome/dataset/CHEA+Transcription+Factor+Targets. The results of BayVel on the pancreas dataset are downloaded from its GitHub page at https://github.com/elenasabbioni/BayVel_notebooks/tree/main/real%20data/Pancreas/moments/output (https://github.com/elenasabbioni/BayVel_notebooks/tree/main/real data/Pancreas/moments/output) (Sabbioni, 2025).

The following previously published datasets were used:

QinQ 2022larry_invitro_adata_sub_raw.h5adfigshare10.6084/m9.figshare.20780344

TritschlerS 2019Comprehensive single cell mRNA profiling reveals a detailed roadmap for pancreatic endocrinogenesisNCBI Gene Expression OmnibusGSE13218810.1242/dev.17384931160421

Pijuan-SalaB GriffithsJ 2018Timecourse single-cell RNAseq of whole mouse embryos harvested between days 6.5 and 8.5 of developmentArrayExpressE-MTAB-6967

10x Genomics 2020Fresh Embryonic E18 Mouse Brain (5k)10x Genomicsfresh-embryonic-e-18-mouse-brain-5-k-1-standard-1-0-0

LinnarssonS 2016RNA-seq analysis of single cells of the oligodendrocyte lineage from nine distinct regions of the anterior-posterior and dorsal-ventral axis of the mouse juvenile central nervous systemNCBI Gene Expression OmnibusGSE75330

WeinrebC Rodriguez-FraticelliA CamargoF KleinAM 2019Lineage tracing on transcriptional landscapes links state to fate during differentiationNCBI Gene Expression OmnibusGSE14080210.1126/science.aaw3381PMC760807431974159

HochgernerH ZeiselA LönnerbergP LinnarssonS 2017Transcriptome analysis of single cells from the mouse dentate gyrusNCBI Gene Expression OmnibusGSE95753

Reproduced under the paper's license (CC BY), 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, 27 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 4 authors, 4 keywords, 9 MeSH terms, 2 funders, 60 references.

Cite

This paper

Li, J., Wang, Z., Shen, H.-B., & Yuan, Y. (2026). Comprehensive RNA velocity by modeling the cascade of gene regulation, transcription, and splicing from single-cell RNA sequencing data with TSvelo. eLife, 14, RP108950. https://doi.org/10.7554/elife.108950

BibTeX

@article{li2026comprehensive,
author = {Li, Jiachen and Wang, Zhe and Shen, Hong-Bin and Yuan, Ye},
title = {{Comprehensive RNA velocity by modeling the cascade of gene regulation, transcription, and splicing from single-cell RNA sequencing data with TSvelo}},
journal = {eLife},
year = {2026},
month = sep,
volume = {14},
pages = {RP108950},
publisher = {eLife Sciences Publications, Ltd},
issn = {2050-084X},
doi = {10.7554/elife.108950},
url = {https://doi.org/10.7554/elife.108950},
pmid = {42742132},
pmcid = {PMC13577664}
}

RIS

TY - JOUR
AU - Li, Jiachen
AU - Wang, Zhe
AU - Shen, Hong-Bin
AU - Yuan, Ye
TI - Comprehensive RNA velocity by modeling the cascade of gene regulation, transcription, and splicing from single-cell RNA sequencing data with TSvelo
T2 - eLife
J2 - Elife
PY - 2026
DA - 2026/09/15
VL - 14
SP - RP108950
SN - 2050-084X
PB - eLife Sciences Publications, Ltd
DO - 10.7554/elife.108950
UR - https://doi.org/10.7554/elife.108950
LA - en
ER -

CSL-JSON

{
"id": "10.7554/elife.108950",
"type": "article-journal",
"title": "Comprehensive RNA velocity by modeling the cascade of gene regulation, transcription, and splicing from single-cell RNA sequencing data with TSvelo",
"container-title": "eLife",
"author": [
{
"family": "Li",
"given": "Jiachen"
},
{
"family": "Wang",
"given": "Zhe"
},
{
"family": "Shen",
"given": "Hong-Bin"
},
{
"family": "Yuan",
"given": "Ye"
}
],
"container-title-short": "Elife",
"volume": "14",
"page": "RP108950",
"DOI": "10.7554/elife.108950",
"PMID": "42742132",
"PMCID": "PMC13577664",
"ISSN": "2050-084X",
"publisher": "eLife Sciences Publications, Ltd",
"URL": "https://doi.org/10.7554/elife.108950",
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
15
]
]
}
}

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.crmeth.2026.101342 [code]
Interpretable learning of temporal cellular dynamics from single-cell data.
Journal: Cell reports methods
In common: scVelo, anndata, Scanpy, 7 other tools, genetics / omics, cellular / molecular, 13 references
[2] doi:10.1093/bioinformatics/btag652 [code]
mmVelo: a deep generative model for estimating cell state-dependent dynamics across multiple modalities.
Journal: Bioinformatics (Oxford, England)
In common: scVelo, anndata, Scanpy, 6 other tools, genetics / omics, mouse, 10 references
[3] doi:10.1038/s41467-026-74000-4 [code]
ArchVelo: archetypal velocity modeling for single-cell multi-omic trajectories.
Journal: Nature communications
In common: scVelo, anndata, Scanpy, 5 other tools, genetics / omics, mouse, cellular / molecular, 10 references
[4] doi:10.1038/s41586-026-10629-x [code]
Whole-genome duplication shaped cell-type evolution in the vertebrate brain.
Journal: Nature
In common: Distributions.jl, scVelo, anndata, 9 other tools, genetics / omics, mouse, cellular / molecular, 2 references
[5] doi:10.1038/s41586-026-10490-y [code]
Lineage and organ signals sequentially build organ intrinsic nervous systems.
Journal: Nature
In common: scVelo, anndata, Scanpy, 9 other tools, mouse, 3 references
[6] 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: scVelo, anndata, Scanpy, 9 other tools, genetics / omics, cellular / molecular, 2 references
[7] doi:10.7554/elife.106347 [code]
Esr1-dependent signaling and transcriptional maturation in the medial preoptic area of the hypothalamus shape the development of mating behavior during adolescence.
Journal: eLife
In common: anndata, Scanpy, NetworkX, 7 other tools, genetics / omics, mouse, 3 references
[8] doi:10.1038/s41467-026-71803-3 [code]
Charting the transition from in vitro gliogenesis to the in vivo maturation of human glial progenitor cells transplanted into the hypomyelinated mouse brain.
Journal: Nature communications
In common: anndata, Scanpy, cowplot, 8 other tools, genetics / omics, mouse, cellular / molecular, 2 references
[9] doi:10.1371/journal.pcbi.1014346 [code]
StPedf: Cell trajectory inference of spatial transcriptomics via spatial proximity embedding and spatial density-adaptive fusion.
Journal: PLoS computational biology
In common: anndata, Scanpy, NetworkX, 6 other tools, genetics / omics, 4 references
[10] doi:10.1016/j.celrep.2026.117073 [code]
Single-cell epigenomics uncovers heterochromatin instability and transcription factor dysfunction during mouse brain aging.
Journal: Cell reports
In common: anndata, Scanpy, NetworkX, 8 other tools, genetics / omics, mouse, cellular / molecular, 1 reference

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.