Analyse de trajectoire Monocle3 en R : le pseudotemps avec un graphe principal

Apprenez un graphe principal sur vos cellules avec Monocle3, enracinez et ordonnez le pseudotemps avec learn_graph et order_cells, et trouvez les gènes dynamiques avec graph_test — le pendant fondé sur les graphes de Slingshot

Un guide pratique de l’inférence de trajectoire et du pseudotemps avec Monocle3 en R. Construisez un cell_data_set, apprenez un graphe principal directement sur l’UMAP propre de Monocle3, enracinez et ordonnez les cellules avec learn_graph et order_cells, et testez quels gènes changent le long du graphe avec graph_test (Moran’s I). Voyez comment les partitions de Monocle3 séparent honnêtement les cellules déconnectées hors de la trajectoire — le pendant fondé sur les graphes de l’arbre couvrant de poids minimal de Slingshot — sur le jeu de données canonique de cellules souches/progénitrices du sang de souris Nestorowa.

Date de publication

4 juillet 2026

Modifié

7 juillet 2026

AstucePoints clés
  • Monocle3 apprend un graphe, pas un arbre de centres de regroupements. Il ajuste un graphe principal — un squelette lisse de nœuds et d’arêtes — directement à travers les cellules avec le plongement de graphe inversé (reversed graph embedding, RGE), si bien que la trajectoire peut se ramifier, converger ou même former une boucle, et il est tracé sur l’UMAP propre de Monocle3 (uniform manifold approximation and projection) (Cao et al., 2019). C’est le contraste avec Slingshot, qui construit un arbre couvrant de poids minimal sur les centroïdes des regroupements et lisse des courbes principales.
  • Le pipeline est une seule séquence ordonnée de verbes. preprocess_cds() (analyse en composantes principales) → reduce_dimension() (UMAP) → cluster_cells() (regroupements et partitions) → learn_graph() (le graphe principal) → order_cells() (le pseudotemps à partir d’une racine que vous choisissez).
  • Les partitions sont la façon honnête qu’a Monocle3 de dire « ces cellules ne sont pas connectées ». cluster_cells() sépare aussi les cellules en partitions — des groupes qu’il juge être des variétés distinctes — et learn_graph() ajuste un graphe au sein de chacune. Les cellules d’une partition déconnectée reçoivent un pseudotemps infini : Monocle3 refuse d’inventer un chemin là où les données n’en montrent pas. C’est une fonctionnalité, et vous pouvez la contourner avec learn_graph(use_partition = FALSE) quand vous pensez qu’il s’agit vraiment d’un seul continuum.
  • Enracinez le graphe par programmation, pas en cliquant. order_cells() veut un nœud de départ du graphe principal. Le sélecteur interactif convient pour explorer, mais pour un script reproductible passez root_pr_nodes = le nœud le plus proche de votre origine connue — ici les cellules souches à long terme.
  • graph_test() trouve les gènes dynamiques avec Moran’s I. Au lieu d’ajuster une courbe par lignée, Monocle3 teste chaque gène pour l’autocorrélation spatiale sur le graphe (Moran’s I) : les cellules voisines sur la trajectoire partagent-elles une expression similaire ? Les gènes qui le font sont ceux qui changent le long d’elle.

Introduction

Vous avez regroupé un jeu de données de RNA-seq single-cell (scRNA-seq) et vous voyez qu’il ne s’agit pas d’un ensemble d’îlots bien nets — les cellules se rejoignent en une forme connectée et ramifiée. C’est une population en cours de différenciation, et la question n’est plus « quel est le type de chaque cellule ? » mais « jusqu’où en est chaque cellule dans son parcours de développement, et quels gènes l’y conduisent ? » La leçon Slingshot y répond avec un arbre couvrant de poids minimal sur les centroïdes des regroupements et des courbes principales lisses. Cette leçon y répond avec Monocle3, qui emprunte une route différente vers le même but — et les différences valent la peine d’être comprises, car elles changent ce que la trajectoire peut exprimer et à quel point elle est honnête à propos des cellules qui n’y appartiennent pas.

Monocle3 ne résume pas les cellules en centres de regroupements pour ensuite les relier. Il apprend un graphe principal : un réseau de nœuds et d’arêtes ajusté à travers les cellules elles-mêmes avec le plongement de graphe inversé (reversed graph embedding, RGE) — l’idée d’incorporer simultanément les données et un graphe qui court le long de leur ossature, introduite pour les trajectoires single-cell dans Monocle 2 (Qiu et al., 2017) et mise à l’échelle pour de grands jeux de données fondés sur l’UMAP dans Monocle3 (Cao et al., 2019). Un graphe peut se ramifier et converger librement, si bien que Monocle3 gère des topologies complexes qu’un simple arbre ne peut pas gérer. Et il est franc à propos de la structure : si deux groupes de cellules ne sont pas connectés, il le dit et les garde séparés.

