Hapus baris dengan semua atau beberapa NAS (nilai yang hilang) di data.frame


852

Saya ingin menghapus garis-garis dalam bingkai data ini yang:

a) mengandung NAs di semua kolom. Di bawah ini adalah contoh kerangka data saya.

             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

Pada dasarnya, saya ingin mendapatkan bingkai data seperti berikut ini.

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

b) mengandung NAs hanya dalam beberapa kolom , jadi saya juga bisa mendapatkan hasil ini:

             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

Jawaban:


1063

Juga periksa 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.omitlebih baik untuk menghapus semua NA. complete.casesmemungkinkan pemilihan sebagian dengan hanya memasukkan kolom tertentu dari bingkai data:

> 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

Solusi Anda tidak dapat bekerja. Jika Anda bersikeras menggunakan is.na, maka Anda harus melakukan sesuatu seperti:

> 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

tetapi menggunakan complete.casesjauh lebih jelas, dan lebih cepat.


8
Apa pentingnya tanda koma final[complete.cases(final),]?
hertzsprung

6
@hertzsprung Anda harus memilih baris, bukan kolom. Bagaimana lagi yang akan Anda lakukan itu?
Joris Meys

4
Apakah ada negasi sederhana complete.cases? Jika saya ingin menjaga baris dengan NAS daripada membuang? final[ ! complete.cases(final),]tidak bekerja sama ...
tumultous_rooster

2
finalapakah variabel dataframe?
Morse

1
@Prateek, memang.
Joris Meys

256

Coba na.omit(your.data.frame). Sedangkan untuk pertanyaan kedua, coba posting sebagai pertanyaan lain (untuk kejelasan).


na.omit menjatuhkan baris tetapi mempertahankan nomor baris. Bagaimana Anda memperbaikinya sehingga diberi nomor yang benar?
Beruang

3
@Bear Jika Anda tidak peduli dengan nomor baris, lakukan saja rownames(x) <- NULL.
Roman Luštrik

harap dicatat bahwa na.omit()menjatuhkan baris yang berisi NAdalam kolom apa pun
Victor Maxwell

116

tidyrmemiliki fungsi baru 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
Tidak ada hubungan nyata antara pipa dan drop_na. Sebagai contoh, df %>% drop_na(), df %>% na.omit()dan drop_na(df)semua pada dasarnya sama.
Ista

4
@ Ista saya tidak setuju. na.omitmenambahkan info tambahan seperti indeks case yang dihilangkan, dan - yang lebih penting - adalah tidak memungkinkan Anda untuk memilih kolom - ini adalah tempat drop_nabersinar.
Lukas

3
Tentu, maksud saya adalah bahwa semua itu tidak ada hubungannya dengan pipa. Anda dapat menggunakan na.omitdengan atau tanpa pipa, sama seperti Anda dapat menggunakan drop_nadengan atau tanpa pipa.
Ista

1
Benar, tidak ada hubungannya sama sekali dengan pipa. drop_na () hanyalah fungsi seperti yang lainnya dan, dengan demikian, dapat dipanggil langsung atau menggunakan pipa. Sayangnya, drop_na (), tidak seperti metode lain yang disebutkan, tidak dapat digunakan pada tipe objek kebun binatang atau xts. Ini bisa menjadi masalah bagi sebagian orang.
Dave

Benar, jadi saya mengedit jawabannya sehingga tidak menyebutkan pipa.
Arthur Yip

91

Saya lebih suka cara berikut untuk memeriksa apakah baris mengandung NAS:

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

Ini mengembalikan vektor logis dengan nilai yang menunjukkan apakah ada NA dalam satu baris. Anda dapat menggunakannya untuk melihat berapa banyak baris yang harus Anda jatuhkan:

sum(row.has.na)

dan akhirnya menjatuhkan mereka

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

Untuk menyaring baris dengan bagian NAS tertentu, ini menjadi sedikit lebih rumit (misalnya, Anda dapat memberi makan 'akhir [, 5: 6]' untuk 'diterapkan'). Secara umum, solusi Joris Meys tampaknya lebih elegan.


2
Ini sangat lambat. Jauh lebih lambat daripada solusi complete.cases () yang disebutkan sebelumnya. Setidaknya, dalam kasus saya, pada data xts.
Dave

3
rowSum(!is.na(final))tampaknya lebih cocok daripadaapply()
sindri_baldur

45

Pilihan lain jika Anda ingin kontrol lebih besar atas bagaimana baris dianggap tidak valid adalah

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

