the - 在data.frame中刪除具有NAs(缺失值)的行




r filter empty (10)

我想刪除此數據框中所有列中包含NA的行。 以下是我的示例數據框。

             gene hsap mmul mmus rnor cfam
1 ENSG00000208234    0   NA   NA   NA   NA
2 ENSG00000199674    0   2    2    2    2
3 ENSG00000221622    0   NA   NA   NA   NA
4 ENSG00000207604    0   NA   NA   1    2
5 ENSG00000207431    0   NA   NA   NA   NA
6 ENSG00000221312    0   1    2    3    2

基本上,我想獲得如下的數據框。

             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2

另外,我想知道如何只篩選一些列,所以我也可以得到這樣的數據框:

             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
4 ENSG00000207604    0   NA   NA   1    2
6 ENSG00000221312    0   1    2    3    2

如果性能是優先級,則使用data.tablena.omit() ,並使用可選參數cols=

na.omit.data.table是我的基準測試中最快的(見下文),無論是對於所有列還是對於選擇列(OP問題第2部分)。

如果您不想使用data.table ,請使用complete.cases()

在vanilla data.framecomplete.casesna.omit()dplyr::drop_na()更快。 請注意, na.omit.data.frame不支持cols=

基準測試結果

這裡是基礎(藍色), dplyr (粉色)和data.table (黃色)方法的比較,用於丟棄全部或選擇缺失的觀察值,對具有獨立5%可能性的20個數值變量的100萬觀察值的名義數據集進行比較缺失,第2部分為4個變量的子集。

您的結果可能因您的特定數據集的長度,寬度和稀疏程度而異。

請注意y軸上的日誌縮放。

基準腳本

#-------  Adjust these assumptions for your own use case  ------------
row_size   <- 1e6L 
col_size   <- 20    # not including ID column
p_missing  <- 0.05   # likelihood of missing observation (except ID col)
col_subset <- 18:21  # second part of question: filter on select columns

#-------  System info for benchmark  ----------------------------------
R.version # R version 3.4.3 (2017-11-30), platform = x86_64-w64-mingw32
library(data.table); packageVersion('data.table') # 1.10.4.3
library(dplyr);      packageVersion('dplyr')      # 0.7.4
library(tidyr);      packageVersion('tidyr')      # 0.8.0
library(microbenchmark)

#-------  Example dataset using above assumptions  --------------------
fakeData <- function(m, n, p){
  set.seed(123)
  m <-  matrix(runif(m*n), nrow=m, ncol=n)
  m[m<p] <- NA
  return(m)
}
df <- cbind( data.frame(id = paste0('ID',seq(row_size)), 
                        stringsAsFactors = FALSE),
             data.frame(fakeData(row_size, col_size, p_missing) )
             )
dt <- data.table(df)

par(las=3, mfcol=c(1,2), mar=c(22,4,1,1)+0.1)
boxplot(
  microbenchmark(
    df[complete.cases(df), ],
    na.omit(df),
    df %>% drop_na,
    dt[complete.cases(dt), ],
    na.omit(dt)
  ), xlab='', 
  main = 'Performance: Drop any NA observation',
  col=c(rep('lightblue',2),'salmon',rep('beige',2))
)
boxplot(
  microbenchmark(
    df[complete.cases(df[,col_subset]), ],
    #na.omit(df), # col subset not supported in na.omit.data.frame
    df %>% drop_na(col_subset),
    dt[complete.cases(dt[,col_subset,with=FALSE]), ],
    na.omit(dt, cols=col_subset) # see ?na.omit.data.table
  ), xlab='', 
  main = 'Performance: Drop NA obs. in select cols',
  col=c('lightblue','salmon',rep('beige',2))
)

使用dplyr包,我們可以如下過濾NA:

dplyr::filter(df,  !is.na(columnname))

另一種選擇,如果你想更好地控制行被認為是無效的是

final <- final[!(is.na(final$rnor)) | !(is.na(rawdata$cfam)),]

使用以上這些:

             gene hsap mmul mmus rnor cfam
1 ENSG00000208234    0   NA   NA   NA   2
2 ENSG00000199674    0   2    2    2    2
3 ENSG00000221622    0   NA   NA   2   NA
4 ENSG00000207604    0   NA   NA   1    2
5 ENSG00000207431    0   NA   NA   NA   NA
6 ENSG00000221312    0   1    2    3    2

變為:

             gene hsap mmul mmus rnor cfam
1 ENSG00000208234    0   NA   NA   NA   2
2 ENSG00000199674    0   2    2    2    2
3 ENSG00000221622    0   NA   NA   2   NA
4 ENSG00000207604    0   NA   NA   1    2
6 ENSG00000221312    0   1    2    3    2

...其中只有第5行被刪除,因為它是包含rnorcfam的唯一行。 然後可以更改布爾邏輯以適應特定需求。


