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

852

Je voudrais supprimer les lignes de ce bloc 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

Fondamentalement, j'aimerais obtenir une trame de données comme celle-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 , donc je peux aussi obtenir ce résultat:

             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
Benoit B.
la source

Réponses:

1063

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.omitest plus agréable pour simplement supprimer tout NA. complete.casespermet une sélection partielle en n'incluant que certaines colonnes de la trame 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 l'utilisation complete.casesest beaucoup plus claire et plus rapide.

Joris Meys
la source
8
Quelle est la signification de la virgule de fin dans final[complete.cases(final),]?
hertzsprung
6
@hertzsprung Vous devez sélectionner des lignes, pas des colonnes. Sinon, comment feriez-vous cela?
Joris Meys
4
Y a-t-il une simple négation de complete.cases? Si je voulais conserver les lignes avec des NA au lieu de les supprimer? final[ ! complete.cases(final),]ne coopère pas ...
tumultous_rooster
2
finalla variable dataframe est-elle variable?
Morse
1
@Prateek en effet, il l'est.
Joris Meys
256

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

Roman Luštrik
la source
na.omit supprime les lignes mais conserve les numéros de ligne. Comment pourriez-vous résoudre ce problème afin qu'il soit correctement numéroté?
Gardez
3
@Bear si vous ne vous souciez pas des numéros de ligne, faites-le rownames(x) <- NULL.
Roman Luštrik
veuillez noter que na.omit()supprime les lignes qui contiennent NAdans n'importe quelle colonne
Victor Maxwell
116

tidyra 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
lukeA
la source
3
Il n'y a pas de véritable connexion entre les tuyaux et drop_na. Par exemple, df %>% drop_na(), df %>% na.omit()et drop_na(df)sont tous essentiellement équivalents.
Ista
4
@Ista, je ne suis pas d'accord. na.omitajoute des informations supplémentaires comme les indices des cas omis, et - plus important encore - ne vous permet pas de sélectionner des colonnes - c'est là que drop_nabrille.
lukeA
3
Bien sûr, mon point est que rien de tout cela n'a à voir avec les tuyaux. Vous pouvez utiliser na.omitavec ou sans tuyaux, tout comme vous pouvez utiliser drop_naavec ou sans tuyaux.
Ista
1
Certes, rien à voir avec les tuyaux du tout. drop_na () est juste une fonction comme les autres et, en tant que telle, peut être appelée directement ou à l'aide d'un tuyau. Malheureusement, drop_na (), contrairement aux autres méthodes mentionnées, ne peut pas être utilisé sur les types d'objet zoo ou xts. Cela pourrait être un problème pour certains.
Dave
D'accord, j'ai donc modifié la réponse pour qu'elle ne mentionne pas les tuyaux.
Arthur Yip
91

Je préfère suivre la façon de 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 une NA dans une rangée. Vous pouvez l'utiliser pour voir combien de lignes vous devrez supprimer:

sum(row.has.na)

et finalement les laisser tomber

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

Pour filtrer les lignes avec certaines parties des AN, cela devient un peu plus compliqué (par exemple, vous pouvez insérer «final [, 5: 6]» pour «appliquer»). D'une manière générale, la solution de Joris Meys semble plus élégante.

donshikin
la source
2
C'est extrêmement lent. Beaucoup plus lent que par exemple la solution complete.cases () susmentionnée. Au moins, dans mon cas, sur les données xts.
Dave
3
rowSum(!is.na(final))semble mieux adapté queapply()
sindri_baldur
45

Une autre option si vous souhaitez un meilleur contrôle sur la façon dont les lignes sont considérées comme non valides 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 car c'est la seule ligne contenant des NA pour les deux rnorET cfam. La logique booléenne peut ensuite être modifiée pour répondre à des exigences spécifiques.

arriver
la source
5
mais comment pouvez-vous l'utiliser si vous voulez vérifier plusieurs colonnes, sans taper chacune, pouvez-vous utiliser une plage finale [, 4: 100]?
Herman Toothrot
40

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 aux questions vierges 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, il éliminera toutes les NA:

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ées:

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
bougies_et_oranges
la source
39

Si les performances sont une priorité, utilisez data.tableet na.omit()avec param facultatif cols=.

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

Si vous ne voulez pas utiliser data.table, utilisez complete.cases().

Sur une vanille data.frame, complete.casesest plus rapide que na.omit()ou dplyr::drop_na(). Notez que na.omit.data.framecela 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 les observations manquantes, sur un ensemble de données théorique de 1 million d'observations de 20 variables numériques avec une probabilité indépendante de 5% 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 rareté de votre ensemble de données particulier.

Notez l'échelle logarithmique sur l'axe y.

entrez la description de l'image ici

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))
)
C8H10N4O2
la source
18

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

dplyr::filter(df,  !is.na(columnname))
Raminsu
la source
1
Cela fonctionne environ 10.000 fois plus lentement quedrop_na()
Zimano
17

Cela retournera les lignes qui ont au moins UNE valeur non NA.

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

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

final[rowSums(is.na(final))<(length(final)-1),]
Leo
la source
16

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

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

Pour la deuxième question, le code est juste une alternance de la solution précédente.

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

Notez que -5 est le nombre de colonnes dans vos données. Cela éliminera les lignes avec toutes les NA, puisque les rowSums totalisent 5 et ils deviennent des zéros après soustraction. Cette fois, car.logical est nécessaire.

LegitMe
la source
final [as.logical ((rowSums (is.na (final)) - ncol (final))),] pour une réponse universelle
Ferroao
14

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

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

Cela ne donnera que les lignes qui n'ont pas de NA à la fois mmul et rnor

Ramya Ural
la source
9

Je suis 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)
}
Jerry T
la source
8

En supposant datque votre trame de données, la sortie attendue peut être obtenue en utilisant

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
Prradep
la source
7

Une approche qui est à la fois générale et donne un code assez lisible est d'utiliser la filterfonction et ses variantes dans le paquet 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(.))
bschneidr
la source
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)

La fonction ci-dessus supprime toutes les lignes du bloc de données qui a «NA» dans n'importe quelle colonne et renvoie les données résultantes. Si vous voulez vérifier plusieurs valeurs comme NAet ?changer le dart=c('NA')paramètre de fonction endart=c('NA', '?')

saveur
la source
3

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
Joni Hoppen
la source
6
cela conservera les lignes avec NA. Je pense que ce que le PO veut, c'est:df %>% filter_all(all_vars(!is.na(.)))
asifzuba