【问题标题】:Applying custom function to each row uses only first value of argument将自定义函数应用于每一行仅使用参数的第一个值
【发布时间】:2019-02-07 15:01:54
【问题描述】:

我正在尝试使用以下数据集在列的子集中将 NA 值重新编码为 0

set.seed(1)
df <- data.frame(
  id = c(1:10),
  trials = sample(1:3, 10, replace = T),
  t1 = c(sample(c(1:9, NA), 10)),
  t2 = c(sample(c(1:7, rep(NA, 3)), 10)),
  t3 = c(sample(c(1:5, rep(NA, 5)), 10))
  )

每一行都有一定数量的相关试验(1-3 之间),由trials 列指定。列t1-t3 代表每个试验的分数。

试验次数表示应将NAs 重新编码为0 的列子集:试验次数内的NAs 表示缺失数据,应重新编码为0,而在试验次数之外的NAs 没有意义,应该保持NAs。因此,对于trials == 3 的行,t3 列中的NA 将被重新编码为0,但在trials == 2 的行中,t3 中的NA 仍将是NA .

所以,我尝试使用这个功能:

replace0 <- function(x, num.sun) {
  x[which(is.na(x[1:(num.sun + 2)]))] <- 0
  return(x)
}

这适用于单个向量。但是,当我尝试使用 apply() 将相同的函数应用于数据框时:

apply(df, 1, replace0, num.sun = df$trials)

我收到一条警告:

In 1:(num.sun + 2) :
  numerical expression has 10 elements: only the first used

结果是num.sun 的值不是根据trials 中的值更改每一行,apply() 只是对每一行使用trials 列中的第一个值。如何应用该函数以使num.sun 参数根据df$trials 的值而变化?

谢谢!

编辑:正如一些人评论的那样,原始示例数据有一些非 NA 分数,根据试验列没有意义。这是一个更正后的数据集:

df <- data.frame(
  id = c(1:5),
  trials = c(rep(1, 2), rep(2, 1), rep(3, 2)),
  t1 = c(NA, 7, NA, 6, NA),
  t2 = c(NA, NA, 3, 7, 12),
  t3 = c(NA, NA, NA, 4, NA)
)