Nous travaillons sur le même jeu de données que la leçon Slingshot pour que vous puissiez comparer les deux méthodes sur des cellules identiques : le jeu de données de sang de souris Nestorowa canonique (Nestorowa et al., 2016), 1 920 cellules souches et progénitrices hématopoïétiques (HSPC — les cellules souches et progénitrices qui produisent chaque cellule sanguine). Chaque cellule porte une étiquette FACS (fluorescence-activated cell sorting — le tri par cytométrie de flux qui a trié les cellules selon leurs marqueurs de surface) indiquant à quelle population progénitrice elle appartient. Nous ne fournissons jamais ces étiquettes à Monocle3 ; nous les mettons de côté pour vérifier la trajectoire qu’il infère.

En quoi Monocle3 diffère de Slingshot

Les deux outils ordonnent les cellules le long d’un continuum de différenciation et vous donnent un pseudotemps — un unique nombre par cellule mesurant jusqu’où elle a progressé, bas au début et élevé à la fin engagée. Mais ils construisent la trajectoire de façons véritablement différentes, et cela façonne ce que vous obtenez.

Slingshot Monocle3
Ce qu’il ajuste Un arbre couvrant de poids minimal (MST) sur les centroïdes des regroupements, lissé en courbes principales Un graphe principal appris à travers les cellules avec le plongement de graphe inversé
S’exécute sur Votre embedding (par ex. l’UMAP Seurat) + vos étiquettes de regroupements Son propre UMAP, construit à l’intérieur du cell_data_set
Topologie Un arbre (des ramifications depuis une racine, pas de boucles) Un graphe — peut se ramifier, converger ou former une boucle
Cellules déconnectées Forcées dans l’arbre ; chaque cellule reçoit un pseudotemps Séparées en partitions ; les cellules hors du graphe principal reçoivent un pseudotemps infini
Test des gènes dynamiques tradeSeq — un GAM de l’expression contre le pseudotemps par lignée graph_test() — l’autocorrélation spatiale de Moran’s I sur le graphe

La conclusion pratique : Slingshot est la valeur par défaut propre et robuste quand vous avez déjà un bon embedding et un arbre de ramification clair. Monocle3 est l’outil vers lequel se tourner quand la topologie est plus complexe, ou quand vous voulez que la méthode vous dise quelles cellules forment une trajectoire connectée plutôt que de toutes les y forcer — comme vous êtes sur le point de le voir faire.

Les données : des cellules souches avec des étiquettes progénitrices connues

Le nestorowa_hsc.rds commité est une tranche de l’atlas HSPC de Nestorowa — le même fichier qu’utilise la leçon Slingshot. Il contient une matrice de comptage brute de 2 500 gènes à travers 1 920 cellules, une table de métadonnées, et une correspondance symbol des identifiants Ensembl vers des noms de gènes lisibles. La colonne broad contient la population FACS dans laquelle chaque cellule a été triée — LTHSC (cellule souche hématopoïétique à long terme, LT-HSC — la cellule souche au sommet), MPP (progéniteur multipotent), LMPP (progéniteur multipotent à orientation lymphoïde), CMP (progéniteur myéloïde commun), GMP (progéniteur granulocyte-macrophage — le destin myéloïde), et MEP (progéniteur mégacaryocyte-érythroïde — le destin globule rouge / plaquette), plus un groupe de cellules sans étiquette fine. Ce gradient de la cellule souche au progéniteur engagé est la vérité de terrain de la différenciation face à laquelle nous validons la trajectoire.

Monocle3 ne travaille pas sur un objet Seurat ; il travaille sur un cell_data_set (un CDS — le conteneur de Monocle3 construit sur le SingleCellExperiment de Bioconductor). Construisez-le à partir de trois pièces : la matrice de comptage, une table de métadonnées cellulaires, et une table de métadonnées géniques dont Monocle3 utilise la colonne gene_short_name pour étiqueter les gènes sur les figures.

library(monocle3)

d <- readRDS("_data/nestorowa_hsc.rds")

# Gene metadata: Monocle3 labels plots from a `gene_short_name` column
gene_meta <- data.frame(gene_short_name = d$symbol[rownames(d$counts)],
                        row.names = rownames(d$counts))

cds <- new_cell_data_set(d$counts,
                        cell_metadata = d$meta,
                        gene_metadata = gene_meta)

cds
class: cell_data_set 
dim: 2500 1920 
metadata(1): cds_version
assays(1): counts
rownames(2500): ENSMUSG00000067149 ENSMUSG00000076609 ...
  ENSMUSG00000097577 ENSMUSG00000092841
