Посчитайте количество переходов

У меня есть набор данных, как показано ниже. У каждого пациента есть 3 посещения, и они могут переходить между 3 состояниями от визита к визиту.

ID <- c(1,1,1,2,2,2,3,3,3)
Visit <- c(1,2,3,1,2,3,1,2,3)
State <- c(2,1,1,3,2,1,2,3,1)

Я хочу создать фрейм данных, который подсчитывает количество переходов состояний от посещения 1 к посещению 2. Для посещения 1 к посещению 2 матрица будет выглядеть следующим образом: (строки представляют состояние при посещении 1, а столбцы представляют состояние при посещении 1). состояние при посещении 2. Записи по диагоналям представляют количество участников, которые не перешли)

Есть ли у каждого пациента записи о посещении 1 и посещении 2?

Zé Loff 10.05.2024 10:29

@ZéLoff Да, они есть у каждого пациента.

Jenn0804 10.05.2024 22:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
90
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

tidyverse подход:

data.frame(ID = c(1,1,1,2,2,2,3,3,3),
           Visit = c(1,2,3,1,2,3,1,2,3),
           State = c(2,1,1,3,2,1,2,3,1)) |>

  # identify the next State within each ID
  mutate(next_State = lead(State), .by = ID) |>
  # we only want when Visit is 1
  filter(Visit == 1) |>
  # How many of each State / next_State?
  count(State, next_State) |>
  # add in any missing combinations with n = 0
  complete(State = 1:3, next_State = 1:3, fill = list(n=0)) |>
  # reshape wide
  pivot_wider(names_from = next_State, values_from = n)

Результат

# A tibble: 3 × 4
  State   `1`   `2`   `3`
  <dbl> <int> <int> <int>
1     1     0     0     0
2     2     1     0     1
3     3     0     1     0

Подход с использованием table:

ID <- c(1,1,1,2,2,2,3,3,3)
Visit <- c(1,2,3,1,2,3,1,2,3)
State <- c(2,1,1,3,2,1,2,3,1)
df <- data.frame(ID, Visit, State=factor(State))

filter(df, Visit==1) |>
  inner_join(filter(df, Visit==2), by = "ID", suffix=c("_1","_2")) |>
  select(State_1, State_2) |>
  table()

       State_2
State_1 1 2 3
      1 0 0 0
      2 1 0 1
      3 0 1 0

library(tidyr)
library(dplyr)
Ответ принят как подходящий

Хотя в использовании других пакетов нет никакого вреда, это можно легко сделать, используя только table на базе R (плюс небольшой шаг, если данные неполные).

Предварительные шаги

Вероятно, ваши данные находятся в data.frame, поэтому мы создадим их на основе вашего образца данных. Я также внесу небольшие изменения в переменные (идентификаторы в виде букв, посещения в виде «V1», «V2» и т. д.) для удобства чтения.

ddff <- data.frame(
  ID = rep(c("A", "B", "C"), each = 3),
  Visit = rep(c("V1", "V2", "V3"), 3),
  State = paste0("S", c(2, 1, 1, 3, 2, 1, 2, 3, 1)))

Сценарий 1: полный набор данных

Если набор данных полный или пропущенные значения явны (т. е. если для каждого посещения каждого пациента есть явная запись, даже если State является NA), тогда достаточно простого table. Нам просто нужно сначала превратить State в factor, чтобы убедиться, что его не уронят, и нам нужно заказать data.frame

ddff$State <- factor(ddff$State)
ddff <- ddff[order(ddff$ID, ddff$Visit), ]

table(ddff$State[ddff$Visit == "V1"],
      ddff$State[ddff$Visit == "V2"],
      dnn = c("V1", "V2"))
    V2
V1   S1 S2 S3
  S1  0  0  0
  S2  1  0  1
  S3  0  1  0

На диагонали будут ненулевые значения, если ни один из пациентов не изменит состояние. Например. для посещения 3 и посещения 2:

table(ddff$State[ddff$Visit == "V2"],
      ddff$State[ddff$Visit == "V3"],
      dnn = c("V2", "V3"))
    V3
