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

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.


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


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

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.


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.


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

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

18

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

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

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),]

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.


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


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)
}

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

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(.))

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', '?')


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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.