rowData names(1): gene_short_name
colnames(1920): HSPC_007 HSPC_013 ... Prog_852 Prog_810
colData names(5): gate broad broad.mpp fine Size_Factor
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):
table(colData(cds)$broad, useNA = "ifany")   # the FACS populations: our ground truth

  CMP   GMP  LMPP LTHSC   MEP   MPP  <NA> 
  328   123   280   323   362   368   136 

Cela affiche les dimensions du CDS — 2 500 gènes par 1 920 cellules — et les effectifs par population triée, du sommet LT-HSC jusqu’aux progéniteurs GMP et MEP engagés, plus les cellules sans étiquette.

Note

Les blocs de cette leçon construisent un seul objet Monocle3 étape par étape, donc exécutez-les dans l’ordre au sein d’une seule session — chaque verbe (preprocess_cds, reduce_dimension, …) ajoute au cds que le bloc précédent a créé. Le pipeline complet est un motif fixe que vous réutiliserez verbatim : seuls les arguments changent.

Prétraiter, calculer l’embedding et regrouper

Monocle3 exécute son propre pipeline de réduction de dimension sur le CDS. Trois verbes s’en chargent. preprocess_cds() exécute l’analyse en composantes principales (PCA) — une réduction linéaire qui conserve les principales directions de variation ; nous en gardons 30 composantes. reduce_dimension() construit ensuite un UMAP (la carte non linéaire en 2-D) à partir de ces composantes. cluster_cells() regroupe les cellules — et, fait important, assigne aussi des partitions : des regroupements plus grossiers de cellules qu’il juge se trouver sur des variétés distinctes. Nous fixons une graine avant chaque étape parce que la PCA, l’UMAP et le clustering utilisent tous du hasard, et une trajectoire bâtie sur des regroupements changeants n’est pas reproductible.

set.seed(42); cds <- preprocess_cds(cds, num_dim = 30)          # PCA
set.seed(42); cds <- reduce_dimension(cds, reduction_method = "UMAP")
set.seed(42); cds <- cluster_cells(cds)

length(unique(partitions(cds)))     # how many separate manifolds Monocle3 found
[1] 2
length(unique(clusters(cds)))       # how many clusters
[1] 2
table(partition = partitions(cds))  # cells per partition
partition
   1    2 
1138  782 

Monocle3 sépare ces cellules en deux partitions — 1 138 cellules dans l’une et 782 dans l’autre — et trouve deux regroupements. Lisez cette séparation attentivement : Monocle3 vous dit qu’il ne voit pas ces 1 920 cellules comme une seule variété connectée. Il a décidé que le groupe de 782 cellules est assez distinct pour qu’une trajectoire unique à travers toutes les cellules soit malhonnête. Gardez cette idée en tête — elle pilote tout ce qui suit, et c’est la différence la plus marquée avec Slingshot, qui forçait chaque cellule dans un seul arbre connecté.

Apprendre le graphe principal

Voici maintenant le cœur de Monocle3 : learn_graph() ajuste le graphe principal — le squelette lisse de nœuds (appelés points principaux, nommés Y_1, Y_2, …) et d’arêtes qui court le long de l’ossature des cellules. C’est l’étape de plongement de graphe inversé : le graphe et les cellules sont ajustés ensemble pour que le graphe retrace la trajectoire à travers les données (Cao et al., 2019). Par défaut, learn_graph() ajuste un graphe distinct au sein de chaque partition, si bien que le groupe déconnecté de 782 cellules obtient son propre squelette plutôt que d’être câblé au principal.

# learn_graph() prints a progress bar while it fits the graph; there is no
# result to show (the graph is stored inside the CDS), so we silence the output.
cds <- learn_graph(cds)

Il n’y a pas de sortie affichée — le graphe est stocké à l’intérieur du CDS. Vous le verrez dans un instant, tracé par-dessus les cellules.

Enraciner et ordonner les cellules

Un graphe à lui seul n’a pas de direction. order_cells() le transforme en pseudotemps, mais il a besoin d’une racine : le nœud du graphe principal où le temps commence. Monocle3 offre un sélecteur interactif (order_cells(cds) sans racine ouvre une figure sur laquelle vous cliquez), pratique pour explorer mais non reproductible — un script ne doit pas dépendre d’un clic de souris. À la place, trouvez la racine par programmation : localisez le nœud principal le plus proche des cellules que vous savez être l’origine. Ici, ce sont les cellules souches LT-HSC, lues à partir de l’étiquette FACS broad.

L’utilitaire ci-dessous est le motif issu de la documentation de Monocle3 : pour les cellules d’un type choisi, trouvez le nœud du graphe sur lequel chacune est projetée, et prenez le nœud vers lequel le plus grand nombre d’entre elles pointent.

# The principal node nearest the LT-HSC stem cells = the biological root
get_earliest_root <- function(cds, cell_col, target) {
  cell_ids <- which(colData(cds)[[cell_col]] == target)
  closest <- principal_graph_aux(cds)[["UMAP"]]$pr_graph_cell_proj_closest_vertex
  igraph::V(principal_graph(cds)[["UMAP"]])$name[
    as.numeric(names(which.max(table(closest[cell_ids, ]))))]
}

