web-dev-qa-db-fra.com

Supprimer les lignes avec tout ou partie des NA (valeurs manquantes) dans data.frame

Je voudrais supprimer les lignes dans ce cadre de données qui:

a) contient NAs dans toutes les colonnes. Voici mon exemple de trame de données.

             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

En gros, j'aimerais obtenir un bloc de données tel que celui-ci.

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

b) contient NAs dans seulement quelques colonnes, je peux donc aussi obtenir le résultat suivant:

             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
769
Benoit B.

Vérifiez également 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_ est plus pratique pour supprimer uniquement tous les NA. _complete.cases_ permet la sélection partielle en n'incluant que certaines colonnes du cadre de données:

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

Votre solution ne peut pas fonctionner. Si vous insistez pour utiliser _is.na_, vous devez faire quelque chose comme:

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

mais utiliser _complete.cases_ est beaucoup plus clair et plus rapide.

967
Joris Meys

Essayez na.omit(your.data.frame). Quant à la deuxième question, essayez de l'afficher comme une autre question (pour plus de clarté).

234
Roman Luštrik

tidyr a une nouvelle fonction 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
_
88
lukeA

Je préfère suivre la méthode suivante pour vérifier si les lignes contiennent des NA:

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

Cela renvoie un vecteur logique avec des valeurs indiquant s'il y a ou non un NA dans une ligne. Vous pouvez l'utiliser pour voir combien de lignes vous devez supprimer:

sum(row.has.na)

et finalement les laisser tomber

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

Pour filtrer les lignes avec certaines parties de NA, cela devient un peu plus compliqué (par exemple, vous pouvez alimenter "final [ 5: 6]" pour "appliquer"). En règle générale, la solution de Joris Meys semble être plus élégante.

86
donshikin

Une autre option si vous souhaitez mieux contrôler l’invalidité des lignes est

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

En utilisant ce qui précède, ceci:

             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

Devient:

             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

... où seule la ligne 5 est supprimée puisqu'il s'agit de la seule ligne contenant des NA pour les deux rnor ET cfam. La logique booléenne peut ensuite être modifiée pour répondre à des exigences spécifiques.

43
getting-there

Si vous souhaitez contrôler le nombre d'AN valides pour chaque ligne, essayez cette fonction. Pour de nombreux ensembles de données d’enquête, trop de réponses en blanc peuvent ruiner les résultats. Ils sont donc supprimés après un certain seuil. Cette fonction vous permettra de choisir le nombre d'AN que la ligne peut avoir avant d'être supprimée:

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

Par défaut, toutes les AN seront éliminées:

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

Ou spécifiez le nombre maximum d'AN autorisé:

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
37
Pierre Lafortune

Si la performance est une priorité, utilisez _data.table_ et na.omit() avec le paramètre optionnel _cols=_.

na.omit.data.table est le plus rapide de mon repère (voir ci-dessous), que ce soit pour toutes les colonnes ou pour certaines colonnes (question OP 2).

Si vous ne souhaitez pas utiliser _data.table_, utilisez complete.cases().

Sur une vanille _data.frame_, complete.cases est plus rapide que na.omit() ou dplyr::drop_na() . Notez que _na.omit.data.frame_ ne prend pas en charge _cols=_.

Résultat de référence

Voici une comparaison des méthodes de base (bleu), dplyr (rose) et _data.table_ (jaune) pour supprimer toutes ou sélectionner des observations manquantes dans un jeu de données notionnel de 1 million d'observations de 20 variables numériques avec indépendante 5% de probabilité d’être manquant et un sous-ensemble de 4 variables pour la partie 2.

Vos résultats peuvent varier en fonction de la longueur, de la largeur et de la parcimonie de votre ensemble de données.

Notez l'échelle du journal sur l'axe des y.

enter image description here

Script de référence

_#-------  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))
)
_
34
C8H10N4O2

En utilisant le package dplyr, nous pouvons filtrer NA comme suit:

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

Cela renverra les lignes qui ont au moins UNE valeur autre que NA.

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

Cela renverra les lignes qui ont au moins DEUX valeurs non NA.

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

Pour votre première question, j'ai un code avec lequel je suis à l'aise pour me débarrasser de toutes les NA. Merci pour @Gregor de simplifier les choses.

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

Pour la deuxième question, le code est simplement une alternative à la solution précédente.

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

Notez que -5 correspond au nombre de colonnes de vos données. Cela éliminera les lignes avec toutes les NA, puisque la somme de somme est égale à 5 et qu'elles deviennent des zéros après la soustraction. Cette fois-ci, comme si logique était nécessaire.

15
LegitMe

Nous pouvons également utiliser la fonction de sous-ensemble pour cela.

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

Cela donnera uniquement les lignes qui n'ont pas NA dans les deux valeurs mmul et rnor

14
Ramya Ural

Je suis un synthétiseur :). Ici, j'ai combiné les réponses en une seule fonction:

#' 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)
}
9
Jerry T

En supposant que dat en tant que votre cadre de données, le résultat attendu peut être obtenu à l'aide de

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
8
Prradep
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)

La fonction ci-dessus supprime toutes les lignes du cadre de données contenant "NA" dans une colonne et renvoie les données résultantes. Si vous voulez vérifier plusieurs valeurs comme NA et ? changez Dart=c('NA') dans la fonction param en Dart=c('NA', '?')

4
sapy

Je suppose que cela pourrait être résolu de manière plus élégante de cette manière

  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
3
Joni Hoppen

Une approche à la fois générale et qui produit un code assez lisible consiste à utiliser la fonction filter et ses variantes dans le package 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(.))
2
bschneidr

na.omit fera dans ce cas. Une fois que cela est fait, vous pouvez visualiser votre jeu de données en utilisant un graphique de valeurs manquantes.

0
Isabelle