R - подсчитать в каждой строке количество столбцов в окрестности строки, которые содержат только NA

Как бы вы создали из фрейма данных вектор, который дает для каждой строки количество столбцов, которые являются «NA» (или пользовательским значением) в этой строке и в строках n выше и m строки ниже.

Итак, если m = n = 1 (т.е. сколько столбцов в каждой строке являются NA и имеют NA непосредственно до и после), и мой фрейм данных

structure(list(X = 1:8, A = c(3L, NA, 10L, NA, 6L, NA, 5L, NA
), B = c(6L, NA, NA, NA, 8L, NA, 13L, NA), C = c(NA, 12L, 14L,  
NA, NA, NA, 9L, NA), D = c(NA, NA, NA, NA, NA, 11L, 7L, NA)), .Names = c("X", 
"A", "B", "C", "D"), class = "data.frame", row.names = c(NA, 
-8L))

i.e.

 t X  A  B  C  D
1 1  3  6 NA NA 
2 2 NA NA 12 NA 
3 3 10 NA 14 NA 
4 4 NA NA NA NA
5 5  6  8 NA NA
6 6 NA NA NA 11
7 7  5 13  9  7
8 8 NA NA NA NA

Я хочу вектор

count
0
1
2
1
1
0
0
0

(если первая и последняя записи NA, это нормально). Я пытаюсь имитировать функцию COUNTIFS в Excel, то есть COUNTIFS(B2:F2,"",B3:F3,"",B4:F4,"") для строки 3.


person Hugh    schedule 09.04.2013    source источник
comment
Я не понимаю, как ваше описание приводит к желаемому результату. Как получается, что в этом счете есть 1 в строке 2?   -  person Henrik    schedule 09.04.2013
comment
Столбец D имеет NA в строке 2, а также в строках 1 и 3. В других столбцах нет, поэтому count там равно 1.   -  person Hugh    schedule 09.04.2013
comment
@Henrik Я думаю, что они имеют в виду: предположим, мы смотрим на строку i, столбец j: если в столбце j есть NA для n строк до и m строк после строки i, мы считаем это. Таким образом, во второй строке только NA в столбце «D» имеет NA непосредственно над и под ним.   -  person mathematical.coffee    schedule 09.04.2013
comment
Да, математический.кофе правильный.   -  person Hugh    schedule 09.04.2013
comment
@ Хью, могу я получить разъяснение - в строке 1 NA в столбце D есть NA после него, но нет перед ним, потому что нет предыдущей строки - вы хотите, чтобы это всегда возвращало 0? (т.е. первый и последний элементы вашего вектора count всегда равны 0?)   -  person mathematical.coffee    schedule 09.04.2013
comment
Возвратите 0 или NA, но я не суетился. Истинное значение в этой строке состоит в том, что условие бессмысленно, но меня устраивает 0. Для более высоких m или n то же самое будет верно для первого n и последние m строк.   -  person Hugh    schedule 09.04.2013


Ответы (5)


Думаю, я понимаю, что вы имеете в виду.

Предположим, что кадр данных называется x.

Во-первых, для каждого (row, column) в x нам нужно увидеть, есть ли NA в этой ячейке И NA в том же столбце для n строк до и m строк после.

Во-первых, давайте сделаем это в случае одной строки, скажем, строки i = 2. У нас также есть n = 1 и m = 1 (из примера в вопросе).

i <- 2
n <- 1
m <- 1

Давайте подсчитаем количество NA в каждом столбце для строк с i - n по i + m включительно (is.na возвращает TRUE, если текущее значение равно NA, colSums дает суммы столбцов)

y <- colSums(is.na(x[(i - n):(i + m), ]))
# X A B C D 
# 0 1 2 1 3 

Теперь у нас есть только NA в предыдущей, текущей и следующей строке, если мы насчитали 3 NAs (т.е. здесь подходит только столбец D):

y == n + m + 1
#     X     A     B     C     D 
# FALSE FALSE FALSE FALSE  TRUE

поэтому количество столбцов, удовлетворяющих нашим критериям (отсюда и ith элемент вывода), равно:

sum(y == n + m + 1)
# 1 

Затем мы можем использовать sapply, чтобы применить это к каждой строке:

countifs <- function (df, n, m) {
    sapply(1:nrow(df),
           function (i) {
               nrows <- nrow(df)
               startRow <- max(i - n, 1)
               endRow   <- min(i + m, nrows)
               y <- colSums(is.na(x[startRow:endRow, ]))
               sum(y == n + m + 1)
           })
}

countifs(x, 1, 1)
# [1] 0 1 2 1 1 0 0 0

Вы также упомянули, что, возможно, захотите сравнить с пользовательским значением, а не с NA. В этом случае вместо is.na(x[...]) вы можете просто сделать x[...] == value (но не в том случае, если value равно NA, в котором вы используете is.na)