root_node <- get_earliest_root(cds, "broad", "LTHSC")
root_node
[1] "Y_3"
cds <- order_cells(cds, root_pr_nodes = root_node)

pt <- pseudotime(cds)
range(pt[is.finite(pt)])            # the pseudotime scale (finite cells only)
[1] 0.000000 8.775911
sum(is.finite(pt))                  # how many cells got a pseudotime
[1] 1138

Les cellules souches ancrent la racine au nœud Y_3, et le pseudotemps va de 0 à la racine jusqu’à environ 8.8 à l’extrémité la plus lointaine du graphe. Mais regardez le décompte : seules 1 138 des 1 920 cellules ont un pseudotemps fini. Les 782 autres — exactement la seconde partition — sont à un pseudotemps infini, parce qu’elles se trouvent sur un graphe déconnecté de la racine. Monocle3 n’ordonnera pas des cellules qu’il ne peut pas atteindre depuis le départ. pseudotime() renvoie Inf pour elles à dessein ; en aval vous filtrez sur les cellules finies.

Voir la trajectoire : le graphe coloré par le pseudotemps

plot_cells() est le verbe de tracé tout-en-un de Monocle3. Colorez les cellules par pseudotime et il pose le graphe principal par-dessus l’UMAP avec le gradient de pseudotemps en dessous — sombre à la racine, clair aux extrémités. La partition déconnectée, sans pseudotemps, apparaît en gris.

plot_cells(cds,
          color_cells_by = "pseudotime",
          label_cell_groups = FALSE,
          label_leaves = FALSE,
          label_branch_points = FALSE,
          graph_label_size = 3,
          cell_size = 0.8)

A UMAP of mouse blood stem and progenitor cells with the Monocle3 principal graph drawn over it as a network of black nodes and edges. Cells are coloured by pseudotime on a dark-to-bright scale: the stem-cell region where the graph is rooted is dark (low pseudotime) and the colour brightens outward along the graph toward the committed progenitors. A large group of cells forms a second, disconnected component that stays grey because it is a separate partition with no pseudotime.

Le graphe principal se faufile à travers le corps principal des cellules, enraciné dans la région sombre des cellules souches et s’éclaircissant vers l’extérieur à mesure que les cellules mûrissent — le gradient de différenciation, dessiné comme un graphe plutôt que comme des courbes séparées. Le bloc gris est la seconde partition : Monocle3 lui a construit un graphe à elle mais ne l’a jamais connecté à la racine, si bien que ces cellules ne portent aucun pseudotemps. Cette honnêteté — ces cellules ne sont pas sur cette trajectoire — est exactement ce que les partitions vous apportent.

Valider le graphe face aux étiquettes connues

Une trajectoire qui semble plausible doit encore correspondre à la biologie que vous avez mise de côté. Colorez la même figure par l’étiquette FACS broad : l’extrémité enracinée et à bas pseudotemps du graphe devrait reposer sur les cellules souches, et le graphe devrait s’étendre vers les progéniteurs engagés.

plot_cells(cds,
          color_cells_by = "broad",
          label_cell_groups = FALSE,
          label_leaves = FALSE,
          label_branch_points = FALSE,
          cell_size = 0.8)

The same UMAP and Monocle3 principal graph, now with cells coloured by their FACS sorted population — LT-HSC, MPP, LMPP, CMP, GMP, MEP, and unlabelled. The region where the graph is rooted is dominated by LT-HSC stem cells, and the committed progenitor populations sit toward the ends of the graph, confirming the trajectory runs from stem cell to committed fate. The disconnected partition holds a distinct mix of populations.

L’extrémité racine du graphe repose carrément sur les cellules souches LTHSC, et le graphe s’étend à travers les populations progénitrices vers les destins engagés — Monocle3 a placé les cellules souches au début du pseudotemps sans jamais voir les étiquettes FACS. Cet accord entre la trajectoire inférée et le tri connu est votre preuve qu’elle est réelle, et non un artefact de l’embedding.

Trouver les gènes qui changent le long du graphe avec graph_test()

Le pseudotemps est un moyen ; le gain est quels gènes pilotent la différenciation. Monocle3 y répond avec graph_test(), et il utilise une statistique différente de tradeSeq. Plutôt que d’ajuster une courbe de l’expression contre le pseudotemps par lignée, il calcule Moran’s I — une mesure d’autocorrélation spatiale — pour chaque gène sur le graphe principal. Moran’s I pose une question simple : les cellules voisines sur le graphe tendent-elles à avoir une expression similaire de ce gène ? Un gène qui s’active dans une région et s’éteint dans une autre est fortement autocorrélé (Moran’s I élevé) ; un gène dispersé aléatoirement sur le graphe ne l’est pas. Les gènes à Moran’s I élevé sont ceux dont l’expression est organisée par la trajectoire.

