Xóa các hàng có tất cả hoặc một số NA (giá trị thiếu) trong data.frame


852

Tôi muốn xóa các dòng trong khung dữ liệu này:

a) chứa NAs trên tất cả các cột. Dưới đây là khung dữ liệu ví dụ của tôi.

             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

Về cơ bản, tôi muốn có được một khung dữ liệu như sau.

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

b) chỉ chứa NAs trong một số cột , vì vậy tôi cũng có thể nhận được kết quả này:

             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

Câu trả lời:


1063

Đồng thời kiểm tra 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.omitlà tốt hơn cho chỉ cần loại bỏ tất cả NA. complete.casescho phép lựa chọn một phần bằng cách chỉ bao gồm các cột nhất định của khung dữ liệu:

> 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

Giải pháp của bạn không thể làm việc. Nếu bạn khăng khăng sử dụng is.na, thì bạn phải làm một cái gì đó như:

> 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

nhưng sử dụng complete.casesthì rõ ràng hơn rất nhiều và nhanh hơn.


8
Ý nghĩa của dấu phẩy trong là final[complete.cases(final),]gì?
hertzsprung

6
@hertzsprung Bạn cần chọn hàng, không phải cột. Làm thế nào khác bạn sẽ làm điều đó?
Joris Meys

4
Có một phủ định đơn giản complete.cases? Nếu tôi muốn giữ các hàng với NA thay vì loại bỏ? final[ ! complete.cases(final),]không hợp tác ...
tumultous_rooster

2
finallà biến dữ liệu?
Morse

1
@Prateek thực sự, nó là.
Joris Meys

256

Hãy thử na.omit(your.data.frame). Đối với câu hỏi thứ hai, hãy thử đăng nó như một câu hỏi khác (cho rõ ràng).


na.omit giảm các hàng nhưng bảo toàn số hàng. Làm thế nào bạn sẽ sửa lỗi này để nó được đánh số đúng?
Gấu

3
@ Nếu bạn không quan tâm đến số hàng, chỉ cần làm rownames(x) <- NULL.
Roman Luštrik

xin lưu ý rằng na.omit()thả các hàng có NAtrong bất kỳ cột nào
Victor Maxwell

116

tidyrcó chức năng mới drop_na:

library(tidyr)
df %>% drop_na()
#              gene hsap mmul mmus rnor cfam
# 2 ENSG00000199674    0    2    2    2    2
# 6 ENSG00000221312    0    1    2    3    2
df %>% drop_na(rnor, cfam)
#              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

3
Không có kết nối thực sự giữa các đường ống và drop_na. Ví dụ, df %>% drop_na(), df %>% na.omit()drop_na(df)là tất cả về cơ bản tương đương.
Ista

4
@Ista tôi không đồng ý. na.omitthêm thông tin bổ sung như các chỉ số của các trường hợp bị bỏ qua và - quan trọng hơn - không cho phép bạn chọn các cột - đây là nơi drop_natỏa sáng.
lukeA

3
Chắc chắn, quan điểm của tôi là không phải điều đó có liên quan đến đường ống. Bạn có thể sử dụng na.omitcó hoặc không có đường ống, giống như bạn có thể sử dụng drop_nacó hoặc không có đường ống.
Ista

1
Đúng, không có gì để làm với đường ống cả. drop_na () chỉ là một hàm giống như bất kỳ hàm nào khác và, như vậy, có thể được gọi trực tiếp hoặc sử dụng đường ống. Thật không may, drop_na (), không giống như các phương thức được đề cập khác, không thể được sử dụng trên các loại đối tượng Zoo hoặc xts. Đây có thể là một vấn đề đối với một số người.
Dave

Phải, vì vậy tôi đã chỉnh sửa câu trả lời để nó không đề cập đến đường ống.
Arthur Yip

91

Tôi thích cách sau để kiểm tra xem các hàng có chứa bất kỳ NA nào không:

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

Điều này trả về vectơ logic với các giá trị biểu thị cho dù có bất kỳ NA nào trong một hàng hay không. Bạn có thể sử dụng nó để xem có bao nhiêu hàng bạn sẽ phải thả:

sum(row.has.na)

và cuối cùng thả chúng

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

Để lọc các hàng với một phần NA nhất định, nó trở nên phức tạp hơn một chút (ví dụ: bạn có thể cung cấp 'cuối cùng [, 5: 6]' để 'áp dụng'). Nói chung, giải pháp của Joris Meys dường như thanh lịch hơn.


2
Điều này là vô cùng chậm. Chậm hơn nhiều so với ví dụ như giải pháp Complete.case () đã nói ở trên. Ít nhất, trong trường hợp của tôi, trên dữ liệu xts.
Dave

3
rowSum(!is.na(final))có vẻ phù hợp hơnapply()
sindri_baldur

45

Một tùy chọn khác nếu bạn muốn kiểm soát nhiều hơn về cách các hàng được coi là không hợp lệ là

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

Sử dụng ở trên, điều này:

             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

Trở thành:

             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

... Trong đó chỉ có hàng 5 bị xóa vì đây là hàng duy nhất chứa NA cho cả rnorAND cfam. Logic boolean sau đó có thể được thay đổi để phù hợp với các yêu cầu cụ thể.


5
nhưng làm thế nào bạn có thể sử dụng cái này nếu bạn muốn kiểm tra nhiều cột, mà không cần gõ từng cột, bạn có thể sử dụng một phạm vi cuối cùng [, 4: 100] không?
Herman Toothrot

