Три возможных решения, в зависимости от ваших строгих потребностей.
Начиная с вашего df
, давайте создадим список из трех. Поскольку в столбце есть так много столбцов, я сосредоточусь лишь на нескольких, но для всех остальных столбцов выполняются вычисления.
dflst <- list(df, df, df)
df[, c("X", "V1", "V20", "V21", "V34")]
# X V1 V20 V21 V34
# 1 1 NA 63.06 36.64 11.69
# 2 2 NA 38.49 31.73 NA
TL, DR: Я думаю, что " Случай 3 ", вероятно, является лучшим (наиболее гибким, наиболее надежным) из трех, учитывая некоторые предположения, и в этом случае вы можете пропустить это «кондиционирование имени столбца» и случаи 1-2, и пропустите прямо ко дну.
Предварительная обработка: Подготовка кадра
Предположение для некоторых из этих случаев состоит в том, что все кадры имеют одинаковые размеры и все столбцы находятся в одном и том же порядке. Если это всегда так, то вы можете пропустить этот шаг «подготовки».
Однако, если это не безопасное предположение, можно в некотором смысле «обусловить» их. Я продемонстрирую со списком из трех поддельных кадров, все немного по-другому:
somelst <- list(data.frame(x=1,y=2), data.frame(y=3, x=4), data.frame(x=5, z=6))
some_names <- names(somelst[[1]])
somelst <- lapply(somelst, `[`, some_names)
# Error in `[.data.frame`(X[[i]], ...) : undefined columns selected
Чтобы исправить это, нам нужно использовать только общие имена. Обратите внимание, что если это произойдет, то следующие шаги будут молча отбрасывать нестандартные столбцы.
somelst <- lapply(somelst, function(l) l[, intersect(some_names, names(l)), drop=FALSE])
somelst
# [[1]]
# x y
# 1 1 2
# [[2]]
# x y
# 1 4 3
# [[3]]
# x
# 1 5
Порядок столбцов теперь стандартный, но (относится к случаям 1-2 ниже) у нас пропущены столбцы. Исправлено:
somelst <- lapply(somelst, function(l) { l[, setdiff(some_names, names(l))] <- NA; l; })
somelst
# [[1]]
# x y
# 1 1 2
# [[2]]
# x y
# 1 4 3
# [[3]]
# x y
# 1 5 NA
(Этот последний шаг просто добавляет все- NA
значения для пропущенных столбцов.)
Итак, я применю это к нашим данным (хотя мы знаем, что здесь нет операции, поскольку все элементы списка являются идентичными кадрами):
df_names <- names(dflst[[1]])
dflst <- lapply(dflst, function(l) l[, intersect(df_names, names(l)), drop=FALSE])
dflst <- lapply(dflst, function(l) { l[, setdiff(df_names, names(l))] <- NA; l; })
Случай 1: X
- это данные
Я думаю, что это маловероятно, но я включил его для полноты на тот случай, если самое простое - это то, что ожидается:
out <- Reduce(`+`, dflst)
out[, c("X", "V1", "V20", "V21", "V34")]
# X V1 V20 V21 V34
# 1 3 NA 189.18 109.92 35.07
# 2 6 NA 115.47 95.19 NA
Случай 2: X
является ключом
В этом случае мы просто складываем все вместе, но без изменения идентификатора X
.
Примечание : предполагается, что все идентификаторы присутствуют во всех кадрах и в том же порядке .
out <- df # really just need "X" and the right number of columns
# ... none of the other values are used
out[,-1] <- Reduce(`+`, lapply(dflst, `[`, -1))
out[, c("X", "V1", "V20", "V21", "V34")]
# X V1 V20 V21 V34
# 1 1 NA 189.18 109.92 35.07
# 2 2 NA 115.47 95.19 NA
Можно проверить это предположение примерно так:
identical(df$X, Reduce(function(a, b) if (identical(a,b)) a else FALSE, lapply(dflst, `[[`, "X")))
# [1] TRUE
Любая разница будет указывать на причину более (не очень простого) кондиционирования или Случай 3 (что по-прежнему является моей общей рекомендацией).
Случай 3: переменные (но все же важные) имена столбцов
В этом случае нам , а не нужно предварительно подготовить данные, как мы это делали, для нормализации имен столбцов и их порядка, как это делается естественным образом. Предполагается, что имена столбцов являются важными и стандартными. Это означает, что если вы видите "V22"
в одном кадре, это означает "V22"
во всех кадрах, и что ничто другое не совпадает с "V22"
.
Однако не предполагает, что все имена в одном кадре будут в другом, поэтому отсутствие столбца обрабатывается плавно. Если все они находятся там и в одном и том же порядке (как и следовало ожидать от автоматизированного набора данных), это тоже работает.
Это может быть сделано в base-R и data.table
, но я считаю, что грамотная форма dplyr
(и семья) является наиболее ясной для демонстрации:
library(dplyr)
library(purrr)
library(tidyr)
out <- map(dflst, ~ gather(., k, v, -X)) %>%
bind_rows(.) %>%
group_by(X, k) %>%
summarize(v = if (all(is.na(v))) NA_real_ else sum(v, na.rm = TRUE)) %>%
spread(k, v)
out[, c("X", "V1", "V20", "V21", "V34")]
# # A tibble: 2 x 5
# # Groups: X [2]
# X V1 V20 V21 V34
# <int> <dbl> <dbl> <dbl> <dbl>
# 1 1 NA 189. 110. 35.1
# 2 2 NA 115. 95.2 NA
(Это tibble
, чье представление на консоли имеет некоторые заметные отличия от необработанных фреймов. Примечательно, что значения "V20"
выглядят иначе, хотя в этом случае это всего лишь хороший способ сохранить tibble
«аккуратные» вещи со значительными цифрами и т. д. Если вместо этого вы наберете as.data.frame(out[, c("X", "V1", "V20", "V21", "V34")])
, вы увидите, что результаты одинаковы.)
Пояснение:
map(dflst, ...)
делает что-то для каждого кадра в списке;
gather(., k, v, -X)
преобразует из «широкого» в «длинный» формат, где отдельный кадр будет выглядеть так:
gather(df, k, v, -X) %>% head(.)
# X k v
# 1 1 V1 NA
# 2 2 V1 NA
# 3 1 V2 NA
# 4 2 V2 NA
# 5 1 V3 NA
# 6 2 V3 NA
bind_rows(.)
объединяет список кадров в один сцепленный со строкой кадр
group_by(X, k) %>% summarize(...)
выполняет агрегацию по id и по (исходному) столбцу, поэтому все X==1
и k=="V1"
объединяются в одну строку и т. Д. if (all(is.na(v))) NA_real_ else sum(v, na.rm = TRUE)
немного взломан;обычно я делал бы просто sum(v, na.rm = TRUE)
(без if
), но в других случаях поле all- NA
сохранялось как NA
, где этот sum
преобразует его в 0
.Я подумал, что важно сохранить идею о том, что «в этом поле никогда не было данных», поэтому, если все они равны NA
, оставьте его NA
, в противном случае дайте сумму всех не NA
полей. spread(k, v)
преобразует обратно из «длинного» в «широкий» формат.