Как я могу заполнить пустые ячейки в каждой строке фрейма данных значением в последней непустой ячейке в этой строке?

У меня есть фрейм данных (или таблица данных, если так проще) с неполными строками:

Вход

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

Как мне это сделать?


Я не хочу использовать dplyr и не хочу заполнять столбцы.

Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
0
180
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Транспонируйте и заполните недостающее, затем транспонируйте еще раз.

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

Интересный! Не знал Reduce. Не могли бы вы объяснить \(x, y)?

user24031531 28.05.2024 14:51

@Бен, это сокращенное выражение function(x,y) в последних версиях R.

ThomasIsCoding 28.05.2024 14:54
Ответ принят как подходящий

Другой ответ с использованием пакета 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

zx8754 28.05.2024 15:41
collapse — это совершенно новый мир, где каждая новая функция — это благословение. Мне очень нравится этот пакет! :)
Maël 28.05.2024 15:42

Интересно, не слишком ли мал этот набор данных для сравнительного анализа? он содержит только 4 строки. Скорее всего, вы оцениваете в основном накладные расходы этих функций, а не алгоритма. Предлагаю увеличить количество строк

B. Christian Kamgang 29.05.2024 11:15

Используя команды 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

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