Dans ce chapitre, nous allons aborder plusieurs méthodes d’analyse à partir d’un jeu de données longitudinales. Tout d’abord, importons les données dans R avec la commande suivante :

load(url("http://larmarange.github.io/analyse-R/data/care_trajectories.RData"))
class(care_trajectories)
[1] "data.table" "data.frame"

Nous obtenons un objet appelé care_trajectories. La fonction class nous montre qu’il s’agit d’un tableau de données au format data.table (voir le chapitre dédié). Chargeons donc cette extension ainsi que le tidyverse.

library(tidyverse, quietly = TRUE)
library(data.table, quietly = TRUE)

Première description des données

Jetons un premier regard aux données.

care_trajectories

Il apparaît que les données sont dans un format long et tidy (voir le chapitre sur tidyr pour une présentation du concept de tidy data), avec une ligne par individu et par pas de temps. Il apparait également que les données sont stockées sous formes de vecteurs labellisés (voir le chapitre dédié aux vecteurs labellisés). Nous aurons donc besoin de l’extension labelled.

library(labelled)

Pour une description des variables, on pourra avoir recours à describe de questionr.

library(questionr)
describe(care_trajectories, freq.n.max = 10)
[49365 obs. x 11 variables] data.table data.frame

$id: patient identifier
integer: 3 3 3 3 3 3 9 9 13 13 ...
min: 3 - max: 9998 - NAs: 0 (0%) - 2929 unique values

$month: month(s) since diagnosis
numeric: 0 1 2 3 4 5 0 1 0 1 ...
min: 0 - max: 50 - NAs: 0 (0%) - 51 unique values

$care_status: care status
labelled character: "D" "D" "D" "D" "D" "D" "D" "D" "D" "D" ...
NAs: 0 (0%) - 4 unique values
4 value labels: [D] diagnosed, but not in care [C] in care, but not on treatment [T] on treatment, but infection not suppressed [S] on treatment and suppressed infection

                                                   n     %
[D] diagnosed, but not in care                 25374  51.4
[C] in care, but not on treatment               5886  11.9
[T] on treatment, but infection not suppressed  4596   9.3
[S] on treatment and suppressed infection      13509  27.4
Total                                          49365 100.0

$sex: sex
labelled double: 1 1 1 1 1 1 1 1 0 0 ...
min: 0 - max: 1 - NAs: 0 (0%) - 2 unique values
2 value labels: [0] male [1] female

               n   %
[0] male   17781  36
[1] female 31584  64
Total      49365 100

$age: age group
labelled double: 1 1 1 1 1 1 2 2 2 2 ...
min: 1 - max: 3 - NAs: 0 (0%) - 3 unique values
3 value labels: [1] 16-29 [2] 30-59 [3] 60+

              n     %
[1] 16-29 16911  34.3
[2] 30-59 29365  59.5
[3] 60+    3089   6.3
Total     49365 100.0

$education: education level
labelled double: 2 2 2 2 2 2 3 3 2 2 ...
min: 1 - max: 3 - NAs: 0 (0%) - 3 unique values
3 value labels: [1] primary [2] secondary [3] higher

                  n     %
[1] primary   10417  21.1
[2] secondary 19024  38.5
[3] higher    19924  40.4
Total         49365 100.0

$wealth: wealth group (assets score)
labelled double: 2 2 2 2 2 2 2 2 1 1 ...
min: 1 - max: 3 - NAs: 0 (0%) - 3 unique values
3 value labels: [1] low [2] middle [3] high

               n     %
[1] low    15432  31.3
[2] middle 20769  42.1
[3] high   13164  26.7
Total      49365 100.0

$distance_clinic: distance to nearest clinic
labelled double: 1 1 1 1 1 1 2 2 2 2 ...
min: 1 - max: 2 - NAs: 0 (0%) - 2 unique values
2 value labels: [1] less than 10 km [2] 10 km or more

                        n     %
[1] less than 10 km 26804  54.3
[2] 10 km or more   22561  45.7
Total               49365 100.0

$next_month: month(s) since diagnosis
numeric: 1 2 3 4 5 NA 1 NA 1 2 ...
min: 1 - max: 50 - NAs: 2929 (0.1%) - 51 unique values

$min_month_pb.x: 
integer: 1 1 1 1 1 1 1 1 1 1 ...
min: 1 - max: 1 - NAs: 19 (0%) - 2 unique values

          n   % val%
1     49346 100  100
NA       19   0   NA
Total 49365 100  100

$min_month_pb.y: 
integer: 1 1 1 1 1 1 1 1 1 1 ...
min: 1 - max: 1 - NAs: 19 (0%) - 2 unique values

          n   % val%
1     49346 100  100
NA       19   0   NA
Total 49365 100  100

Dans cette étude, on a suivi des patients à partir du moment où ils ont été diagnostiqués pour une pathologie grave et chronique et on a suivi leurs parcours de soins chaque mois à partir du diagnostic. La variable status contient le statut dans les soins de chaque individu pour chaque mois de suivi :

  • D : s’il n’est pas actuellement suivi dans une clinique, soit que la personne n’est pas encore entrée en clinique après le diagnostic, soit qu’elle a quitté la clinique et qu’elle est donc sortie des soins ;
  • C : indique que le patient est entré en soins (il est suivi dans une clinique) mais il n’a pas encore commencé le traitement, ou bien il a arrêté le traitement mais est toujours suivi en clinique ;
  • T : la personne est sous traitement mais l’infections n’est pas supprimée ou contrôlée, soit que le traitement n’a pas encore eu le temps de faire effet, soit qu’il n’est plus efficace ;
  • S : la personne est suivie en clinique, sous traitement et son infection est supprimée / contrôlée, indiquant que le traitement est efficace et produit son effet. Cette étape ultime du parcours de soins est celle dans laquelle on souhaite maintenir les individus le plus longtemps possible.