Nous l’exécutons une fois, sur le graphe principal. graph_test() affiche une longue barre de progression par gène pendant qu’il travaille, donc nous le calculons dans un bloc silencieux (sortie masquée) et lisons les gènes significatifs dans le bloc suivant.

set.seed(42)
gt <- graph_test(cds, neighbor_graph = "principal_graph", cores = 1)
sum(gt$q_value < 0.05, na.rm = TRUE)          # significant genes at 5% FDR
[1] 1372
top <- gt[order(gt$morans_I, decreasing = TRUE), ]
head(top[, c("gene_short_name", "morans_I", "q_value")], 10)
                   gene_short_name  morans_I q_value
ENSMUSG00000027556            Car1 0.6993611       0
ENSMUSG00000076617            Ighm 0.6578331       0
ENSMUSG00000054191            Klf1 0.6502009       0
ENSMUSG00000009350             Mpo 0.6215372       0
ENSMUSG00000057729           Prtn3 0.6055396       0
ENSMUSG00000002985            Apoe 0.5852085       0
ENSMUSG00000028825             Rhd 0.5840181       0
ENSMUSG00000025014            Dntt 0.5757559       0
ENSMUSG00000040314            Ctsg 0.5741149       0
ENSMUSG00000031877           Ces2g 0.5605474       0

Sur les 2 500 gènes testés, 1 372 sont significatifs à un taux de fausses découvertes de 5 % (q_value) — une large fraction, comme on s’y attend dans une population dont toute la raison d’être est de se différencier. Le haut de la liste, classé par Moran’s I, aligne les marqueurs canoniques de lignées sanguines, et ils se trient nettement par destin :

  • Érythroïde (globule rouge) : Klf1, Car1, Rhd — le facteur de transcription érythroïde maître et son programme en aval.
  • Myéloïde (granulocyte) : Mpo, Prtn3, Ctsg — les enzymes des granules des neutrophiles.
  • Lymphoïde : Ighm, Dntt — l’immunoglobuline et le marqueur de progéniteur lymphoïde, la transférase terminale.

Que les gènes les plus fortement organisés spatialement soient exactement les marqueurs de destin connus est la confirmation biologique que graph_test() a trouvé une vraie structure, et non du bruit — les mêmes gènes que tradeSeq fait ressortir sur ces données par une statistique complètement différente.

Tracer les gènes dynamiques sur le graphe

La façon la plus claire de voir un gène dynamique est de peindre son expression sur le graphe. Passez à plot_cells() quelques gènes et il affiche un UMAP par gène en facettes, colorant les cellules par l’expression. Choisissez un marqueur par destin — érythroïde, myéloïde, lymphoïde — et chacun devrait s’allumer dans une région différente du graphe, suivant l’endroit où ce destin s’engage.

plot_cells(cds,
          genes = c("Klf1", "Mpo", "Dntt"),
          label_cell_groups = FALSE,
          show_trajectory_graph = FALSE,
          cell_size = 0.7)

A three-panel plot, one UMAP per gene, with cells coloured by expression. Klf1 (erythroid) lights up in one arm of the graph; Mpo (myeloid) lights up in a different arm; Dntt (lymphoid) lights up in a third region. Each fate marker is high in its own part of the trajectory and low elsewhere, showing the graph separates the committing blood lineages.

Chaque marqueur est élevé dans sa propre partie du graphe et discret ailleurs : le gène érythroïde Klf1, le gène de granule myéloïde Mpo, et le gène lymphoïde Dntt marquent chacun une branche différente. Le graphe principal a séparé les lignées en cours d’engagement, et graph_test() a choisi exactement les gènes qui les distinguent — tout le propos de l’analyse, en une seule figure.

La question des partitions : une trajectoire ou deux ?

Revenons à ces deux partitions, car c’est la décision que Monocle3 vous confie et que Slingshot ne confie pas. Monocle3 a jugé le groupe de 782 cellules déconnecté et lui a donné un pseudotemps infini. Parfois c’est juste — les cellules sont vraiment une population distincte (un type cellulaire contaminant, une variété distincte). Parfois c’est trop prudent — vous avez une raison biologique de croire qu’il s’agit d’un seul continuum et que l’écart est un artefact d’échantillonnage ou d’embedding.

Quand vous pensez qu’il s’agit d’une seule trajectoire, contournez la valeur par défaut avec learn_graph(use_partition = FALSE), qui ajuste un seul graphe à travers toutes les partitions. Ré-enracinez et ré-ordonnez, et chaque cellule reçoit un pseudotemps fini.

# learn_graph() again prints a progress bar; compute quietly, read the result below.
cds_one <- learn_graph(cds, use_partition = FALSE)
cds_one <- order_cells(cds_one,
                      root_pr_nodes = get_earliest_root(cds_one, "broad", "LTHSC"))