V2   S1 S2 S3
  S1  1  0  0
  S2  1  0  0
  S3  1  0  0

Но если они вам действительно не нужны, вы легко присвоите диагонали нули:

tt <- table(ddff$State[ddff$Visit == "V2"],
            ddff$State[ddff$Visit == "V3"],
            dnn = c("V2", "V3"))
diag(tt) <- 0
tt
    V3
V2   S1 S2 S3
  S1  0  0  0
  S2  1  0  0
  S3  1  0  0

Сценарий 2: неявно недостающие данные

Если в наборе данных отсутствуют значения, т. е. если нет строки для каждого посещения каждого пациента, можно использовать тот же подход, но нам нужно заполнить недостающие точки данных, присоединив data.frame к комбинации всех возможных значений. Идентификаторы и посещения.

Сначала мы опустим V2 для пациента Б, чтобы создать неполный data.frame:

ddff2 <- ddff[-5, ]
ddff2
  ID Visit State
1  A    V1    S2
2  A    V2    S1
3  A    V3    S1
4  B    V1    S3
5  B    V3    S1
6  C    V1    S2
7  C    V2    S3
8  C    V3    S1

Затем мы используем expand.grid, чтобы создать data.frame со всеми возможными комбинациями ID и Visit, а затем используем merge, чтобы скрестить его с нашим набором данных. Это превратит неявные пропущенные значения в явные пропущенные значения:

ddff2 <- merge(
  ddff2,
  expand.grid(ID = unique(ddff2$ID), Visit = unique(ddff2$Visit)),
  all.y = T)
ddff2
  ID Visit State
1  A    V1    S2
2  A    V2    S1
3  A    V3    S1
4  B    V1    S3
5  B    V2  <NA>
6  B    V3    S1
7  C    V1    S2
8  C    V2    S3
9  C    V3    S1

Теперь мы можем использовать тот же подход, что и раньше:

table(ddff2$State[ddff2$Visit == "V1"],
      ddff2$State[ddff2$Visit == "V2"],
      dnn = c("V1", "V2"))
    V2
V1   S1 S2 S3
  S1  0  0  0
  S2  1  0  1
  S3  0  0  0

Мы можем tapply State by ID и подмножество матрицы состояний.

> s <- sort(unique(df$State))
> M <- array(0, dim=replicate(2, length(s), simplify=FALSE), 
+            dimnames=replicate(2, paste0('State', s), simplify=FALSE))
> M[t(subset(df, Visit < 3) |> with(tapply(State, ID, c)) |> simplify2array())] <- 1
> M
       State1 State2 State3
State1      0      0      0
State2      1      0      1
State3      0      1      0

Данные:

> dput(df)
structure(list(ID = c(1, 1, 1, 2, 2, 2, 3, 3, 3), Visit = c(1, 
2, 3, 1, 2, 3, 1, 2, 3), State = c(2, 1, 1, 3, 2, 1, 2, 3, 1)), class = "data.frame", row.names = c(NA, 
-9L))

Вы можете попробовать код ниже

f <- function(df, from = 1, to = 2) {
    s <- with(df, table(State, State) * 0)
    df %>%
        {
            cbind(from = head(., -1), to = tail(., -1))
        } %>%
        filter(from.Visit == from & to.Visit == to) %>%
        {
            s[with(., cbind(from.State, to.State))] <- 1
            s
        }
}

такой, что

> f(dat)
     State
State 1 2 3
    1 0 0 0
    2 1 0 1
    3 0 1 0

> f(dat, 3, 1)
     State
State 1 2 3
    1 0 1 1
    2 0 0 0
    3 0 0 0

Данные

dat <- data.frame(
    ID = c(1, 1, 1, 2, 2, 2, 3, 3, 3),
    Visit = c(1, 2, 3, 1, 2, 3, 1, 2, 3),
    State = c(2, 1, 1, 3, 2, 1, 2, 3, 1)
)

Другие вопросы по теме