ลบแถวที่มี NA ทั้งหมดหรือบางส่วน (ค่าที่หายไป) ใน data.frame


852

ฉันต้องการลบบรรทัดในกรอบข้อมูลนี้ที่:

ก) มี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

โดยทั่วไปฉันต้องการรับ data frame ดังต่อไปนี้

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

b) มีNAs ในบางคอลัมน์เท่านั้นดังนั้นฉันสามารถรับผลลัพธ์นี้:

             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

คำตอบ:


1063

ตรวจสอบด้วย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เป็นเพียงดีกว่าเอาNA's complete.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ค่อนข้างชัดเจนและเร็วขึ้น


8
เครื่องหมายจุลภาคต่อท้ายมีความสำคัญfinal[complete.cases(final),]อย่างไร
hertzsprung

6
@hertzsprung คุณต้องเลือกแถวไม่ใช่คอลัมน์ คุณจะทำอย่างอื่นได้อย่างไร
Joris Meys

4
มีการปฏิเสธง่ายๆcomplete.casesหรือไม่? ถ้าฉันต้องการที่จะรักษาแถวกับ NAs แทนที่จะละทิ้ง? final[ ! complete.cases(final),]ไม่ให้ความร่วมมือ ...
tumultous_rooster

2
finalตัวแปร dataframe คืออะไร?
มอร์ส

1
@Prateek แน่นอนมันเป็น
Joris Meys

256

ลองna.omit(your.data.frame)ดู สำหรับคำถามที่สองลองโพสต์เป็นคำถามอื่น (เพื่อความชัดเจน)


na.omit วางแถว แต่เก็บหมายเลขแถวไว้ คุณจะแก้ไขสิ่งนี้อย่างไรเพื่อให้ได้หมายเลขอย่างถูกต้อง
แบก

3
@Bear rownames(x) <- NULLถ้าคุณไม่สนใจเกี่ยวกับตัวเลขแถวเพียงแค่ทำ
Roman Luštrik

โปรดทราบว่าna.omit()วางแถวที่มีNAอยู่ในคอลัมน์ใด ๆ
Victor Maxwell

116

tidyrมีฟังก์ชั่นใหม่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
drop_naไม่มีการเชื่อมต่อจริงระหว่างท่อและเป็น ตัวอย่างเช่นdf %>% drop_na(), df %>% na.omit()และdrop_na(df)ทุกคนเทียบเท่าพื้น
Ista

4
@Ista ฉันไม่เห็นด้วย na.omitเพิ่มข้อมูลเพิ่มเติมเช่นดัชนีของกรณีที่ถูกตัดออกและ - ที่สำคัญกว่านั้นคือไม่อนุญาตให้คุณเลือกคอลัมน์ - นี่คือที่ที่drop_naส่องสว่าง
ลุค

3
แน่นอนว่าประเด็นของฉันคือไม่ใช่สิ่งที่เกี่ยวข้องกับท่อ คุณสามารถใช้ทั้งแบบna.omitมีหรือไม่มีท่อก็ได้เช่นเดียวกับที่คุณสามารถใช้แบบdrop_naมีหรือไม่มีท่อก็ได้
Ista

1
จริงไม่มีอะไรเกี่ยวข้องกับท่อเลย drop_na () เป็นเพียงฟังก์ชั่นที่เหมือนกันและสามารถเรียกใช้โดยตรงหรือใช้ไพพ์ น่าเสียดายที่ drop_na () ไม่เหมือนกับวิธีอื่น ๆ ที่กล่าวถึงไม่สามารถใช้กับประเภทวัตถุสวนสัตว์หรือ xts นี่อาจเป็นปัญหาสำหรับบางคน
Dave

ใช่ฉันจึงแก้ไขคำตอบเพื่อไม่พูดถึงไพพ์
Arthur Yip

91

ฉันชอบวิธีต่อไปนี้เพื่อตรวจสอบว่าแถวมี NAs ใด ๆ หรือไม่:

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

สิ่งนี้จะคืนค่าเวกเตอร์แบบลอจิคัลพร้อมค่าที่แสดงว่ามี NA ในแถวใดหรือไม่ คุณสามารถใช้มันเพื่อดูจำนวนแถวที่คุณต้องดรอป:

sum(row.has.na)

และในที่สุดก็วางพวกเขา

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

สำหรับการกรองแถวที่มีบางส่วนของ NA จะกลายเป็นเรื่องยากขึ้นเล็กน้อย (ตัวอย่างเช่นคุณสามารถป้อน 'final [, 5: 6]' เป็น 'Apply') โดยทั่วไปวิธีแก้ปัญหาของ Joris Meys นั้นดูจะสง่างามกว่า


2
ช้ามาก ช้ากว่ามากเช่นโซลูชัน complete.cases () ดังกล่าวข้างต้น อย่างน้อยในกรณีของฉันกับข้อมูล xts
Dave

3
rowSum(!is.na(final))ดูเหมือนจะเหมาะสมกว่าดีกว่าapply()
sindri_baldur

45

ตัวเลือกอื่นหากคุณต้องการควบคุมวิธีการถือว่าแถวไม่ถูกต้องมากขึ้น

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 แถวจะถูกลบออกเพราะมันเป็นแถวเท่านั้นที่มี NAS สำหรับทั้งสองและrnor cfamตรรกะบูลีนนั้นสามารถเปลี่ยนแปลงได้เพื่อให้เหมาะกับความต้องการเฉพาะ


