У меня есть фрейм данных (или таблица данных, если так проще) с неполными строками:
ID Var1 Var2 Var3
1 2 5 1
2 12 3
3 8
4 4
d <- data.frame(
ID = 1:4,
Var1 = c(2, 12, 8, 4),
Var2 = c(5, 3, NA, NA),
Var3 = c(1, NA, NA, NA)
)
library(data.table)
d <- fread("
ID Var1 Var2 Var3
1 2 5 1
2 12 3 NA
3 8 NA NA
4 4 NA NA
")
Пустые ячейки всегда находятся в конце строки.
Я хотел бы заполнить пустые ячейки в каждой строке значением в последней непустой ячейке в этой строке, например:
ID Var1 Var2 Var3
1 2 5 1
2 12 3 -> 3
3 8 -> 8 -> 8
4 4 -> 4 -> 4
Как мне это сделать?





Транспонируйте и заполните недостающее, затем транспонируйте еще раз.
library(data.table)
transpose(
setnafill(transpose(d, keep.names = "ID"),
type = "locf", cols = 1:nrow(d) + 1),
make.names = "ID")
# ID Var1 Var2 Var3
# <int> <int> <int> <int>
# 1: 1 2 5 1
# 2: 2 12 3 3
# 3: 3 8 8 8
# 4: 4 4 4 4
Вот подход в базе:
(start <- structure(list(
ID = 1:4,
Var1 = c(2L, 12L, 8L, 4L),
Var2 = c(5L, 3L, NA, NA),
Var3 = c(1L, NA, NA, NA)
),
class = "data.frame",
row.names = c(NA, -4L)))
start$last <- apply(start,MARGIN = 1,FUN = \(row){row |>
as.matrix() |>
na.omit() |>
tail(n=1)})
start$last
(fin <- apply(start,MARGIN = 1,FUN = \(row){
n <- length(row)
p <- ifelse(is.na(row),row[n] ,row)
head(p,-1)
}) |> t() |> data.frame())
Вариант tidyr::pivot/fill:
library(tidyverse) # `tidyr`
# toy data
aux <- tibble::tribble(
~ID, ~Var1, ~Var2, ~Var3,
1, 2, 5, 1,
2, 12, 3, NA,
3, 8, NA, NA,
4, 4, NA, NA)
# Pivot down, fill down and and pivot back
new_aux <- aux %>%
pivot_longer(-ID) %>%
fill(value, .direction = "down") %>%
pivot_wider()
Выход:
> new_aux
# A tibble: 4 × 4
ID Var1 Var2 Var3
<dbl> <dbl> <dbl> <dbl>
1 1 2 5 1
2 2 12 3 3
3 3 8 8 8
4 4 4 4 4
Created on 2024-05-28 with reprex v2.1.0
d[, (cols) := Reduce(\(x, y) ifelse(is.na(y), x, y), .SD, accumulate = TRUE), .SDcols = cols]
Или
d[, (cols) := Reduce(fcoalesce, .SD, right = TRUE, accumulate = TRUE), .SDcols = cols]
Выход
# ID Var1 Var2 Var3
# <int> <int> <int> <int>
# 1: 1 2 5 1
# 2: 2 12 3 3
# 3: 3 8 8 8
# 4: 4 4 4 4
Где
cols <- sprintf("Var%d", 1:3)
d <- data.table(
ID = 1:4,
Var1 = c(2L, 12L, 8L, 4L),
Var2 = c(5L, 3L, 8L, 4L),
Var3 = c(1L, 3L, 8L, 4L)
)
Вы можете просто запустить Reduce, как показано ниже.
> df[-1] <- Reduce(\(x, y) ifelse(is.na(y), x, y), df[-1], accumulate = TRUE)
или
df[-1] <- Reduce(\(x, y) rowSums(cbind(x, y), TRUE) - x * !is.na(y), df[-1], accumulate = TRUE)
который дает
> df
ID Var1 Var2 Var3
1 1 2 5 1
2 2 12 3 3
3 3 8 8 8
4 4 4 4 4
@Бен, это сокращенное выражение function(x,y) в последних версиях R.
Другой ответ с использованием пакета collapse, преимущество которого заключается в том, что он особенно быстр (по сравнению с data.table):
library(collapse)
dapply(d, na_locf, MARGIN = 1)
# ID Var1 Var2 Var3
# 1 1 2 5 1
# 2 2 12 3 3
# 3 3 8 8 8
# 4 4 4 4 4
Микробенчмарк:
# Unit: microseconds
# expr min lq mean median uq max neval
# collapse 69.5 112.95 244.847 135.45 161.7 9964.4 100
# dt 592.9 788.70 1237.643 874.70 1186.6 14563.1 100
# tidyr 32283.2 36170.80 41293.420 40501.55 43809.1 75417.8 100
# Reduce_dt 645.0 803.70 1083.373 954.05 1222.6 2367.5 100
# Reduce_TiC 383.9 499.25 661.475 586.40 687.6 5179.1 100
Код для теста:
microbenchmark::microbenchmark(
collapse = dapply(d, na_locf, MARGIN = 1),
dt = data.table::transpose(
setnafill(data.table::transpose(d, keep.names = "ID"),
type = "locf", cols = 1:nrow(d) + 1), make.names = "ID"),
tidyr = d %>%
pivot_longer(-ID) %>%
fill(value, .direction = "down") %>%
pivot_wider(),
Reduce_dt = d[, (sprintf("Var%d", 1:3)) := Reduce(\(x, y) ifelse(is.na(y), x, y), .SD, accumulate = TRUE), .SDcols = sprintf("Var%d", 1:3)],
Reduce_TiC = Reduce(\(x, y) ifelse(is.na(y), x, y), d[-1], accumulate = TRUE)
)
Отлично, коллапс — для меня новый пакет, это часть fastverse, который также включает data.table! :D
collapse — это совершенно новый мир, где каждая новая функция — это благословение. Мне очень нравится этот пакет! :)
Интересно, не слишком ли мал этот набор данных для сравнительного анализа? он содержит только 4 строки. Скорее всего, вы оцениваете в основном накладные расходы этих функций, а не алгоритма. Предлагаю увеличить количество строк
Используя команды set и fcoalesce из пакета data.table:
library(data.table)
# way 1
cols = c("Var1", "Var2", "Var3")
set(d, j=cols, value=Reduce(\(x, y) fcoalesce(y, x), d[cols], accumulate=TRUE))
# way 2
cols = c("Var2", "Var3")
p = d$Var1
for(cl in cols) set(d, j=cl, value=(p <- fcoalesce(d[[cl]], p)))
ID Var1 Var2 Var3
1 1 2 5 1
2 2 12 3 3
3 3 8 8 8
4 4 4 4 4
Интересный! Не знал
Reduce. Не могли бы вы объяснить\(x, y)?