Применение пользовательской функции к каждой строке использует только первое значение аргумента - PullRequest
0 голосов
/ 02 сентября 2018

Я пытаюсь перекодировать 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 представляют оценки для каждого испытания.

Количество испытаний указывает на подмножество столбцов, в которых NA s должно быть перекодировано в 0: NA s, которые находятся в пределах числа испытаний, представляют пропущенные данные и должны быть перекодированы как 0, в то время как NA s вне числа испытаний не имеет смысла, и должно оставаться NA s. Так, для строки, где trials == 3, NA в столбце t3 будет перекодировано как 0, но в строке, где trials == 2, NA в t3 останется 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)
)

Ответы [ 4 ]

0 голосов
/ 02 сентября 2018

Вот способ 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
0 голосов
/ 02 сентября 2018

Здесь я просто переписываю вашу функцию, используя двойное подмножество 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
0 голосов
/ 02 сентября 2018

Другой подход:

# 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
0 голосов
/ 02 сентября 2018

Вот способ сделать это:

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, чем в исследованиях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...