Remova linhas com todos ou alguns NAs (valores ausentes) em data.frame

852

Gostaria de remover as linhas neste quadro de dados que:

a) contém NAs em todas as colunas. Abaixo está o meu exemplo de quadro de dados.

             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

Basicamente, gostaria de obter um quadro de dados como o seguinte.

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

b) contém NAs em apenas algumas colunas , para que eu também possa obter este resultado:

             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.
fonte

Respostas:

1063

Verifique também 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é melhor por apenas remover todos NA. complete.casespermite a seleção parcial incluindo apenas determinadas colunas do quadro de dados:

> 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

Sua solução não pode funcionar. Se você insistir em usar is.na, precisará fazer algo como:

> 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

mas o uso complete.casesé bem mais claro e rápido.

Joris Meys
fonte
8
Qual é o significado da vírgula à direita final[complete.cases(final),]?
Hertzsprung 01/10/12
6
@hertzsprung Você precisa selecionar linhas, não colunas. De que outra forma você faria isso?
Joris Meys
4
Existe uma simples negação de complete.cases? Se eu quisesse manter as linhas com os NAs em vez de descartar? final[ ! complete.cases(final),]não coopera ...
tumultous_rooster
2
finalé o dataframe variável?
Morse
1
@ Prateek de fato, é.
Joris Meys
256

Tente na.omit(your.data.frame). Quanto à segunda pergunta, tente postá-la como outra pergunta (para maior clareza).

Roman Luštrik
fonte
na.omit descarta as linhas, mas preserva os números das linhas. Como você corrige isso para que seja numerado corretamente?
Bear
3
@ Bear, se você não se importa com números de linha, apenas faça rownames(x) <- NULL.
Roman Luštrik
Por favor note que na.omit()cai linhas que contêm NAem qualquer coluna
Victor Maxwell
116

tidyrtem uma nova função 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
fonte
3
Não há conexão real entre tubos e drop_na. Por exemplo, df %>% drop_na(), df %>% na.omit()e drop_na(df)são todos basicamente equivalente.
Ista
4
@ Ista eu discordo. na.omitadiciona informações adicionais, como os índices de casos omitidos, e - o mais importante - não permite selecionar colunas - é aqui que drop_nabrilha.
lukeA
3
Claro, meu argumento é que nada disso tem a ver com tubos. Você pode usar na.omitcom ou sem tubos, assim como você pode usar drop_nacom ou sem tubos.
Ista
1
É verdade que nada tem a ver com canos. drop_na () é apenas uma função como qualquer outra e, como tal, pode ser chamada diretamente ou usando um pipe. Infelizmente, drop_na (), diferentemente dos outros métodos mencionados, não pode ser usado em tipos de objetos zoo ou xts. Isso pode ser um problema para alguns.
Dave
Certo, então editei a resposta para não mencionar os tubos.
Arthur Yip
91

Prefiro seguir a maneira de verificar se as linhas contêm NAs:

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

Isso retorna um vetor lógico com valores que indicam se existe algum NA em uma linha. Você pode usá-lo para ver quantas linhas você precisará eliminar:

sum(row.has.na)

e, eventualmente, largá-los

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

Para filtrar linhas com certa parte das NAs, fica um pouco mais complicado (por exemplo, você pode alimentar 'final [, 5: 6]' para 'aplicar'). Geralmente, a solução de Joris Meys parece ser mais elegante.

donshikin
fonte
2
Isso é extremamente lento. Muito mais lento que, por exemplo, a solução complete.cases () acima mencionada. Pelo menos no meu caso, em dados xts.
Dave
3
rowSum(!is.na(final))parece mais adequado do queapply()
sindri_baldur
45

Outra opção se você deseja maior controle sobre como as linhas são consideradas inválidas é

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

Usando o acima, isto:

             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

Torna-se:

             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