【问题讨论】:

    标签: r apply na missing-data


    【解决方案1】:

    另一种方法:

    # create an index of the NA values
    w <- which(is.na(df), arr.ind = TRUE)
    
    # create an index with the max column by row where an NA is allowed to be replaced by a zero
    m <- matrix(c(1:nrow(df), (df$trials + 2)), ncol = 2)
    
    # subset 'w' such that only the NA's which fall in the scope of 'm' remain
    i <- w[w[,2] <= m[,2][match(w[,1], m[,1])],]
    
    # use 'i' to replace the allowed NA's with a zero
    df[i] <- 0
    

    给出:

    > df
       id trials t1 t2 t3
    1   1      1  3 NA  5
    2   2      2  2  2 NA
    3   3      2  6  6  4
    4   4      3  0  1  2
    5   5      1  5 NA NA
    6   6      3  7  0  0
    7   7      3  8  7  0
    8   8      2  4  5  1
    9   9      2  1  3 NA
    10 10      1  9  4  3
    

    您可以轻松地将其包装在一个函数中:

    replace.NA.with.0 <- function(df) {
      w <- which(is.na(df), arr.ind = TRUE)
      m <- matrix(c(1:nrow(df), (df$trials + 2)), ncol = 2)
      i <- w[w[,2] <= m[,2][match(w[,1], m[,1])],]
      df[i] <- 0
      return(df)
    }
    

    现在,使用replace.NA.with.0(df) 将产生上述结果。


    正如其他人所指出的,某些行(1、3 和 10)的值多于轨迹。您可以通过将上述函数重写为:

    replace.with.NA.or.0 <- function(df) {
      w <- which(is.na(df), arr.ind = TRUE)
      df[w] <- 0
    
      v <- tapply(m[,2], m[,1], FUN = function(x) tail(x:5,-1))
      ina <- matrix(as.integer(unlist(stack(v)[2:1])), ncol = 2)
      df[ina] <- NA
    
      return(df)
    }
    

    现在,使用replace.with.NA.or.0(df) 会产生以下结果:

       id trials t1 t2 t3
    1   1      1  3 NA NA
    2   2      2  2  2 NA
    3   3      2  6  6 NA
    4   4      3  0  1  2
    5   5      1  5 NA NA
    6   6      3  7  0  0
    7   7      3  8  7  0
    8   8      2  4  5 NA
    9   9      2  1  3 NA
    10 10      1  9 NA NA
    

    【讨论】:

      【解决方案2】:

      在这里,我只是使用双子集x[paste0('t',x['trials'])] 重写了您的函数,它通过 第 6 行

      克服了其他两个解决方案中的问题
      replace0 <- function(x){
               #browser()
               x_na <- x[paste0('t',x['trials'])]
               if(is.na(x_na)){x[paste0('t',x['trials'])] <- 0}
           return(x)
      }
      
      t(apply(df, 1, replace0))
      
           id trials t1 t2 t3
      [1,]  1      1  3 NA  5
      [2,]  2      2  2  2 NA
      [3,]  3      2  6  6  4
      [4,]  4      3 NA  1  2
      [5,]  5      1  5 NA NA
      [6,]  6      3  7 NA  0
      [7,]  7      3  8  7  0
      [8,]  8      2  4  5  1
      [9,]  9      2  1  3 NA
      [10,] 10      1  9  4  3
      

      【讨论】:

        【解决方案3】:

        这是一种方法:

        x <- is.na(df)
        df[x & t(apply(x, 1, cumsum)) > 3 - df$trials] <- 0
        

        输出如下:

        > df
           id trials t1 t2 t3
        1   1      1  3 NA  5
        2   2      2  2  2 NA
        3   3      2  6  6  4
        4   4      3  0  1  2
        5   5      1  5 NA NA
        6   6      3  7  0  0
        7   7      3  8  7  0
        8   8      2  4  5  1
        9   9      2  1  3 NA
        10 10      1  9  4  3
        > x <- is.na(df)
        > df[x & t(apply(x, 1, cumsum)) > 3 - df$trials] <- 0
        > df
           id trials t1 t2 t3
        1   1      1  3 NA  5
        2   2      2  2  2 NA
        3   3      2  6  6  4
        4   4      3  0  1  2
        5   5      1  5 NA NA
        6   6      3  7  0  0
        7   7      3  8  7  0
        8   8      2  4  5  1
        9   9      2  1  3 NA
        10 10      1  9  4  3
        

        注意:第 1/3/10 行存在问题,因为非 NA 值比试验多。

        【讨论】:

        • 第 3 行和第 10 行也有问题
        • @Moody_Mudskipper,感谢您的来信。我已经编辑了答案以反映这一点。
        【解决方案4】:

        这是tidyverse 方式,请注意,它不会提供与其他解决方案相同的输出。

        您的示例数据显示了“没有发生”的试验结果,我假设您的真实数据没有。

        library(tidyverse)
        df %>%
          nest(matches("^t\\d")) %>%
          mutate(data = map2(data,trials,~mutate_all(.,replace_na,0) %>% select(.,1:.y))) %>%
          unnest
        
        #    id trials t1 t2 t3
        # 1   1      1  3 NA NA
        # 2   2      2  2  2 NA
        # 3   3      2  6  6 NA
        # 4   4      3  0  1  2
        # 5   5      1  5 NA NA
        # 6   6      3  7  0  0
        # 7   7      3  8  7  0
        # 8   8      2  4  5 NA
        # 9   9      2  1  3 NA
        # 10 10      1  9 NA NA
        

        使用更常用的gather 策略如下:

        df %>%
          gather(k,v,matches("^t\\d")) %>%
          arrange(id) %>%
          group_by(id) %>%
          slice(1:first(trials)) %>%
          mutate_at("v",~replace(.,is.na(.),0)) %>%
          spread(k,v)
        
        # # A tibble: 10 x 5
        # # Groups:   id [10]
        #       id trials    t1    t2    t3
        #    <int>  <int> <dbl> <dbl> <dbl>
        #  1     1      1     3    NA    NA
        #  2     2      2     2     2    NA
        #  3     3      2     6     6    NA
        #  4     4      3     0     1     2
        #  5     5      1     5    NA    NA
        #  6     6      3     7     0     0
        #  7     7      3     8     7     0
        #  8     8      2     4     5    NA
        #  9     9      2     1     3    NA
        # 10    10      1     9    NA    NA
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-05
          • 2021-08-03
          • 2021-02-22
          • 1970-01-01
          • 2021-05-22
          相关资源
          最近更新 更多