У меня есть набор данных, как показано ниже. У каждого пациента есть 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. Записи по диагоналям представляют количество участников, которые не перешли)
@ZéLoff Да, они есть у каждого пациента.





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)))
Если набор данных полный или пропущенные значения явны (т. е. если для каждого посещения каждого пациента есть явная запись, даже если 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
Если в наборе данных отсутствуют значения, т. е. если нет строки для каждого посещения каждого пациента, можно использовать тот же подход, но нам нужно заполнить недостающие точки данных, присоединив 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)
)
Есть ли у каждого пациента записи о посещении 1 и посещении 2?