pt_one <- pseudotime(cds_one)
range(pt_one[is.finite(pt_one)])    # pseudotime scale with one connected graph
[1]  0.00000 22.81901
sum(is.finite(pt_one))              # now every cell is ordered
[1] 1920

Avec un seul graphe connecté, l’ensemble des 1 920 cellules reçoit un pseudotemps fini et l’échelle s’étire jusqu’à environ 22.8 — le graphe couvre désormais les deux anciennes partitions. Laquelle est juste ? C’est un jugement biologique, pas une valeur par défaut à accepter aveuglément : si les étiquettes FACS et les gènes marqueurs disent que les deux partitions sont un seul continuum en cours de différenciation, use_partition = FALSE est le choix honnête ; si le second groupe est une population véritablement distincte, laissez les partitions en place et gardez-le hors de la trajectoire. La contribution de Monocle3 est de faire émerger la question plutôt que d’y répondre silencieusement à votre place.

Quelle méthode quand

Vous avez… Utilisez Pourquoi
Une différenciation à ramification claire en R, avec un bon embedding déjà disponible Slingshot (+ tradeSeq) MST + courbes principales sur votre UMAP existant ; simple, robuste, natif Seurat
Une topologie complexe ou possiblement déconnectée ; vous voulez que la méthode signale les cellules non connectées Monocle3 (+ graph_test) Apprend un graphe principal, utilise les partitions pour séparer les variétés, teste les gènes avec Moran’s I
À inférer la direction à partir des données, pas d’une racine connue RNA velocity (une méthode d’abord Python) Utilise les rapports épissés/non épissés pour pointer la flèche de la différenciation
Des types cellulaires discrets et stables (aucun continuum) Aucune — annotez plutôt Il n’y a aucun chemin le long duquel ordonner ; utilisez l’annotation des types cellulaires

Slingshot et Monocle3 ne sont pas tant des rivaux que des compléments : exécutez les deux sur un jeu de données difficile et là où ils s’accordent vous pouvez faire confiance à la trajectoire ; là où ils divergent — le plus souvent sur la question de savoir si les cellules forment un ou plusieurs continuums — les partitions de Monocle3 vous disent pourquoi.

Problèmes fréquents

order_cells() ouvre une figure interactive et mon script se bloque. Appelé comme order_cells(cds) sans racine, Monocle3 lance un widget de sélection de racine par clic — pratique interactivement, mais il bloque un rendu ou un script non interactif. Passez toujours une racine explicitement : order_cells(cds, root_pr_nodes = "Y_3"), ou calculez le nœud à partir d’un type cellulaire connu avec l’utilitaire get_earliest_root() ci-dessus. Un pipeline reproductible ne dépend jamais d’un clic de souris.

La plupart de mes cellules ont un pseudotemps infini. Elles sont dans une partition déconnectée de la racine. Monocle3 n’ordonne que les cellules atteignables depuis le nœud de départ, donc les cellules sur un graphe séparé reçoivent Inf. Décidez si c’est correct : si les cellules supplémentaires sont une population véritablement distincte, filtrez sur is.finite(pseudotime(cds)) et passez à la suite ; si vous pensez qu’il s’agit d’un seul continuum, ré-ajustez avec learn_graph(cds, use_partition = FALSE) pour couvrir les partitions, puis ré-enracinez et ré-ordonnez.

J’ai un objet Seurat, pas un cell_data_set. Monocle3 a besoin d’un CDS. Soit reconstruisez-en un à partir des pièces brutes avec new_cell_data_set(counts, cell_metadata, gene_metadata) comme dans cette leçon (la route la plus robuste, indépendante des versions de Seurat), soit convertissez directement avec SeuratWrappers::as.cell_data_set(seurat_obj). Si vous convertissez, ré-exécutez cluster_cells() et learn_graph() sur le CDS — Monocle3 a besoin de ses propres regroupements et partitions pour construire le graphe.

Questions fréquentes

Monocle3 infère une trajectoire de développement en apprenant un graphe principal — un squelette lisse de nœuds et d’arêtes — directement à travers vos cellules dans un espace de faible dimension, puis en ordonnant les cellules le long de lui pour donner à chacune un pseudotemps. Contrairement à une méthode qui relie les centres de regroupements par un arbre, son graphe peut se ramifier, converger ou former une boucle, si bien qu’il gère des topologies plus complexes. Vous l’exécutez comme un pipeline : preprocess_cds()reduce_dimension()cluster_cells()learn_graph()order_cells().