Кроме того, вы можете сэкономить немного работы, используя только sapply в строках с n + 1 по nrow(df) - m - 1 и автоматически устанавливая первый n и последний m элементы равными 0.

person mathematical.coffee    schedule 09.04.2013
comment
Все ответы очень полезны, но это ответило на все. Благодарю вас! - person Hugh; 09.04.2013

Это должно привести к желаемому результату -

y = is.na(yourDataFrame)

rowSums(y & rbind(rep(F,5), y[-nrow(yourDataFrame),]) & rbind(y[-1,], rep(F,5)))
person Nishanth    schedule 09.04.2013
comment
Ух ты. Я впечатлен. Вот версия с использованием m и n: rowSums(y & rbind(matrix(FALSE, nrow = m, ncol = 5), y[-((length(yourDataFrame)-(m-1)):length(yourDataFrame)),]) & rbind(y[-(1:n),], matrix(FALSE, nrow = n, ncol = 5))) - person Henrik; 09.04.2013
comment
@ Хенрик нет, ваша версия подсчитывает число, в котором столбец n строк до и m строк после является NA, а не где все строки от n до m после являются NA (в этом столбце). Очень нравится гладкость этого ответа, хотя, к сожалению, он не масштабируется до произвольных n и m - person mathematical.coffee; 09.04.2013
comment
@mathematical.coffee Согласен. Я пропустил этот момент в описании. И кажется сложным адаптировать это решение к желаемому поведению. - person Henrik; 09.04.2013

Вот функция, которая идентифицирует значения NA в центре прогона length = .length.

foo <- function(x,.length){
  x <- is.na(x)
  if( .length < 2L ||.length %%2L == 0L ){stop('.length must be an odd number greater than 2')}
  lx <- length(x)
  if(lx <.length) {return(rep_len(FALSE, lx))}
  midpoints <- seq.int(2L, lx-1L, by = 1L)
  c(FALSE,sapply(midpoints, function(xx) all(x[(xx-1L):(xx+1L)])),FALSE)
}

Мы можем использовать это с rowSums и sapply, чтобы получить то, что вы хотите.

rowSums(sapply(xx, foo, .length = 3))
## [1] 0 1 2 1 1 0 0 0

не изобретая велосипед (то, что катится)

Или вы можете использовать rollapply из пакета zoo

library(zoo)
rowSums(sapply(xx, function(x) {
    rollapply(is.na(x), width = 3, fill = FALSE, FUN = all)
   }))

или даже просто

rowSums(rollapply(is.na(xx),width=3, FUN=all, fill = FALSE))
person mnel    schedule 09.04.2013

Я не смог придумать векторизованную версию, поэтому вот пример с использованием старого доброго цикла for:

x <- structure(list(X = 1:8, A = c(3L, NA, 10L, NA, 6L, NA, 5L, NA
), B = c(6L, NA, NA, NA, 8L, NA, 13L, NA), C = c(NA, 12L, 14L,  
NA, NA, NA, 9L, NA), D = c(NA, NA, NA, NA, NA, 11L, 7L, NA)), .Names = c("X", 
"A", "B", "C", "D"), class = "data.frame", row.names = c(NA, 
-8L))

y <- x
y[is.na(y)] <- -99
out <- vector("numeric", nrow(y))

n <- 1
m <- 1
for (c in (1+n):(nrow(y)-m)) {
    out[c] <- sum((y[(c-n),] == -99) & (y[(c),] == -99) & (y[(c+m),] == -99))
}
out

Это должно помочь (даже с учетом n и m):

> out
[1] 0 1 2 1 1 0 0 0

Обратите внимание, что я использую два приема. Так как работать с NAs проблематично, я заменяю их на -99 (хотя это и не обязательно). Тогда я не зацикливаюсь на первых n и последних m строках.

Если кто-то придумает векторизованную версию, он / она определенно получит мой голос.

person Henrik    schedule 09.04.2013
comment
Да, это решает случай *n*=*m*=1. (Общий случай, вероятно, требует слишком многого.) Есть ли какая-то конкретная причина, по которой вы выбрали -99? - person Hugh; 09.04.2013
comment
Как было замечено в Math.coffee выше, использование m и n здесь некорректно. вместо этого используйте rollapply. - person Henrik; 09.04.2013

Вы можете использовать describe только для получения количества NA в каждом столбце.

describe(traindata)

Вывод выглядит следующим образом:

Column_3 
      n missing  unique    Mean     .05     .10     .25     .50     .75     .90     .95 
    646      23     283  0.2792  0.0000  0.0000  0.0000  0.1455  0.4798  0.9305  1.0000 

Проверьте значения missing.

person Panos Kal.    schedule 09.04.2013
comment
Я не думаю, что это будет работать для этого. Мне нужно знать позицию строки условия NA, а не только общее количество случаев этого условия для каждого столбца. Извините, мой заголовок сбивает с толку. - person Hugh; 09.04.2013