Подтвердить что ты не робот

Подмножество data.table с использованием!= <некоторые не-NA> исключает NA тоже

У меня есть таблица данных с столбцом с NA s. Я хочу удалить строки, где этот столбец принимает определенное значение (что бывает ""). Однако моя первая попытка привела к тому, что я потерял строки с помощью NA:

> a = c(1,"",NA)
> x <- data.table(a);x
    a
1:  1
2:   
3: NA
> y <- x[a!=""];y
   a
1: 1

Посмотрев на ?`!=`, я нашел один лайнер, который работает, но это боль:

> z <- x[!sapply(a,function(x)identical(x,""))]; z
    a
1:  1
2: NA

Мне интересно, есть ли лучший способ сделать это? Кроме того, я не вижу хорошего способа расширить это до исключения нескольких значений не NA. Здесь плохой путь:

>     drop_these <- function(these,where){
+         argh <- !sapply(where,
+             function(x)unlist(lapply(as.list(these),function(this)identical(x,this)))
+         )
+         if (is.matrix(argh)){argh <- apply(argh,2,all)}
+         return(argh)
+     }
>     x[drop_these("",a)]
    a
1:  1
2: NA
>     x[drop_these(c(1,""),a)]
    a
1: NA

Я посмотрел на ?J и попробовал вещи с помощью data.frame, который, похоже, работает по-другому, сохраняя NA при подмножестве:

> w <- data.frame(a,stringsAsFactors=F); w
     a
1    1
2     
3 <NA>
> d <- w[a!="",,drop=F]; d
      a
1     1
NA <NA>
4b9b3361

Ответ 1

Чтобы решить вопрос:

Вы должны использовать %in%. Это возвращает логический вектор.

a %in% ""
# [1] FALSE  TRUE FALSE

x[!a %in% ""]
#     a
# 1:  1
# 2: NA

Чтобы узнать, почему это происходит в data.table:

(как показано на data.frame)

Если вы посмотрите исходный код data.table в файле data.table.R в функции "[.data.table", там есть набор if-statements, который проверяет аргумент i. Один из них:

if (!missing(i)) {
    # Part (1)
    isub = substitute(i)

    # Part (2)
    if (is.call(isub) && isub[[1L]] == as.name("!")) {
        notjoin = TRUE
        if (!missingnomatch) stop("not-join '!' prefix is present on i but nomatch is provided. Please remove nomatch.");
        nomatch = 0L
        isub = isub[[2L]]
    }

    .....
    # "isub" is being evaluated using "eval" to result in a logical vector

    # Part 3
    if (is.logical(i)) {
        # see DT[NA] thread re recycling of NA logical
        if (identical(i,NA)) i = NA_integer_  
        # avoids DT[!is.na(ColA) & !is.na(ColB) & ColA==ColB], just DT[ColA==ColB]
        else i[is.na(i)] = FALSE  
    }
    ....
}

Чтобы объяснить несоответствие, я вставил здесь важный кусок кода. И я также отметил их на 3 части.

Во-первых, почему dt[a != ""] работает не так, как ожидалось (с помощью OP)?

Сначала part 1 оценивает объект класса call. Вторая часть оператора if в part 2 возвращает FALSE. После этого call оценивается, чтобы дать c(TRUE, FALSE, NA). Затем выполняется part 3. Таким образом, NA заменяется на FALSE (последняя строка логического цикла).

почему x[!(a== "")] работает как ожидалось (по OP)?

part 1 снова возвращает вызов. Но part 2 имеет значение TRUE и поэтому устанавливает:

1) `notjoin = TRUE`
2) isub <- isub[[2L]] # which is equal to (a == "") without the ! (exclamation)

Вот где произошло волшебство. На данный момент отрицание отменено. И помните, что это все еще объект вызова класса. Таким образом, это оценивается (используя eval) для логического повторения. Итак, (a=="") оценивается до c(FALSE, TRUE, NA).

Теперь это отмечено для is.logical в part 3. Итак, здесь NA заменяется на FALSE. Поэтому он становится c(FALSE, TRUE, FALSE). В какой-то момент позже выполняется which(c(F,T,F)), что приводит к 2 здесь. Потому что возвращается notjoin = TRUE (from part 2) seq_len(nrow(x))[-2]= c (1,3). поэтому x[!(a=="")] в основном возвращает x[c(1,3)], что является желаемым результатом. Вот соответствующий фрагмент кода:

if (notjoin) {
    if (bywithoutby || !is.integer(irows) || is.na(nomatch)) stop("Internal error: notjoin but bywithoutby or !integer or nomatch==NA")
    irows = irows[irows!=0L]
    # WHERE MAGIC HAPPENS (returns c(1,3))
    i = irows = if (length(irows)) seq_len(nrow(x))[-irows] else NULL  # NULL meaning all rows i.e. seq_len(nrow(x))
    # Doing this once here, helps speed later when repeatedly subsetting each column. R [irows] would do this for each
    # column when irows contains negatives.
}

Учитывая это, я думаю, что есть некоторые несоответствия с синтаксисом. И если мне удастся найти время, чтобы сформулировать проблему, я скоро напишу сообщение.

Ответ 2

Как вы уже выяснили, это причина:

a != ""
#[1]  TRUE    NA FALSE

Вы можете сделать то, что вы уже выяснили, т.е. x[is.na(a) | a != ""], или вы можете setkey на a и выполните следующие действия:

setkey(x, a)
x[!J("")]

Ответ 3

Справочный ответ от Матфея:

Поведение с != на NA, как было подчеркнуто в этом вопросе, не предназначалось, думая об этом. Первоначальное намерение действительно отличалось от [.data.frame w.r.t. == и NA, и я считаю, что все довольны этим. Например, в FAQ 2.17 есть:

DT[ColA==ColB] проще, чем DF[!is.na(ColA) & !is.na(ColB) & ColA==ColB,]

Это удобство достигается с помощью:

DT[c(TRUE,NA,FALSE)] рассматривает NA как FALSE, но DF[c(TRUE,NA,FALSE)]возвращает NA строки для каждого NA

Мотивация - это не просто удобство, а скорость, так как каждый !, is.na, & и == являются самими векторными сканами с соответствующим распределением памяти каждого из их результатов (объясняется во встроенной виньетке), Поэтому, хотя x[is.na(a) | a!=""] является рабочим решением, это именно тот тип логики, который я пытался избежать в data.table. x[!a %in% ""] немного лучше; то есть 2 сканирования (%in% и !), а не 3 (is.na, | и !=). Но действительно x[a != ""] должен делать то, что ожидал Фрэнк (включить NA) в одно сканирование.

Добавлен новый запрос функции, который ссылается на этот вопрос:

DT [col != "" ] должен включать NA

Спасибо Фрэнку, Эдди и Аруну. Если я не понял правильно, не стесняйтесь исправить, иначе это будет сделано в конечном итоге. Это нужно сделать так, чтобы рассматривать составные выражения; например, DT[colA=="foo" & colB!="bar"] следует исключать строки с NA в colA, но включать строки, где colA не NA, а colB - NA. Аналогично, DT[colA!=colB] должен содержать строки, где либо colA, либо colB NA, но не оба. И, возможно, DT[ColA==ColB] должен включать строки, где оба colA и colB являются NA (которые, как я полагаю, в настоящее время не существует).