Slingshot ajuste un arbre couvrant de poids minimal sur les centroïdes des regroupements et des courbes principales lisses sur votre embedding — simple, robuste, natif Seurat, et idéal pour un arbre à ramification claire. Monocle3 apprend un graphe principal sur son propre UMAP, gère les cellules déconnectées avec des partitions (leur donnant un pseudotemps infini plutôt que de forcer un chemin), et teste les gènes dynamiques avec Moran’s I via graph_test(). Utilisez Slingshot pour un arbre simple ; tournez-vous vers Monocle3 pour des topologies complexes ou possiblement déconnectées, ou quand vous voulez que la méthode signale les cellules qui ne sont pas sur la trajectoire.

Passez à order_cells() un nœud de départ du graphe principal via root_pr_nodes. Plutôt que de cliquer sur le sélecteur interactif (qui n’est pas reproductible), calculez le nœud le plus proche d’un type cellulaire d’origine connu : trouvez le nœud principal sur lequel la plupart de vos cellules souches se projettent, puis order_cells(cds, root_pr_nodes = that_node). Enraciner à partir de connaissances biologiques — un marqueur de cellule souche ou une étiquette FACS — est ce qui rend la direction du pseudotemps significative.

Parce qu’elles sont dans une partition déconnectée de la racine. cluster_cells() sépare les cellules en partitions — des groupes qu’il juge être des variétés distinctes — et learn_graph() ajuste un graphe au sein de chacune. Les cellules non atteignables depuis le nœud racine reçoivent un pseudotemps Inf à dessein ; Monocle3 n’inventera pas de chemin à travers un écart. Si vous pensez que les cellules sont vraiment un seul continuum, ré-ajustez avec learn_graph(cds, use_partition = FALSE) pour couvrir toutes les partitions avec un seul graphe.

graph_test(cds, neighbor_graph = "principal_graph") calcule Moran’s I, une statistique d’autocorrélation spatiale, pour chaque gène sur le graphe principal. Il demande si les cellules voisines sur la trajectoire ont une expression similaire : un gène élevé dans une région et bas dans une autre obtient un Moran’s I élevé, tandis qu’un gène dispersé aléatoirement obtient une valeur proche de zéro. Les gènes significatifs à votre taux de fausses découvertes choisi (la colonne q_value) sont ceux dont l’expression est organisée par la trajectoire.

Testez vos connaissances

En partant de nestorowa_hsc.rds, construisez le cell_data_set, exécutez le pipeline complet (preprocess_cdsreduce_dimensioncluster_cellslearn_graphorder_cells, enraciné au nœud enrichi en LT-HSC), et notez combien de cellules obtiennent un pseudotemps fini. Puis ré-ajustez le graphe avec learn_graph(cds, use_partition = FALSE), ré-enracinez, ré-ordonnez, et notez à nouveau le décompte. Combien de cellules chaque version ordonne-t-elle, et que vous dit la différence à propos de la seconde partition ?

Le seul changement entre les deux exécutions est l’argument use_partition de learn_graph(). Comptez les cellules ordonnées avec sum(is.finite(pseudotime(cds))) après chaque order_cells(). Réutilisez l’utilitaire get_earliest_root() pour enraciner les deux versions de la même façon, de sorte que la seule chose qui diffère soit que le graphe couvre ou non les partitions.

library(monocle3)
d <- readRDS("_data/nestorowa_hsc.rds")
gene_meta <- data.frame(gene_short_name = d$symbol[rownames(d$counts)],
                        row.names = rownames(d$counts))
cds <- new_cell_data_set(d$counts, cell_metadata = d$meta, gene_metadata = gene_meta)
set.seed(42); cds <- preprocess_cds(cds, num_dim = 30)
set.seed(42); cds <- reduce_dimension(cds, reduction_method = "UMAP")
set.seed(42); cds <- cluster_cells(cds)

get_earliest_root <- function(cds, cell_col, target) {
  cell_ids <- which(colData(cds)[[cell_col]] == target)
  closest <- principal_graph_aux(cds)[["UMAP"]]$pr_graph_cell_proj_closest_vertex
  igraph::V(principal_graph(cds)[["UMAP"]])$name[
    as.numeric(names(which.max(table(closest[cell_ids, ]))))]
}

# Default: a graph per partition
cds <- learn_graph(cds)
cds <- order_cells(cds, root_pr_nodes = get_earliest_root(cds, "broad", "LTHSC"))
sum(is.finite(pseudotime(cds)))        # ~1138 of 1920 — the main partition only

# One graph across all partitions
cds1 <- learn_graph(cds, use_partition = FALSE)
cds1 <- order_cells(cds1, root_pr_nodes = get_earliest_root(cds1, "broad", "LTHSC"))
sum(is.finite(pseudotime(cds1)))       # 1920 — every cell ordered

Par défaut, environ 1 138 cellules sont ordonnées et la seconde partition de 782 cellules reste à un pseudotemps infini ; avec use_partition = FALSE, l’ensemble des 1 920 cellules est ordonné. L’écart est la seconde partition. Que l’ordonner soit correct est un choix biologique : vérifiez si ces cellules partagent le programme de différenciation (par l’étiquette FACS et les gènes marqueurs) avant de décider de combler l’écart.

