Несколько наблюдений за идентификатор, создание столбца для последнего наблюдения [дубликат]

12
задан jenesaisquoi 12 November 2015 в 06:01
поделиться

5 ответов

Вот еще один способ:

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
10
ответ дан Frank 19 August 2018 в 15:08
поделиться

Я не уверен, как улучшить ответ @ alexis за пределами того, что @Frank уже сделал, но ваш оригинальный подход с базой R был не слишком далек от чего-то, что достаточно эффективно.

Вот вариант вашего подхода, который мне понравился, потому что (1) он достаточно быстр и (2) он не требует слишком много размышлений, чтобы выяснить, что происходит:

as.matrix(dat)[cbind(1:nrow(dat), max.col(!is.na(dat), "last"))] 

Самой дорогой частью этого является часть as.matrix(dat), но в остальном она кажется более быстрой, чем melt подход, которым поделился @akrun.

3
ответ дан A5C1D2H2I1M1N2O1R2T1 19 August 2018 в 15:08
поделиться

Мы преобразуем «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]
4
ответ дан akrun 19 August 2018 в 15:08
поделиться
  • 1
    @TheTime Да, вы можете сделать это так, но если нам нужно преобразовать из data.frame в data.table, то опции в setDT будут удобны. – akrun 12 November 2015 в 06:04
  • 2
    @TheTime Извините за это, я добавил несколько объяснений. value из имен столбцов по умолчанию после шага melt. – akrun 12 November 2015 в 06:09
  • 3
    Вот что-то смешное, что я придумал. Я сомневаюсь, что это достойно ответа, хотя: dat[, do.call(Map, c(function(...) tail(c(...)[!is.na(c(...))],1), lapply(dat,as.character)) )] – thelatemail 12 November 2015 в 06:24
  • 4
    Вы можете сбросить NA в расплаве: melt(dat[, r := .I], id="r", na.rm=TRUE)[, value[.N], by=r] – Frank 12 November 2015 в 07:30
  • 5
    @TheTime Ваш вариант .BY, вероятно, медленный, потому что перед ним выполняется операция по строке. Вместо этого ... 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
9
ответ дан alexis_laz 19 August 2018 в 15:08
поделиться
  • 1
    Да, я получил свою идею от одной из ваших предыдущих постов. Интересно, как он сравнивается с 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
  • 2
    @Frank: с приблизительным эталоном, Reduce.. действительно быстрее вашего первого подхода, но, я думаю, тройное чтение каждого столбца для +, ! и is.na добавляет некоторое время. Я не добавил max.col, потому что microbenchmark(as.matrix(DAT1)) кажется достаточно медленным для начала. – alexis_laz 12 November 2015 в 16:20
  • 3
    @TheTime: Использовали ли вы "data.table" в рекурсивной функции? У меня были некоторые проблемы с подмножеством data.table и сначала использовались as.list.data.table. – alexis_laz 12 November 2015 в 17:18
  • 4
    У меня была та же проблема, что и TheTime, но as.list разрешил ее, да. – Frank 12 November 2015 в 17:19
  • 5
    Добавлен еще один тест с вашей идеей, но в цикле с 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" 
3
ответ дан Colonel Beauvel 19 August 2018 в 15:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: