Заменить строку NA значением, отличным от NA, из предыдущей строки и определенного столбца

У меня есть матрица, в которой строки могут иметь NA для всех столбцов. Я хочу заменить эти строки NA значением предыдущей строки, отличным от NA, и столбцом K-th.

Например, эта матрица:

      [,1] [,2]
 [1,]   NA   NA
 [2,]   NA   NA
 [3,]    1    2
 [4,]    2    3
 [5,]   NA   NA
 [6,]   NA   NA
 [7,]   NA   NA
 [8,]    6    7
 [9,]    7    8
[10,]    8    9

Должны быть преобразованы в эту не-NA матрицу, где мы используем 2-й столбец для замены:

      [,1] [,2]
 [1,]   NA   NA
 [2,]   NA   NA
 [3,]    1    2
 [4,]    2    3
 [5,]    3    3
 [6,]    3    3
 [7,]    3    3
 [8,]    6    7
 [9,]    7    8
[10,]    8    9

Я написал для этого функцию, но используя цикл:

# replaces rows which contains all NAs with non-NA values from previous row and K-th column
na.replace <- function(x, k) {
    cols <- ncol(x)
    for (i in 2:nrow(x)) {
        if (sum(is.na(x[i - 1, ])) == 0 && sum(is.na(x[i, ])) == cols) {
            x[i, ] <- x[i - 1 , k]
        }
    }
    x
}

Кажется, эта функция работает правильно, но я хочу избежать этих циклов. Может ли кто-нибудь посоветовать, как я могу сделать эту замену без использования циклов?

ОБНОВЛЕНИЕ

agstudy предложил собственное векторизованное решение без цикла:

na.replace <- function(mat, k){
  idx       <-  which(rowSums(is.na(mat)) == ncol(mat))
  mat[idx,] <- mat[ifelse(idx > 1, idx-1, 1), k]
  mat
}

Но это решение возвращает разные и неправильные результаты по сравнению с моим решением с циклами. Почему это происходит? Теоретически петлевые и непетлевые решения идентичны.


r na
person Eldar Agalarov    schedule 09.05.2014    source источник


Ответы (4)


Попробуйте эту функцию. Мы можем заменить NA в любом месте вектора.

NA.replace <-function(x) {
       i <- cumprod(is.na(x))
       x[!!i] <- x[which.min(i)]
        if (length(x) > 0L) {
            non.na.idx <- which(!is.na(x))
            if (is.na(x[1L])) {
                non.na.idx <- c(1L, non.na.idx)
            }
            rep.int(x[non.na.idx], diff(c(non.na.idx, length(x) + 1L)))
        }  
}

NA.replace(c(NA, 1, 2, NA, NA, 3, NA, NA, 4, NA))

# [1] 1 1 2 2 2 3 3 3 4 4
person Jibin    schedule 09.11.2016

Я бы использовал функцию na.locf в цикле, который просто использует следующий столбец для создания вектора значений замены. Однако это может быть не очень эффективно, если ваша матрица большая.

library(zoo)

m <- cbind(
    c(NA, NA, 1, 2, NA, 4, NA, 6, 7, 8),
    c(NA, NA, 2, 3, NA, 5, NA, 7, 8, 9)
)

m[, ncol(m)] <- na.locf(m[, ncol(m)], na.rm=FALSE)

for (i in seq(ncol(m)-1, 1)) {
    replacement_values = na.locf(m[, i+1], na.rm=FALSE)
    m[is.na(m[, i]), i] <- replacement_values[is.na(m[, i])]    
}
person FascinatingFingers    schedule 09.05.2014
comment
Сначала я не заметил, что вменение происходит и в последнем столбце. Как вы предполагаете, чтобы это произошло? Является ли последний столбец обычным na.locf или значения в [5,2] и [7,2] взяты из предыдущего столбца в той же строке? - person FascinatingFingers; 10.05.2014
comment
Да, для последнего столбца результат такой же, как и для na.locf, потому что мы заменяем значения NA последнего столбца предыдущими значениями, не относящимися к NA, из того же последнего столбца. Это просто частный случай. - person Eldar Agalarov; 10.05.2014

РЕДАКТИРОВАТЬ: я полностью меняю первое решение, основанное на na.locf,

Вот новое векторизованное решение:

idx <- which(rowSums(is.na(mat)) == ncol(mat))
mat[idx,1:2]= mat[ifelse(idx>1,idx-1,1),2]

     X..1. X..2.
[1,]     NA    NA
[2,]     NA    NA
[3,]      1     2
[4,]      2     3
[5,]      3     3
[6,]      4     5
[7,]      5     5
[8,]      6     7
[9,]      7     8
[10,]     8     9

Вы можете обернуть это в функцию:

function(mat,k){
  idx       <-  which(rowSums(is.na(mat)) == ncol(mat))
  mat[idx,] <- mat[ifelse(idx>1,idx-1,1),k]
}
person agstudy    schedule 09.05.2014
comment
Я знаю про na.locf, но эта функция не подходит для моей задачи. Он заменяет NA предыдущими не-NA из того же столбца. Ваша выходная матрица неверна: строка 5 должна иметь значения (3, 3), а строка 7 - значения (5, 5). - person Eldar Agalarov; 10.05.2014
comment
Я обнаружил, что если хотя бы в двух последовательных строках есть NA, например, в 6-й и 7-й строках, то ваше решение заменяет только 6-ю строку, но не 7-ю строку. Я обновил свой пример в теме с лучшим вариантом. - person Eldar Agalarov; 10.05.2014

Наконец-то я реализовал собственную векторную версию. Он возвращает ожидаемый результат:

na.replace <- function(x, k) {
    isNA <- is.na(x[, k])
    x[isNA, ] <- na.locf(x[, k], na.rm = F)[isNA]
    x
}

ОБНОВЛЕНИЕ

Лучшее решение, без каких-либо пакетов

na.lomf <- function(x) {
    if (length(x) > 0L) {
        non.na.idx <- which(!is.na(x))
        if (is.na(x[1L])) {
            non.na.idx <- c(1L, non.na.idx)
        }
        rep.int(x[non.na.idx], diff(c(non.na.idx, length(x) + 1L)))
    }
}

na.lomf(c(NA, 1, 2, NA, NA, 3, NA, NA, 4, NA))
# [1] NA  1  2  2  2  3  3  3  4  4
person Eldar Agalarov    schedule 20.05.2014