如何按行获取非零元素的均值,如何改变条件使用的列

时间:2019-04-15 04:59:47

标签: r dplyr data.table boolean aggregate

假设我有以下数据表:

  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中:第一个非零元素的右边没有非零元素。

3 个答案:

答案 0 :(得分:2)

按行使用apply,我们可以首先在行中找出不为0的索引。然后计算mean的非零值if至少有一个非返回值else的最后一列NA中没有零值且非零值。

tempmat$TN <- apply(tempmat, 1, function(x) {
           inds <- x != 0
           if (any(inds) & 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 :(得分:2)

这是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)))

或者另一个选择是转置数据集,然后进行逐级操作

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 :(得分:0)

您可以遍历列,仅在非零列且在该行的第一个非零列之后运行:

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)][]