A. L’analyse a échoué — chaque cellule devrait recevoir un pseudotemps. B. Ces cellules sont dans une partition déconnectée de la racine, donc Monocle3 ne les ordonnera pas le long d’un graphe qu’il ne peut pas atteindre depuis elles ; vous décidez de combler l’écart (use_partition = FALSE) ou de les traiter comme une population distincte. C. Ces cellules sont les plus différenciées, donc elles se situent au maximum du pseudotemps.

B. Un pseudotemps infini marque les cellules d’une partition que Monocle3 a jugée déconnectée du graphe enraciné. Ce n’est pas un échec — c’est Monocle3 qui refuse d’inventer un ordonnancement à travers un écart dans les données. La bonne réponse est un jugement biologique : si ces cellules appartiennent au même continuum, ré-ajustez avec learn_graph(use_partition = FALSE) pour couvrir les partitions ; si elles sont une population véritablement distincte, filtrez sur les cellules à pseudotemps fini et analysez-les séparément.

Conclusion

Monocle3 emprunte une route fondée sur les graphes vers le même but que Slingshot : il apprend un graphe principal à travers les cellules avec le plongement de graphe inversé, les ordonne le long de lui à partir d’une racine que vous choisissez, et donne à chacune un pseudotemps. Sur le jeu de données de sang Nestorowa, il a enraciné le graphe dans les cellules souches LT-HSC — confirmé face aux étiquettes FACS mises de côté — et graph_test() a fait ressortir les marqueurs érythroïdes, myéloïdes et lymphoïdes canoniques par Moran’s I. La différence déterminante avec Slingshot est l’honnêteté à propos de la structure : Monocle3 a séparé les cellules en partitions et a donné à 782 d’entre elles un pseudotemps infini plutôt que de forcer un chemin, vous confiant — au lieu de la cacher — la décision de savoir si elles appartiennent à une seule trajectoire. Exécutez-le quand votre topologie est complexe ou que vous soupçonnez des populations déconnectées ; exécutez Slingshot pour un arbre à ramification propre ; et quand un jeu de données est difficile, exécutez les deux et faites confiance à la trajectoire là où ils s’accordent.

Leçons connexes

  • Trajectoire & pseudotemps avec Slingshot — le pendant fondé sur les arbres de cette leçon ; ajuste un MST sur les centroïdes des regroupements et des courbes lisses, et trouve les gènes dynamiques avec tradeSeq. Lisez les deux pour choisir entre elles. · Clustering & UMAP — construit les regroupements et l’embedding sur lesquels une trajectoire est ajustée ; de bons regroupements font un bon graphe. · Gènes marqueurs & annotation des types cellulaires — le pendant discret : étiqueter des types cellulaires stables, la question à laquelle l’analyse de trajectoire ne répond pas. · Bioinformatique — le pilier.
  • Où cela s’inscrit : clustering & UMAPgènes marqueurs & annotation → trajectoire & pseudotemps (cette leçon avec Monocle3, ou Slingshot) → les gènes-le-long-du-graphe avec graph_test — d’une carte statique des types cellulaires au chemin dynamique qui les relie.
🟢 Avec un agent IA

Demandez à Prova « comment exécuter une trajectoire Monocle3 sur mon propre objet Seurat — construire un cell_data_set, apprendre le graphe, fixer la racine par programmation, et trouver les gènes dynamiques avec graph_test ? » — elle répond avec du code R que vous pouvez exécuter sur vos propres données : convertir ou reconstruire le CDS, exécuter le pipeline preprocess → reduce → cluster → learn_graph → order_cells, l’enraciner à partir d’un type cellulaire connu, et tester les gènes avec Moran’s I. The runtime is the judge. Demander à Prova →

Cette page vous a-t-elle été utile ?

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN
Note

Cette leçon est reproductible : chaque figure et chaque nombre ont été produits par le code montré, exécuté de haut en bas dans l’image de rendu datanovia/quarto-bioinformatics (monocle3 1.4.27) — copiez les blocs et exécutez-les dans l’ordre pour les reproduire. Le _freeze commité est ce que le site sert. The runtime is the judge.

Références

Réutilisation

Citation

BibTeX
@online{2026,
  author = {},
  title = {Analyse de trajectoire Monocle3 en R : le pseudotemps avec un
    graphe principal},
  date = {2026-07-04},
  url = {https://www.datanovia.com/learn/bioinformatics/single-cell/scrnaseq-trajectory-monocle3.html},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“Analyse de trajectoire Monocle3 en R : le pseudotemps avec un graphe principal.” 2026. July 4. https://www.datanovia.com/learn/bioinformatics/single-cell/scrnaseq-trajectory-monocle3.html.