У меня есть фрейм данных в R:
df <- data.frame(CRP = c("10", "2", "3", NA, "<4", ">5"))
Я хочу создать числовой столбец, который заменяет строки числовыми значениями. Кроме того, всякий раз, когда он находит строку, начинающуюся с «<» или «>», он должен искать следующую букву, то есть число, и вменять строку с медианой наблюдений выше или ниже этого числа. Например, «<4» следует заменить на медиану(c(2,3)). Если нет значений ниже или выше порогового значения, значение NA. Остальные значения NA должны оставаться такими, какие они есть сейчас.
Желаемый результат:
df = data.frame(c(10,2,3,NA,median(c(2,3)),median(10))
Мне не удалось найти решение, позволяющее избежать предварительного указания функций априори. Я также хочу избежать циклов for. В идеале я хотел бы применить это к фрейму данных с миллионами строк, где эта проблема возникает с несколькими столбцами.
library(dplyr)
library(stringr)
library(purrr)
nums <- na.omit(as.numeric(df$CRP))
df |>
mutate(imputed = map2_dbl(CRP, str_detect(CRP, "<|>"), \(x, gt_lt) {
if (gt_lt %in% T) {
eval(str2expression(str_glue("median(nums[nums{x}])")))
} else as.numeric(x)
})
)
# CRP imputed
# 1 10 10.0
# 2 2 2.0
# 3 3 3.0
# 4 <NA> NA
# 5 <4 2.5
# 6 >5 10.0
По сути, здесь используется CRP
для построения и вычисления выражения типа: median(nums[nums<4])
, где nums
— это c(10, 2, 3)
. В зависимости от вашей структуры данных может быть проще назначить эту анонимную функцию и определить nums
внутри нее.
Для нескольких переменных вы можете попробовать следующий код, который использует цикл for только для уникальных наблюдений, содержащих «<» или «>», а затем заменяет все значения, удовлетворяющие этому условию.
df <- structure(list(CRP = c("10", "2", "3", NA, "<4", ">5"), CRP2 = c("10",
"12", "<5", "NA", ">5", "5")), class = "data.frame", row.names = c(NA,
-6L))
imputed <- paste0(names(df), "_imputed")
df[imputed] <- sapply(names(df), \(var) {
x <- df[,var]
num <- suppressWarnings(as.numeric(x))
for(s in unique(grep('<|>', x, value=TRUE))) {
x[which(x==s)] <- eval(parse(text=paste0("median(num[num", s, "], na.rm=TRUE)")))
}
suppressWarnings(as.numeric(x))
})
df
CRP CRP2 CRP_imputed CRP2_imputed
1 10 10 10.0 10
2 2 12 2.0 12
3 3 <5 3.0 NA
4 <NA> NA NA NA
5 <4 >5 2.5 11
6 >5 5 10.0 5
Это быстро даже для набора данных, содержащего миллионы строк.
n <- 5000000
df <- data.frame(CRP = sample(c("10", "2", "3", NA, "<4", ">5"), n, TRUE),
CRP2 = sample(c("10", "12", "<5", "NA", ">5", "5"), n, TRUE))
imputed <- paste0(names(df), "_imputed")
system.time(
df[imputed] <- sapply(names(df), \(var) {
x <- df[,var]
num <- suppressWarnings(as.numeric(x))
for(s in unique(grep('<|>', x, value=TRUE))) {
x[which(x==s)] <- eval(parse(text=paste0("median(num[num", s, "], na.rm=TRUE)")))
}
suppressWarnings(as.numeric(x))
})
)
user system elapsed
3.39 0.08 5.25
Отлично, спасибо!
Я указал вывод сейчас. Мне не нужна последовательность.