... onde apenas a linha 5 é removida, pois é a única linha que contém NAs para rnorAND cfam. A lógica booleana pode ser alterada para atender a requisitos específicos.

chegando la
fonte
5
mas como você pode usar isso se quiser verificar muitas colunas, sem digitar cada uma, pode usar um intervalo final [, 4: 100]?
Herman Toothrot
40

Se você deseja controlar quantas NAs são válidas para cada linha, tente esta função. Para muitos conjuntos de dados de pesquisa, muitas respostas em branco às perguntas podem arruinar os resultados. Portanto, eles são excluídos após um certo limite. Esta função permitirá que você escolha quantas NAs a linha pode ter antes de ser excluída:

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

Por padrão, ele eliminará todos os NAs:

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

Ou especifique o número máximo de NAs permitido:

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
candles_and_oranges
fonte
39

Se o desempenho for uma prioridade, use data.tablee na.omit()com parâmetros opcionais cols=.

na.omit.data.table é o mais rápido no meu benchmark (veja abaixo), seja para todas as colunas ou para as colunas selecionadas (pergunta OP parte 2).

Se você não quiser usar data.table, usecomplete.cases() .

Em uma baunilha data.frame, complete.casesé mais rápido que na.omit()ou dplyr::drop_na(). Observe que na.omit.data.framenão suporta cols=.

Resultado de referência

Aqui está uma comparação dos métodos base (azul), dplyr(rosa) e data.table(amarelo) para descartar todas ou selecionar observações ausentes, no conjunto de dados nocional de 1 milhão de observações de 20 variáveis ​​numéricas com probabilidade independente de 5% de estar ausente e um subconjunto de 4 variáveis ​​para a parte 2.

Seus resultados podem variar com base no comprimento, largura e esparsidade do seu conjunto de dados específico.

Observe a escala do log no eixo y.

insira a descrição da imagem aqui

Script de benchmark

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

Usando o pacote dplyr, podemos filtrar o NA da seguinte maneira:

dplyr::filter(df,  !is.na(columnname))
Raminsu
fonte
1
Isso executa cerca de 10.000 vezes mais devagar quedrop_na()
Zimano 21/02
17

Isso retornará as linhas que possuem pelo menos UM valor que não seja NA.

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

Isso retornará as linhas que possuem pelo menos DOIS valor não-NA.

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

Para sua primeira pergunta, tenho um código com o qual me sinto à vontade para me livrar de todas as NAs. Obrigado por @Gregor para torná-lo mais simples.

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

Para a segunda pergunta, o código é apenas uma alternativa da solução anterior.

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

Observe que -5 é o número de colunas nos seus dados. Isso eliminará linhas com todos os NAs, uma vez que o rowSums soma até 5 e eles se tornam zeros após a subtração. Desta vez, como.logical é necessário.

LegitMe
fonte
final [as.logical ((rowSums (is.na (final)) - Ncol (final))),] para uma resposta universal
Ferroao
14

Também podemos usar a função de subconjunto para isso.

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

Isso fornecerá apenas as linhas que não possuem NA em mmul e rnor

Ramya Ural
fonte
9

Eu sou um sintetizador :). Aqui eu combinei as respostas em uma função:

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

Assumindo datcomo seu quadro de dados, a saída esperada pode ser alcançada usando

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

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

Uma abordagem que é tanto geral e produz código bastante legível é usar a filterfunção e suas variantes no pacote 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
fonte
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)

A função acima exclui todas as linhas do quadro de dados que possui 'NA' em qualquer coluna e retorna os dados resultantes. Se você deseja verificar vários valores como NAe ?alterar o dart=c('NA')parâmetro de função paradart=c('NA', '?')

sapy
fonte
3

Meu palpite é que isso poderia ser resolvido de maneira mais elegante desta maneira:

  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
fonte
6
isso manterá as linhas com NA. Eu acho que o que o OP quer é:df %>% filter_all(all_vars(!is.na(.)))
asifzuba 26/06