40

Nếu bạn muốn kiểm soát số lượng NA hợp lệ cho mỗi hàng, hãy thử chức năng này. Đối với nhiều bộ dữ liệu khảo sát, quá nhiều câu trả lời câu hỏi trống có thể làm hỏng kết quả. Vì vậy, chúng bị xóa sau một ngưỡng nhất định. Hàm này sẽ cho phép bạn chọn số lượng NA có thể có hàng trước khi bị xóa:

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

Theo mặc định, nó sẽ loại bỏ tất cả các NA:

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

Hoặc chỉ định số lượng NA tối đa được phép:

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

39

Nếu hiệu suất là ưu tiên, sử dụng data.tablena.omit()với tùy chọn param cols=.

na.omit.data.table là điểm nhanh nhất trong điểm chuẩn của tôi (xem bên dưới), cho dù cho tất cả các cột hoặc cho các cột được chọn (câu hỏi OP phần 2).

Nếu bạn không muốn sử dụng data.table, hãy sử dụngcomplete.cases() .

Trên một vani data.frame, complete.casesnhanh hơn na.omit()hoặcdplyr::drop_na() . Lưu ý rằng na.omit.data.framekhông hỗ trợ cols=.

Kết quả điểm chuẩn

Dưới đây là so sánh cơ sở (màu xanh), dplyr (màu hồng) và data.table(màu vàng) để loại bỏ tất cả hoặc chọn các quan sát bị thiếu, trên bộ dữ liệu đáng chú ý của 1 triệu quan sát của 20 biến số với khả năng bị mất 5% độc lập và tập hợp con của 4 biến cho phần 2.

Kết quả của bạn có thể thay đổi dựa trên chiều dài, chiều rộng và độ thưa của bộ dữ liệu cụ thể của bạn.

Lưu ý quy mô log trên trục y.

nhập mô tả hình ảnh ở đây

Kịch bản điểm chuẩn

#-------  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))
)

18

Sử dụng gói dplyr chúng ta có thể lọc NA như sau:

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

1
Điều này thực hiện chậm hơn khoảng 10.000 lần so vớidrop_na()
Zimano

17

Điều này sẽ trả về các hàng có ít nhất MỘT giá trị không NA.

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

Điều này sẽ trả về các hàng có ít nhất HAI giá trị không NA.

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

16

Đối với câu hỏi đầu tiên của bạn, tôi có một mã mà tôi cảm thấy thoải mái để loại bỏ tất cả các NA. Cảm ơn @Gregor để làm cho nó đơn giản hơn.

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

Đối với câu hỏi thứ hai, mã chỉ là một sự thay thế từ giải pháp trước đó.

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

Lưu ý -5 là số cột trong dữ liệu của bạn. Điều này sẽ loại bỏ các hàng với tất cả các NA, vì rowSums cộng lại thành 5 và chúng trở thành số 0 sau khi trừ. Lần này, as.logical là cần thiết.


cuối cùng [as.logical ((rowSums (is.na (Final)) - ncol (Final))),] cho một câu trả lời phổ quát
Ferroao

14

Chúng ta cũng có thể sử dụng chức năng tập hợp con cho việc này.

finalData<-subset(data,!(is.na(data["mmul"]) | is.na(data["rnor"])))

Điều này sẽ chỉ cung cấp cho những hàng không có NA ở cả mmul và rnor


9

Tôi là người tổng hợp :). Ở đây tôi kết hợp các câu trả lời thành một chức năng:

#' 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://stackoverflow.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)
}

8

Giả sử datnhư khung dữ liệu của bạn, đầu ra dự kiến ​​có thể đạt được bằng cách sử dụng

1.rowSums

> dat[!rowSums((is.na(dat))),]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2

2.lapply

> dat[!Reduce('|',lapply(dat,is.na)),]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2

7

Một cách tiếp cận đó là cả hai nói chung và mang lại đang khá có thể đọc được là sử dụng các filterchức năng và các biến thể của nó trong gói dplyr ( filter_all, filter_at, filter_if):

library(dplyr)

vars_to_check <- c("rnor", "cfam")

# Filter a specific list of columns to keep only non-missing entries
df %>% 
  filter_at(.vars = vars(one_of(vars_to_check)),
            ~ !is.na(.))

# Filter all the columns to exclude NA
df %>% 
  filter_all(~ !is.na(.))

# Filter only numeric columns
df %>%
  filter_if(is.numeric,
            ~ !is.na(.))

4
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)

Hàm trên xóa tất cả các hàng khỏi khung dữ liệu có 'NA' trong bất kỳ cột nào và trả về dữ liệu kết quả. Nếu bạn muốn kiểm tra nhiều giá trị như NA?thay đổi dart=c('NA')trong hàm param thànhdart=c('NA', '?')


3

Tôi đoán là điều này có thể được giải quyết thanh lịch hơn theo cách này:

  m <- matrix(1:25, ncol = 5)
  m[c(1, 6, 13, 25)] <- NA
  df <- data.frame(m)
  library(dplyr) 
  df %>%
  filter_all(any_vars(is.na(.)))
  #>   X1 X2 X3 X4 X5
  #> 1 NA NA 11 16 21
  #> 2  3  8 NA 18 23
  #> 3  5 10 15 20 NA

6
cái này sẽ giữ lại hàng với NA. Tôi nghĩ những gì OP muốn là:df %>% filter_all(all_vars(!is.na(.)))
asifzuba
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.