5
แต่คุณจะใช้มันได้อย่างไรถ้าคุณต้องการตรวจสอบคอลัมน์จำนวนมากโดยไม่ต้องพิมพ์แต่ละคอลัมน์คุณสามารถใช้ช่วงสุดท้าย [, 4: 100] ได้หรือไม่
เฮอร์แมนทูตรอ

40

หากคุณต้องการควบคุมจำนวน NA ที่ถูกต้องสำหรับแต่ละแถวให้ลองใช้ฟังก์ชันนี้ สำหรับชุดข้อมูลการสำรวจหลายชุดการตอบคำถามเปล่ามากเกินไปอาจทำลายผลลัพธ์ได้ ดังนั้นจะถูกลบหลังจากเกณฑ์ที่กำหนด ฟังก์ชั่นนี้จะช่วยให้คุณเลือกจำนวน NA ที่สามารถมีได้ก่อนที่จะถูกลบ:

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

39

หากผลการดำเนินงานเป็นลำดับความสำคัญการใช้งานdata.tableและมีตัวเลือกพระรามna.omit()cols=

na.omit.data.table เป็นวิธีที่เร็วที่สุดในมาตรฐานของฉัน (ดูด้านล่าง) ไม่ว่าจะสำหรับทุกคอลัมน์หรือสำหรับคอลัมน์ที่เลือก (OP คำถามตอนที่ 2)

หากคุณไม่ต้องการที่จะใช้การใช้งานdata.tablecomplete.cases()

เมื่อวันที่วานิลลาdata.frame, complete.casesเร็วกว่าหรือna.omit() dplyr::drop_na()ขอให้สังเกตว่าไม่สนับสนุนna.omit.data.framecols=

ผลการเปรียบเทียบ

นี่คือการเปรียบเทียบวิธีฐาน (สีน้ำเงิน), dplyr(สีชมพู) และdata.table(สีเหลือง) สำหรับการทิ้งการสังเกตทั้งหมดหรือเลือกที่ขาดหายไปในชุดข้อมูล 1 ล้านการสังเกตการณ์ของตัวแปรตัวเลขจำนวน 1 ล้านการสังเกตและ 5% โอกาสที่จะหายไปและ ส่วนย่อยของ 4 ตัวแปรสำหรับส่วนที่ 2

ผลลัพธ์ของคุณอาจแตกต่างกันไปตามความยาวความกว้างและ sparsity ของชุดข้อมูลเฉพาะของคุณ

บันทึกขนาดบันทึกบนแกน 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))
)

18

การใช้แพ็คเกจ dplyr เราสามารถกรอง NA ดังนี้:

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

1
วิธีนี้ทำงานช้ากว่าประมาณ 10.000 เท่าdrop_na()
Zimano

17

นี่จะส่งคืนแถวที่มีค่าที่ไม่ใช่ NA อย่างน้อยหนึ่งค่า

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

นี่จะส่งคืนแถวที่มีค่าที่ไม่ใช่ NA อย่างน้อยสองค่า

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

16

สำหรับคำถามแรกของคุณฉันมีรหัสที่ฉันสามารถกำจัด NA ทั้งหมดได้ ขอบคุณสำหรับ @Gregor ที่จะทำให้ง่ายขึ้น

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

สำหรับคำถามที่สองรหัสเป็นเพียงทางเลือกจากโซลูชันก่อนหน้า

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

โปรดสังเกตว่า -5 คือจำนวนคอลัมน์ในข้อมูลของคุณ สิ่งนี้จะกำจัดแถวที่มี NA ทั้งหมดเนื่องจาก rowSums เพิ่มขึ้นเป็น 5 และจะกลายเป็นศูนย์หลังจากการลบ ในเวลานี้ตามความจำเป็น


final [as.logical ((rowSums (is.na (final)) - ncol (final))),] สำหรับคำตอบทั่วไป
Ferroao

14

นอกจากนี้เรายังสามารถใช้ฟังก์ชั่นชุดย่อยสำหรับสิ่งนี้

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

สิ่งนี้จะให้เฉพาะแถวที่ไม่มี NA ทั้งใน mmul และ rnor


9

ฉันเป็นนักสังเคราะห์ :) ที่นี่ฉันรวมคำตอบเป็นหนึ่งฟังก์ชัน:

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

สมมติว่าdatเป็น dataframe ของคุณเอาต์พุตที่ต้องการสามารถทำได้โดยใช้

1rowSums

> 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

2lapply

> 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

วิธีการหนึ่งที่ทั้งทั่วไปและอัตราผลตอบแทนอย่างเป็นธรรมรหัสที่อ่านได้คือการใช้filterฟังก์ชั่นและตัวแปรของมันในแพคเกจ 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)

ฟังก์ชั่นด้านบนจะลบแถวทั้งหมดออกจากกรอบข้อมูลที่มี 'NA' ในคอลัมน์ใด ๆ และส่งคืนข้อมูลผลลัพธ์ หากคุณต้องการตรวจสอบหลายค่าเช่นNAและ?การเปลี่ยนแปลงdart=c('NA')ในฟังก์ชันพารามิเตอร์เป็นdart=c('NA', '?')


3

ฉันเดาว่านี่สามารถแก้ไขได้อย่างหรูหรามากขึ้นด้วยวิธีนี้:

  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
NAนี้จะยังคงมีแถวที่มี ฉันคิดว่าสิ่งที่ OP ต้องการคือ:df %>% filter_all(all_vars(!is.na(.)))
asifzuba
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.