Вот еще один способ:
dat[, res := NA_character_]
for (v in rev(names(dat))[-1]) dat[is.na(res), res := get(v)]
X1 X2 X3 X4 X5 res
1: u NA NA NA NA u
2: f q NA NA NA q
3: f b w NA NA w
4: k g h NA NA h
5: u b r NA NA r
6: f q w x t t
7: u g h i e e
8: u q r n t t
Benchmarks Используя те же данные, что и @alexis_laz, и делая (по-видимому) поверхностные изменения функций, я вижу разные результаты. Просто покажите их здесь, если кому-то интересно.
Функции:
alex = function(x, ans = rep_len(NA, length(x[[1L]])), wh = seq_len(length(x[[1L]]))){
if(!length(wh)) return(ans)
ans[wh] = as.character(x[[length(x)]])[wh]
Recall(x[-length(x)], ans, wh[is.na(ans[wh])])
}
alex2 = function(x){
x[, res := NA_character_]
wh = x[, .I]
for (v in (length(x)-1):1){
if (!length(wh)) break
set(x, j="res", i=wh, v = x[[v]][wh])
wh = wh[is.na(x$res[wh])]
}
x$res
}
frank = function(x){
x[, res := NA_character_]
for(v in rev(names(x))[-1]) x[is.na(res), res := get(v)]
return(x$res)
}
frank2 = function(x){
x[, res := NA_character_]
for(v in rev(names(x))[-1]) x[is.na(res), res := .SD, .SDcols=v]
x$res
}
Пример данных и эталон:
DAT1 = as.data.table(lapply(ceiling(seq(0, 1e4, length.out = 1e2)),
function(n) c(rep(NA, n), sample(letters, 3e5 - n, TRUE))))
DAT2 = copy(DAT1)
DAT3 = as.list(copy(DAT1))
DAT4 = copy(DAT1)
library(microbenchmark)
microbenchmark(frank(DAT1), frank2(DAT2), alex(DAT3), alex2(DAT4), times = 30)
Unit: milliseconds
expr min lq mean median uq max neval
frank(DAT1) 850.05980 909.28314 985.71700 979.84230 1023.57049 1183.37898 30
frank2(DAT2) 88.68229 93.40476 118.27959 107.69190 121.60257 346.48264 30
alex(DAT3) 98.56861 109.36653 131.21195 131.20760 149.99347 183.43918 30
alex2(DAT4) 26.14104 26.45840 30.79294 26.67951 31.24136 50.66723 30
Я не уверен, как улучшить ответ @ alexis за пределами того, что @Frank уже сделал, но ваш оригинальный подход с базой R был не слишком далек от чего-то, что достаточно эффективно.
Вот вариант вашего подхода, который мне понравился, потому что (1) он достаточно быстр и (2) он не требует слишком много размышлений, чтобы выяснить, что происходит:
as.matrix(dat)[cbind(1:nrow(dat), max.col(!is.na(dat), "last"))]
Самой дорогой частью этого является часть as.matrix(dat)
, но в остальном она кажется более быстрой, чем melt
подход, которым поделился @akrun.
Мы преобразуем «data.frame» в «data.table» и создаем столбец идентификатора строки (setDT(df1, keep.rownames=TRUE)
). Мы переформатируем формат «широкий» в «длинный» с помощью melt
. В группе «rn», if
нет элемента NA
в столбце «значение», мы получаем последний элемент «value» (value[.N]
) или else
, мы получаем элемент перед первым NA в «значение», чтобы получить столбец «V1», который мы извлекаем ($V1
).
melt(setDT(df1, keep.rownames=TRUE), id.var='rn')[,
if(!any(is.na(value))) value[.N]
else value[which(is.na(value))[1]-1], by = rn]$V1
#[1] "u" "q" "w" "h" "r" "t" "e" "t"
В случае, если данные уже являются data.table
dat[, rn := 1:.N]#create the 'rn' column
melt(dat, id.var='rn')[, #melt from wide to long format
if(!any(is.na(value))) value[.N]
else value[which(is.na(value))[1]-1], by = rn]$V1
#[1] "u" "q" "w" "h" "r" "t" "e" "t"
Вот еще один вариант
dat[, colInd := sum(!is.na(.SD)), by=1:nrow(dat)][
, as.character(.SD[[.BY[[1]]]]), by=colInd]
Или как @Frank, упомянутый в комментариях, мы можем использовать na.rm=TRUE
из melt
и сделать его более компактным
melt(dat[, r := .I], id="r", na.rm=TRUE)[, value[.N], by=r]
data.frame
в data.table
, то опции в setDT
будут удобны.
– akrun
12 November 2015 в 06:04
value
из имен столбцов по умолчанию после шага melt
.
– akrun
12 November 2015 в 06:09
dat[, do.call(Map, c(function(...) tail(c(...)[!is.na(c(...))],1), lapply(dat,as.character)) )]
– thelatemail
12 November 2015 в 06:24
melt(dat[, r := .I], id="r", na.rm=TRUE)[, value[.N], by=r]
– Frank
12 November 2015 в 07:30
dat[, colInd := Reduce(function(x,y) x+!is.na(y), .SD, init=0L)][, res := as.character(.SD[[.BY[[1]]]]), by=colInd]
(Не уверен, хотите ли вы его изменить).
– Frank
12 November 2015 в 15:04
Другая идея - похожая на Фрэнка - пытается (1) избегать подмножества строк «data.table» (которые, как я полагаю, должна иметь некоторую стоимость) и (2), чтобы избежать проверки вектора length == nrow(dat)
для NA
s на каждой итерации.
alex = function(x, ans = rep_len(NA, length(x[[1L]])), wh = seq_len(length(x[[1L]])))
{
if(!length(wh)) return(ans)
ans[wh] = as.character(x[[length(x)]])[wh]
Recall(x[-length(x)], ans, wh[is.na(ans[wh])])
}
alex(as.list(dat)) #had some trouble with 'data.table' subsetting
# [1] "u" "q" "w" "h" "r" "t" "e" "t"
И сравнить с Франком:
frank = function(x)
{
x[, res := NA_character_]
for(v in rev(names(x))[-1]) x[is.na(res), res := get(v)]
return(x$res)
}
DAT1 = as.data.table(lapply(ceiling(seq(0, 1e4, length.out = 1e2)),
function(n) c(rep(NA, n), sample(letters, 3e5 - n, TRUE))))
DAT2 = copy(DAT1)
microbenchmark::microbenchmark(alex(as.list(DAT1)),
{ frank(DAT2); DAT2[, res := NULL] },
times = 30)
#Unit: milliseconds
# expr min lq median uq max neval
# alex(as.list(DAT1)) 102.9767 108.5134 117.6595 133.1849 166.9594 30
# { frank(DAT2) DAT2[, `:=`(res, NULL)] } 1413.3296 1455.1553 1497.3517 1540.8705 1685.0589 30
identical(alex(as.list(DAT1)), frank(DAT2))
#[1] TRUE
dat[, colInd := Reduce(function(x,y) x+!is.na(y), .SD, init=0L)][, res := as.character(.SD[[.BY[[1]]]]), by=colInd]
. Для нескольких cols и многих строк я думаю, что этот способ может быть довольно хорошим. Кроме того, было бы интересно посмотреть подход OP max.col
.
– Frank
12 November 2015 в 16:07
Reduce..
действительно быстрее вашего первого подхода, но, я думаю, тройное чтение каждого столбца для +
, !
и is.na
добавляет некоторое время. Я не добавил max.col
, потому что microbenchmark(as.matrix(DAT1))
кажется достаточно медленным для начала.
– alexis_laz
12 November 2015 в 16:20
as.list.data.table
.
– alexis_laz
12 November 2015 в 17:18
as.list
разрешил ее, да.
– Frank
12 November 2015 в 17:19
set
; это несколько быстрее.
– Frank
12 November 2015 в 18:47
Ниже приведен один подход base R
:
sapply(split(dat, seq(nrow(dat))), function(x) tail(x[!is.na(x)],1))
# 1 2 3 4 5 6 7 8
#"u" "q" "w" "h" "r" "t" "e" "t"
rev
. – akrun 12 November 2015 в 09:09