另請檢查complete.cases

> final[complete.cases(final), ]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
6 ENSG00000221312    0    1    2    3    2

na.omit更適合於刪除所有NAcomplete.cases允許通過僅包含數據幀的某些列來進行部分選擇:

> final[complete.cases(final[ , 5:6]),]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
4 ENSG00000207604    0   NA   NA    1    2
6 ENSG00000221312    0    1    2    3    2

您的解決方案無法工作。 如果您堅持使用is.na ,那麼您必須執行以下操作:

> final[rowSums(is.na(final[ , 5:6])) == 0, ]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
4 ENSG00000207604    0   NA   NA    1    2
6 ENSG00000221312    0    1    2    3    2

但使用complete.cases更清晰,更快。


如果您想控制每行有效的NAs數量,請嘗試使用此功能。 對於許多調查數據集來說,太多空白的問題反應會破壞結果。 所以他們在一定的門檻後被刪除。 此功能將允許您選擇該行在刪除之前可以擁有多少個NAs:

delete.na <- function(DF, n=0) {
  DF[rowSums(is.na(DF)) <= n,]
}

默認情況下,它將消除所有的NAs:

delete.na(final)
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
6 ENSG00000221312    0    1    2    3    2

或者指定允許的最大NAs數量:

delete.na(final, 2)
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
4 ENSG00000207604    0   NA   NA    1    2
6 ENSG00000221312    0    1    2    3    2

對於你的第一個問題,我有一個我很熟悉的代碼來擺脫所有的NAs。 感謝@Gregor讓它變得更簡單。

final[!(rowSums(is.na(final))),]

對於第二個問題,代碼只是以前解決方案的一個替代方案。

final[as.logical((rowSums(is.na(final))-5)),]

請注意,-5是數據中的列數。 這將消除具有所有NA的行,因為rowSums加起來為5,並且它們在減法後變為零。 這一次,as.logical是必要的。


我是一個合成器:)。 在這裡我將答案組合成一個函數:

#' keep rows that have a certain number (range) of NAs anywhere/somewhere and delete others
#' @param df a data frame
#' @param col restrict to the columns where you would like to search for NA; eg, 3, c(3), 2:5, "place", c("place","age")
#' \cr default is NULL, search for all columns
#' @param n integer or vector, 0, c(3,5), number/range of NAs allowed.
#' \cr If a number, the exact number of NAs kept
#' \cr Range includes both ends 3<=n<=5
#' \cr Range could be -Inf, Inf
#' @return returns a new df with rows that have NA(s) removed
#' @export
ez.na.keep = function(df, col=NULL, n=0){
    if (!is.null(col)) {
        # R converts a single row/col to a vector if the parameter col has only one col
        # see https://radfordneal.wordpress.com/2008/08/20/design-flaws-in-r-2-%E2%80%94-dropped-dimensions/#comments
        df.temp = df[,col,drop=FALSE]
    } else {
        df.temp = df
    }

    if (length(n)==1){
        if (n==0) {
            # simply call complete.cases which might be faster
            result = df[complete.cases(df.temp),]
        } else {
            # credit: http://.com/a/30461945/2292993
            log <- apply(df.temp, 2, is.na)
            logindex <- apply(log, 1, function(x) sum(x) == n)
            result = df[logindex, ]
        }
    }

    if (length(n)==2){
        min = n[1]; max = n[2]
        log <- apply(df.temp, 2, is.na)
        logindex <- apply(log, 1, function(x) {sum(x) >= min && sum(x) <= max})
        result = df[logindex, ]
    }

    return(result)
}

我更喜歡以下方式來檢查行是否包含任何NAs:

row.has.na <- apply(final, 1, function(x){any(is.na(x))})

這將返回邏輯向量,其值表示一行中是否有任何NA。 您可以使用它來查看您必須刪除多少行:

sum(row.has.na)

並最終放棄它們

final.filtered <- final[!row.has.na,]

對於使用某些部分NAs過濾行,它變得有點棘手(例如,您可以將'final [,5:6]'提供給'apply')。 一般來說,Joris Meys的解決方案似乎更加優雅。


這將返回至少有一個非NA值的行。

final[rowSums(is.na(final))<length(final),]

這將返回至少有兩個非NA值的行。

final[rowSums(is.na(final))<(length(final)-1),]

delete.dirt <- function(DF, dart=c('NA')) {
  dirty_rows <- apply(DF, 1, function(r) !any(r %in% dart))
  DF <- DF[dirty_rows, ]
}

mydata <- delete.dirt(mydata)

上面的函數刪除任何列中具有“NA”的數據框中的所有行並返回結果數據。 如果你想檢查多個值,如NA? 將函數參數中的dart=c('NA')更改為dart=c('NA', '?')





r-faq