Как получить среднее значение ненулевых элементов по строке, изменяя, какие столбцы используются условием

#r #dplyr #data.table #boolean #aggregate

#r #dplyr #data.table #логическое #агрегировать

Вопрос:

Предположим, у меня есть следующая таблица данных:

   tempmat=matrix(c(1,1,0,4,1,0,0,4,0,1,0,4, 0,0,1,4, 0,0,0,5),5,4,byrow=T)
  tempmat=rbind(rep(0,4),tempmat)
  tempmat=data.table(tempmat)
  names(tempmat)=paste0('prod1vint',1:4)
  

Что выглядит так:

        prod1vint1 prod1vint2 prod1vint3 prod1vint4
1:          0          0          0          0
2:          1          1          0          4
3:          1          0          0          4
4:          0          1          0          4
5:          0          0          1          4
6:          0          0          0          5
  

Я хочу определить новый столбец, TN, который принимает среднее значение по строкам следующим образом.

  1. Для каждой строки найдите первый ненулевой элемент, идущий слева направо.
  2. Затем найдите среднее значение всех ненулевых элементов СПРАВА от этого.

Вывод должен быть:

    prod1vint1 prod1vint2 prod1vint3 prod1vint4   TN
1:          0          0          0          0   NA
2:          1          1          0          4   2.5
3:          1          0          0          4   4
4:          0          1          0          4   4
5:          0          0          1          4   4 
6:          0          0          0          5   NA
  

NA возникают потому, что в 1: нет ненулевых элементов, а в 6: нет ненулевых элементов справа от первого ненулевого элемента.

Комментарии:

1. может быть: tempmat[, TN := mean(unlist(.SD)[abs(.SD)>0][-1L]), by=tempmat[, seq_len(.N)]] ? но если вам нужна скорость, вы можете рассмотреть Rcpp

Ответ №1:

Вот один из вариантов с melt

 library(data.table)
library(dplyr)
TN <- melt(tempmat[, rid := seq_len(.N)], id.var = 'rid')[, 
    {i1 <- cumsum(value) > 0
    mean(na_if(value[i1][-1], 0), na.rm = TRUE)}, rid]$V1
tempmat[, TN := TN][]
  

Или используя tidyverse

 library(tidyverse)
tempmat %>% 
   mutate(TN = pmap(., ~ c(...) %>% 
           keep(., cumsum(.) > 0) %>%
           tail(-1) %>% 
           na_if(0) %>%
           mean(na.rm = TRUE)))
  

Или другой вариант — транспонировать набор данных, а затем выполнить операцию colwise

 t(tempmat) %>%
    as.data.frame %>% 
    summarise_all(list(~ mean(na_if(.[cumsum(.) > 0], 0)[-1],
          na.rm = TRUE))) %>%
    unlist %>%
    mutate(tempmat, TN = .)
  

Или используя векторизованный подход

 library(matrixStats)
m1 <- rowCumsums(as.matrix(tempmat)) > 0
m1[cbind(seq_len(nrow(m1)), max.col(m1, 'first'))] <- FALSE
rowMeans(na_if(tempmat * NA^!m1, 0), na.rm = TRUE)
  

Или используя apply

 apply(tempmat, 1, FUN = function(x) 
      mean(na_if(x[cumsum(x) > 0], 0)[-1], na.rm = TRUE))
  

Ответ №2:

Используя apply по строкам, мы можем сначала найти индексы в строке, которые не равны 0. Затем вычислите mean для ненулевых if значений, по крайней мере, одно ненулевое значение, а ненулевое значение отсутствует в последнем else возврате столбца NA .

 tempmat$TN <- apply(tempmat, 1, function(x) {
           inds <- x != 0
           if (any(inds) amp; which.max(inds) != length(x)) 
             mean(Filter(function(f) f > 0, x[(which.max(inds)   1) : length(x)]))
           else  
              NA
            })

tempmat
#   prod1vint1 prod1vint2 prod1vint3 prod1vint4  TN
#1:          0          0          0          0  NA
#2:          1          1          0          4 2.5
#3:          1          0          0          4 4.0
#4:          0          1          0          4 4.0
#5:          0          0          1          4 4.0
#6:          0          0          0          5  NA
  

Комментарии:

1. Интересно, вы думаете, что может быть что-то более эффективное, чем подход apply? Я считаю, что apply не особенно хорошо масштабируется.

Ответ №3:

Вы можете перебирать столбцы, работая только при ненулевом значении и после первого ненулевого столбца в этой строке:

 DT[, `:=`(n = 0L, s = 0, v = NA_real_)]
for (k in sprintf("prod1vint%s", 1:4)) 
  DT[get(k) != 0, `:=`(s = s   (n > 0)*get(k), n = n   1L)]
DT[n > 1L, v := s/(n - 1)][]

   prod1vint1 prod1vint2 prod1vint3 prod1vint4 n s   v
1:          0          0          0          0 0 0  NA
2:          1          1          0          4 3 5 2.5
3:          1          0          0          4 2 4 4.0
4:          0          1          0          4 2 4 4.0
5:          0          0          1          4 2 4 4.0
6:          0          0          0          5 1 0  NA
  

Поскольку это векторизовано, не принуждает к матрице и работает выборочно, я ожидаю, что это довольно эффективно. get Часть неудобна. но этого можно было бы избежать, например…

 DT[, `:=`(n = 0L, s = 0, v = NA_real_)]
for (k in sprintf("prod1vint%s", 1:4)){ 
  expr = substitute(DT[k != 0, `:=`(s = s   (n > 0)*k, n = n   1L)], list(k = as.name(k)))
  eval(expr)
}
DT[n > 1L, v := s/(n - 1)][]