values - إزالة الصفوف مع NAs(القيم المفقودة) في data.frame




remove na in r (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.table و na.omit() مع cols= param اختيارية cols= .

na.omit.data.table هي الأسرع في معياري (انظر أدناه) ، سواء لجميع الأعمدة أو للأعمدة المحددة (OP question part 2).

إذا كنت لا ترغب في استخدام data.table ، استخدم complete.cases() .

على فانيلا data.frame ، complete.cases أسرع من na.omit() أو dplyr::drop_na() . لاحظ أن na.omit.data.frame لا يدعم cols= .

النتيجة المعيارية

هنا مقارنة بين القواعد الأساسية (الزرقاء) ، dplyr (الوردي) ، وأساليب data.table (الصفراء) لإسقاط إما جميع أو اختيار الملاحظات المفقودة ، على مجموعة بيانات افتراضية من 1 مليون ملاحظة من 20 متغيرات رقمية مع احتمال مستقل بنسبة 5 ٪ من كونها في عداد المفقودين ، ومجموعة فرعية من 4 متغيرات للجزء 2.

قد تختلف النتائج حسب طول وعرض وعرض مجموعة البيانات الخاصة بك.

لاحظ مقياس السجل على 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))
)

أفضل الطريقة التالية للتحقق مما إذا كانت الصفوف تحتوي على أي 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 أكثر أناقة.


إذا كنت تحب أنابيب ( %>% ) ، فإن 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

إذا كنت تريد التحكم في عدد 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 هو عدد الأعمدة في بياناتك. سيؤدي هذا إلى إزالة الصفوف مع جميع NAs ، حيث يضيف الصف x حتى 5 وتصبح أصفار بعد الطرح. هذه المرة ، as.logical ضروري.


تحقق أيضا 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 . complete.cases يسمح الاختيار الجزئي عن طريق تضمين أعمدة معينة فقط من dataframe:

> 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

لكن استخدام full.cases أكثر وضوحًا وأسرع.


خيار آخر إذا كنت تريد قدرًا أكبر من التحكم في كيفية اعتبار الصفوف غير صالحة

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 فقط حيث إنه الصف الوحيد الذي يحتوي على rnor لكل من rnor و cfam . ومن ثم يمكن تغيير المنطق البولياني ليتناسب مع متطلبات محددة.


سيؤدي هذا إلى إرجاع الصفوف التي تحتوي على قيمة واحدة غير NA على الأقل.

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

سيؤدي هذا إلى إرجاع الصفوف التي تحتوي على قيمة غير NA على الأقل.

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

يمكننا أيضا استخدام وظيفة فرعية لهذا.

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

هذا سيعطي فقط تلك الصفوف التي ليس لديها NA في كل mmul و rnor


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') في دالة param إلى dart=c('NA', '?')





r-faq