Il est important de noter que nous avons ici des statuts hiérarchiquement ordonnés (D < C < T < S), ce qui aura son importance pour les choix méthodologiques que nous aurons à faire.

Nous disposons également d’autres variables (âge, sexe, niveau d’éducation, distance à la clinique…) qui sont ici dépendantes du temps, c’est-à-dire que le cas échéant, elles peuvent varier d’un mois à l’autre en cas de changement.

Le fichier contient 49 365 lignes, ce qui ne veut pas dire qu’il y a ce nombre d’invidus suivis au cours du temps, puisque plusieurs lignes correspondent à un même individu. On peut obtenir le nombre d’individus différents assez facilement avec la commande :

length(unique(care_trajectories$id))
[1] 2929

Précision : dans ce fichier, tous les individus ne sont pas suivis pendant la même durée, car ils n’ont pas tous été diagnostiqués au même moment. Cependant, il n’y a pas de trous dans le suivi (ce qui serait le cas si certains individus sortaient de l’observation pendant quelques mois puis re-rentraient dans la cohorte de suivi).

Avant d’aller plus avant, il nous faut avoir une idée du nombre d’individus observé au cours du temps, ce que l’on peut obtenir avec :

ggplot(care_trajectories) + aes(x = month) + geom_bar()

Améliorons ce graphique en y ajoutant la distribution selon le statut dans les soins chaque mois, en améliorant l’axe du temps (tous les 6 mois est plus facile à lire) et en y ajoutant un titre et des étiquettes appropriées. Afin de disposer d’une palette de couleurs à fort contraste, nous allons utiliser l’extension viridis. Enfin, nous allons utiliser une petite astuce pour indiquer les effectifs sur l’axe horizontal. Au passage, nous allons également franciser les étiquettes de la variable care_status avec val_labels (notez aussi le recours à to_factor dans aed qui nous permet de transformer à la volée la variable en facteur, format attendu par ggplot2 pour les variables catégorielles). On se référera au chapitre dédié à ggplot2 pour plus de détails sur les différentes fonctions de cette extension graphique.

library(viridis)
Loading required package: viridisLite

Attaching package: 'viridis'
The following object is masked from 'package:scales':

    viridis_pal
n <- care_trajectories[month %in% (0:8 * 6), .(n = .N), by = month]$n
etiquettes <- paste0("M", 0:8 * 6, "\n(n=", n, ")")
val_labels(care_trajectories$care_status) <- c(`diagnostiqué mais pas suivi` = "D", 
  `suivi mais pas sous traitement` = "C", `sous traitement, mais infection son contrôlée` = "T", 
  `sous traitement et infection contrôlée` = "S")
ggplot(care_trajectories) + aes(x = month, fill = to_factor(care_status)) + geom_bar(color = "gray50", 
  width = 1) + scale_x_continuous(breaks = 0:8 * 6, labels = etiquettes) + ggtitle("Distribution du statut dans les soins chaque mois") + 
  xlab("") + ylab("") + theme_light() + theme(legend.position = "bottom") + labs(fill = "Statut dans les soins") + 
  scale_fill_viridis(discrete = TRUE, direction = -1) + guides(fill = guide_legend(nrow = 2))

On s’aperçoit qu’une majorité des personnes suivies ne l’ont été que peu de temps, avec une décroissance rapide des effectifs.

Évolution de la cascade de soins au cours du temps

On nomme communément cascade de soins la proportion d’individus dans chaque statut à un moment du temps donné. On peut facilement obtenir celle-ci à partir du code du graphique précédent en ajoutant l’option position = fill à geom_bar.

ggplot(care_trajectories) + aes(x = month, fill = to_factor(care_status)) + geom_bar(color = "gray50", 
  width = 1, position = "fill") + scale_x_continuous(breaks = 0:8 * 6, labels = etiquettes) + 
  ggtitle("Cascade des soins observée, selon le temps depuis le diagnostic") + 
  xlab("") + ylab("") + theme_light() + theme(legend.position = "bottom") + labs(fill = "Statut dans les soins") + 
  scale_fill_viridis(discrete = TRUE, direction = -1) + guides(fill = guide_legend(nrow = 2))

Les effectifs sont très faibles au-delà de 36 mois et il serait préférable de couper la cascade au-delà de M36, ce que l’on peut faire aisément ne gardant que les lignes correspondantes de care_trajectories.

ggplot(care_trajectories[month <= 36]) + aes(x = month, fill = to_factor(care_status)) + 
  geom_bar(color = "gray50", width = 1, position = "fill") + scale_x_continuous(breaks = 0:8 * 
  6, labels = etiquettes) + ggtitle("Cascade des soins observée, selon le temps depuis le diagnostic") + 
  xlab("") + ylab("") + theme_light() + theme(legend.position = "bottom") + labs(fill = "Statut dans les soins") + 
  scale_fill_viridis(discrete = TRUE, direction = -1) + guides(fill = guide_legend(nrow = 2))

Une première analyse de séquences sur l’ensemble du fichier

Nous allons réaliser une analyse de séquences (voir le chapitre dédié) sur l’ensemble de notre fichier. Pour cela, il va falloir préalable que nous transformions nos donnée actuellement dans un format long and un tableau large, c’est-à-dire avec une ligne par individu et une variable différentes par pas de temps. On peut réaliser cela facilement avec spread de tidyr (voir le chapitre dédié à tidyr)

library(tidyr)
large <- care_trajectories %>% dplyr::select(id, m = month, care_status) %>% spread(key = m, 
  value = care_status, sep = "")
large