Menggunakan di atas, ini:

             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

Menjadi:

             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

... di mana hanya baris 5 yang dihapus karena ini adalah satu-satunya baris yang mengandung NAS untuk keduanya rnorDAN cfam. Logika boolean kemudian dapat diubah agar sesuai dengan persyaratan spesifik.


5
tetapi bagaimana Anda bisa menggunakan ini jika Anda ingin memeriksa banyak kolom, tanpa mengetik masing-masing, dapatkah Anda menggunakan rentang akhir [, 4: 100]?
Herman Toothrot

40

Jika Anda ingin mengontrol berapa banyak NA yang valid untuk setiap baris, coba fungsi ini. Untuk banyak set data survei, terlalu banyak respons pertanyaan kosong dapat merusak hasil. Jadi mereka dihapus setelah batas tertentu. Fungsi ini memungkinkan Anda untuk memilih berapa banyak NA yang bisa dimiliki baris sebelum dihapus:

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

Secara default, ini akan menghilangkan semua NAS:

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

Atau tentukan jumlah maksimum NA yang diizinkan:

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

Jika kinerja adalah prioritas, gunakan data.tabledan na.omit()dengan param opsional cols=.

na.omit.data.table adalah yang tercepat di tolok ukur saya (lihat di bawah), baik untuk semua kolom atau untuk kolom pilih (pertanyaan OP bagian 2).

Jika Anda tidak ingin menggunakan data.table, gunakan complete.cases().

Pada vanila data.frame, complete.caseslebih cepat dari na.omit()atau dplyr::drop_na(). Perhatikan bahwa na.omit.data.frametidak mendukung cols=.

Hasil benchmark

Berikut ini adalah perbandingan metode dasar (biru), dplyr(merah muda), dan data.table(kuning) untuk menjatuhkan semua atau memilih pengamatan yang hilang, pada dataset nosional dari 1 juta pengamatan dari 20 variabel numerik dengan kemungkinan independen 5% kemungkinan hilang, dan subset dari 4 variabel untuk bagian 2.

Hasil Anda dapat bervariasi berdasarkan panjang, lebar, dan tingkat dataset tertentu Anda.

Catat skala log pada sumbu y.

masukkan deskripsi gambar di sini

Skrip patokan

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

Menggunakan paket dplyr, kita dapat memfilter NA sebagai berikut:

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

1
Ini melakukan sekitar 10.000 kali lebih lambat daripadadrop_na()
Zimano

17

Ini akan mengembalikan baris yang memiliki setidaknya SATU nilai non-NA.

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

Ini akan mengembalikan baris yang memiliki setidaknya DUA nilai non-NA.

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

16

Untuk pertanyaan pertama Anda, saya memiliki kode yang saya rasa nyaman untuk menghilangkan semua NAS. Terima kasih atas @Gregor untuk membuatnya lebih sederhana.

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

Untuk pertanyaan kedua, kode tersebut hanyalah pergantian dari solusi sebelumnya.

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

Perhatikan bahwa -5 adalah jumlah kolom dalam data Anda. Ini akan menghilangkan baris dengan semua NAS, karena rowSum menambah hingga 5 dan mereka menjadi nol setelah dikurangi. Kali ini, as.logical diperlukan.


final [as.logical ((rowSums (is.na (final)) - ncol (final))),] untuk jawaban universal
Ferroao

14

Kita juga bisa menggunakan fungsi subset untuk ini.

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

Ini hanya akan memberikan baris yang tidak memiliki NA pada mmul dan rnor


9

Saya seorang synthesizer :). Di sini saya menggabungkan jawaban menjadi satu fungsi:

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

Dengan asumsi datsebagai kerangka data Anda, output yang diharapkan dapat dicapai dengan menggunakan

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

Salah satu pendekatan yang baik umum dan menghasilkan kode cukup dibaca adalah dengan menggunakan filterfungsi dan variannya dalam paket 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)

Fungsi di atas menghapus semua baris dari bingkai data yang memiliki 'NA' di kolom apa pun dan mengembalikan data yang dihasilkan. Jika Anda ingin memeriksa beberapa nilai suka NAdan ?ubah dart=c('NA')fungsi param menjadidart=c('NA', '?')


3

Dugaan saya adalah bahwa ini bisa diselesaikan dengan lebih elegan dengan cara ini:

  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
ini akan mempertahankan baris dengan NA. Saya pikir yang diinginkan OP adalah:df %>% filter_all(all_vars(!is.na(.)))
asifzuba
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.