Como substituo valores NA por zeros em um dataframe R?

728

Eu tenho um quadro de dados e algumas colunas têm NAvalores.

Como substituo esses NAvalores por zeros?

Renato Dinhani
fonte
13
pequena modificação de stackoverflow.com/questions/7279089/... (que eu encontrado pela procura "[r] substituir NA com zero") ...
Ben Bolker
25
d [is.na (d)] <- 0
psychonomics

Respostas:

880

Veja meu comentário na resposta @ gsk3. Um exemplo simples:

> m <- matrix(sample(c(NA, 1:10), 100, replace = TRUE), 10)
> d <- as.data.frame(m)
   V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  3 NA  3  7  6  6 10  6   5
2   9  8  9  5 10 NA  2  1  7   2
3   1  1  6  3  6 NA  1  4  1   6
4  NA  4 NA  7 10  2 NA  4  1   8
5   1  2  4 NA  2  6  2  6  7   4
6  NA  3 NA NA 10  2  1 10  8   4
7   4  4  9 10  9  8  9  4 10  NA
8   5  8  3  2  1  4  5  9  4   7
9   3  9 10  1  9  9 10  5  3   3
10  4  2  2  5 NA  9  7  2  5   5

> d[is.na(d)] <- 0

> d
   V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  3  0  3  7  6  6 10  6   5
2   9  8  9  5 10  0  2  1  7   2
3   1  1  6  3  6  0  1  4  1   6
4   0  4  0  7 10  2  0  4  1   8
5   1  2  4  0  2  6  2  6  7   4
6   0  3  0  0 10  2  1 10  8   4
7   4  4  9 10  9  8  9  4 10   0
8   5  8  3  2  1  4  5  9  4   7
9   3  9 10  1  9  9 10  5  3   3
10  4  2  2  5  0  9  7  2  5   5

Não há necessidade de se inscrever apply. =)

EDITAR

Você também deve dar uma olhada no normpacote. Possui muitos recursos interessantes para a análise de dados ausentes. =)

aL3xa
fonte
2
Eu já tentei esse código ontem antes de publicá-lo e não funcionou. Porque isso eu postei a pergunta. Mas eu tentei conhecer e funcionou perfeitamente. Eu acho que estava fazendo algo errado.
Renato Dinhani
12
@ RenatoDinhaniConceição: se você já tentou algo, é útil compartilhar essas informações quando você faz a pergunta; ajuda a diminuir onde o problema pode estar.
Aaron saiu de Stack Overflow
2
d [is.na (d)] <- 0 não faz sentido para mim. Parece para trás? Como R processa esta declaração?
user798719
13
@ user798719 - "<-" é o operador de atribuição de R e pode ser lido como: faça algo no lado direito e depois atribua-o ao local / nome à esquerda. Nesse caso, não estamos realmente "fazendo" nada - apenas zerando. O lado esquerdo está dizendo: olhe para o objeto d, dentro do objeto d (colchetes), encontre todos os elementos que retornam TRUE (is.na (d) retorna uma lógica para cada elemento). Uma vez encontrados, substitua-os ("atribua-os") ao valor 0. Isso deixa todos os não-NAs como eram e substitui apenas aqueles com falta.
Twitch_City
3
E ... se você tem um quadro de dados e só deseja aplicar a substituição de vetores específicos nurmeric (digamos deixando ... cordas com NA):df[19:28][is.na(df[19:28])] <- 0
jtdoud
299

As opções hibridizadas do dplyr agora são cerca de 30% mais rápidas do que o subconjunto Base R reatribui. Em um dataponto de 100M, o dataframe mutate_all(~replace(., is.na(.), 0))é executado meio segundo mais rápido que a d[is.na(d)] <- 0opção R básica . O que se deseja evitar especificamente é usar um ifelse()ou um if_else(). (A análise completa de 600 ensaios durou mais de 4,5 horas, principalmente devido à inclusão dessas abordagens.) Consulte as análises de benchmark abaixo para obter os resultados completos.

Se você está enfrentando grandes quadros de dados, data.tableé a opção mais rápida de todas: 40% mais rápida que a abordagem padrão da Base R. Ele também modifica os dados, permitindo efetivamente trabalhar com quase o dobro dos dados de uma só vez.


Um agrupamento de outras abordagens úteis de substituição ordenada

Localmente:

  • índice mutate_at(c(5:10), ~replace(., is.na(.), 0))
  • referência direta mutate_at(vars(var5:var10), ~replace(., is.na(.), 0))
  • Meta fixada mutate_at(vars(contains("1")), ~replace(., is.na(.), 0))
    • ou no lugar de contains(), tente ends_with(),starts_with()
  • correspondência de padrão mutate_at(vars(matches("\\d{2}")), ~replace(., is.na(.), 0))

Condicionalmente:
(altere apenas um tipo e deixe outros tipos em paz.)

  • inteiros mutate_if(is.integer, ~replace(., is.na(.), 0))
  • números mutate_if(is.numeric, ~replace(., is.na(.), 0))
  • cordas mutate_if(is.character, ~replace(., is.na(.), 0))

A análise completa -

Atualizado para o dplyr 0.8.0: as funções usam ~símbolos de formato purrr : substituindo funs()argumentos obsoletos .

Abordagens testadas:

# Base R: 
baseR.sbst.rssgn   <- function(x) { x[is.na(x)] <- 0; x }
baseR.replace      <- function(x) { replace(x, is.na(x), 0) }
baseR.for          <- function(x) { for(j in 1:ncol(x))
    x[[j]][is.na(x[[j]])] = 0 }

# tidyverse
## dplyr
dplyr_if_else      <- function(x) { mutate_all(x, ~if_else(is.na(.), 0, .)) }
dplyr_coalesce     <- function(x) { mutate_all(x, ~coalesce(., 0)) }

## tidyr
tidyr_replace_na   <- function(x) { replace_na(x, as.list(setNames(rep(0, 10), as.list(c(paste0("var", 1:10)))))) }

## hybrid 
hybrd.ifelse     <- function(x) { mutate_all(x, ~ifelse(is.na(.), 0, .)) }
hybrd.replace_na <- function(x) { mutate_all(x, ~replace_na(., 0)) }
hybrd.replace    <- function(x) { mutate_all(x, ~replace(., is.na(.), 0)) }
hybrd.rplc_at.idx<- function(x) { mutate_at(x, c(1:10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.nse<- function(x) { mutate_at(x, vars(var1:var10), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.stw<- function(x) { mutate_at(x, vars(starts_with("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.ctn<- function(x) { mutate_at(x, vars(contains("var")), ~replace(., is.na(.), 0)) }
hybrd.rplc_at.mtc<- function(x) { mutate_at(x, vars(matches("\\d+")), ~replace(., is.na(.), 0)) }
hybrd.rplc_if    <- function(x) { mutate_if(x, is.numeric, ~replace(., is.na(.), 0)) }

# data.table   
library(data.table)
DT.for.set.nms   <- function(x) { for (j in names(x))
    set(x,which(is.na(x[[j]])),j,0) }
DT.for.set.sqln  <- function(x) { for (j in seq_len(ncol(x)))
    set(x,which(is.na(x[[j]])),j,0) }
DT.nafill        <- function(x) { nafill(df, fill=0)}
DT.setnafill     <- function(x) { setnafill(df, fill=0)}

O código para esta análise:

library(microbenchmark)
# 20% NA filled dataframe of 10 Million rows and 10 columns
set.seed(42) # to recreate the exact dataframe
dfN <- as.data.frame(matrix(sample(c(NA, as.numeric(1:4)), 1e7*10, replace = TRUE),
                            dimnames = list(NULL, paste0("var", 1:10)), 
                            ncol = 10))
# Running 600 trials with each replacement method 
# (the functions are excecuted locally - so that the original dataframe remains unmodified in all cases)
perf_results <- microbenchmark(
    hybrid.ifelse    = hybrid.ifelse(copy(dfN)),
    dplyr_if_else    = dplyr_if_else(copy(dfN)),
    hybrd.replace_na = hybrd.replace_na(copy(dfN)),
    baseR.sbst.rssgn = baseR.sbst.rssgn(copy(dfN)),
    baseR.replace    = baseR.replace(copy(dfN)),
    dplyr_coalesce   = dplyr_coalesce(copy(dfN)),
    tidyr_replace_na = tidyr_replace_na(copy(dfN)),
    hybrd.replace    = hybrd.replace(copy(dfN)),
    hybrd.rplc_at.ctn= hybrd.rplc_at.ctn(copy(dfN)),
    hybrd.rplc_at.nse= hybrd.rplc_at.nse(copy(dfN)),
    baseR.for        = baseR.for(copy(dfN)),
    hybrd.rplc_at.idx= hybrd.rplc_at.idx(copy(dfN)),
    DT.for.set.nms   = DT.for.set.nms(copy(dfN)),
    DT.for.set.sqln  = DT.for.set.sqln(copy(dfN)),
    times = 600L
)

Resumo dos Resultados

> print(perf_results)
Unit: milliseconds
              expr       min        lq     mean   median       uq      max neval
      hybrd.ifelse 6171.0439 6339.7046 6425.221 6407.397 6496.992 7052.851   600
     dplyr_if_else 3737.4954 3877.0983 3953.857 3946.024 4023.301 4539.428   600
  hybrd.replace_na 1497.8653 1706.1119 1748.464 1745.282 1789.804 2127.166   600
  baseR.sbst.rssgn 1480.5098 1686.1581 1730.006 1728.477 1772.951 2010.215   600
     baseR.replace 1457.4016 1681.5583 1725.481 1722.069 1766.916 2089.627   600
    dplyr_coalesce 1227.6150 1483.3520 1524.245 1519.454 1561.488 1996.859   600
  tidyr_replace_na 1248.3292 1473.1707 1521.889 1520.108 1570.382 1995.768   600
     hybrd.replace  913.1865 1197.3133 1233.336 1238.747 1276.141 1438.646   600
 hybrd.rplc_at.ctn  916.9339 1192.9885 1224.733 1227.628 1268.644 1466.085   600
 hybrd.rplc_at.nse  919.0270 1191.0541 1228.749 1228.635 1275.103 2882.040   600
         baseR.for  869.3169 1180.8311 1216.958 1224.407 1264.737 1459.726   600
 hybrd.rplc_at.idx  839.8915 1189.7465 1223.326 1228.329 1266.375 1565.794   600
    DT.for.set.nms  761.6086  915.8166 1015.457 1001.772 1106.315 1363.044   600
   DT.for.set.sqln  787.3535  918.8733 1017.812 1002.042 1122.474 1321.860   600

Boxplot de Resultados

ggplot(perf_results, aes(x=expr, y=time/10^9)) +
    geom_boxplot() +
    xlab('Expression') +
    ylab('Elapsed Time (Seconds)') +
    scale_y_continuous(breaks = seq(0,7,1)) +
    coord_flip()

Comparação de Boxplot do tempo decorrido

Gráfico de Dispersão com Código de Cores (com eixo y em uma escala de log)

qplot(y=time/10^9, data=perf_results, colour=expr) + 
    labs(y = "log10 Scaled Elapsed Time per Trial (secs)", x = "Trial Number") +
    coord_cartesian(ylim = c(0.75, 7.5)) +
    scale_y_log10(breaks=c(0.75, 0.875, 1, 1.25, 1.5, 1.75, seq(2, 7.5)))

Gráfico de dispersão de todos os tempos de teste

Uma nota sobre os outros de alto desempenho

Quando os conjuntos de dados aumentam , os Tidyr 's replace_nahistoricamente se destacaram. Com a coleção atual de 100 milhões de pontos de dados para executar, ele executa quase exatamente tão bem quanto um Base R For Loop. Estou curioso para ver o que acontece com quadros de dados de tamanhos diferentes.

Exemplos adicionais para o mutatee summarize _ate _allvariantes de função pode ser encontrada aqui: https://rdrr.io/cran/dplyr/man/summarise_all.html Além disso, eu encontrei manifestações votos e coleções de exemplos aqui: https: //blog.exploratory. io / dplyr-0-5-is-awesome-heres-why-be095fd4eb8a

Atribuições e apreciações

Agradecimentos especiais a:

  • Tyler Rinker e Akrun, por demonstrar a marca de microbench .
  • alexis_laz por trabalhar para me ajudar a entender o uso local()e (com a ajuda do paciente de Frank também) o papel que a coerção silenciosa desempenha na aceleração de muitas dessas abordagens.
  • ArthurYip para o puxão para adicionar a coalesce()função mais nova e atualizar a análise.
  • Gregor, para que o cutucão descubra as data.tablefunções bem o suficiente para finalmente incluí-las na programação.
  • Loop For da base R : alexis_laz
  • data.table Para loops: Matt_Dowle
  • Roman por explicar o que is.numeric()realmente testa.

(Obviamente, estenda a mão e dê a eles votos positivos também, se você achar essas abordagens úteis.)

Nota sobre o uso de Numéricos: Se você possui um conjunto de dados inteiro puro, todas as suas funções serão executadas mais rapidamente. Por favor, veja o trabalho de alexiz_laz para mais informações. IRL, não me lembro de encontrar um conjunto de dados contendo mais de 10 a 15% de números inteiros; portanto, estou executando esses testes em quadros de dados totalmente numéricos.

CPU utilizada de hardware de 3,9 GHz com 24 GB de RAM

leerssej
fonte
2
@ Frank - Obrigado por encontrar essa discrepância. As referências são todas limpas e os resultados foram reexecutados inteiramente em uma única máquina e reeditados.
precisa saber é o seguinte
Ok obrigado. Além disso, eu acho que df1[j][is.na(df1[j])] = 0é errado, deve serdf1[[j]][is.na(df1[[j]])] = 0
Frank
Ah, agora vejo que você escreveu duas vezes, de forma diferente em cada benchmark. De qualquer forma, forLp_Sbstnão parece ser uma maneira ninguém deve considerar aproximando-vsforLp_smplfSbst
Frank
1
@UweBlock - ótima pergunta: ele me permitiu fazer a operação de atribuição esquerda de subconjunto com todas as funções trabalhando exatamente no mesmo quadro de dados. Desde que eu tive que envolver o local em torno dessa função, então, em nome da ciência [Um emprego, você tinha um emprego!] Eu o envolvi em todos eles para que o campo de jogo estivesse inequivocamente nivelado. Para obter mais informações - consulte aqui: stackoverflow.com/questions/41604711/… Eu reduzi a resposta anterior bastante demorada - mas essa parte da discussão seria boa para adicionar novamente. Obrigado!
Leerssej
1
@ArthurYip - adicionei a coalesce()opção e execute novamente o tempo todo. Obrigado pela atualização.
21817 Leerssej
128

Para um único vetor:

x <- c(1,2,NA,4,5)
x[is.na(x)] <- 0

Para um data.frame, faça uma função fora das opções acima e depois applynas colunas.

Forneça um exemplo reproduzível da próxima vez, conforme detalhado aqui:

Como fazer um ótimo exemplo reprodutível de R?

Ari B. Friedman
fonte
18
is.naé uma função genérica e possui métodos para objetos de data.frameclasse. então este também funcionará em data.frames!
aL3xa
3
Quando eu corri methods(is.na)pela primeira vez, eu fiquei tipo, o que ?!? . Eu amo quando coisas assim acontecem! =)
aL3xa 17/11
9
Suponha que você tenha um quadro de dados denominado df em vez de um único vetor e deseje substituir as observações ausentes em uma única coluna denominada X3. Você pode fazer isso com esta linha: df $ X3 [is.na (df $ X3)] <- 0
Mark Miller
8
Suponha que você queira substituir NA por 0 apenas nas colunas 4-6 de um quadro de dados denominado my.df. Você pode usar: my.df [, 4: 6] [is.na (my.df [, 4: 6])] <- 0
Mark Miller
como você passa 'x' para is.na (x) existe uma maneira de dizer quais rotinas de biblioteca em R são vetorizadas?
Uh_big_mike_boi
73

exemplo dplyr:

library(dplyr)

df1 <- df1 %>%
    mutate(myCol1 = if_else(is.na(myCol1), 0, myCol1))

Nota: Isso funciona por coluna selecionada, se precisamos fazer isso para todas as colunas, consulte @reidjax 's resposta usando mutate_each .

ianmunoz
fonte
57

Se estamos tentando substituir NAs ao exportar, por exemplo, ao gravar em csv, podemos usar:

  write.csv(data, "data.csv", na = "0")
mrsoltys
fonte
47

Sei que a pergunta já foi respondida, mas fazê-lo dessa maneira pode ser mais útil para alguns:

Defina esta função:

na.zero <- function (x) {
    x[is.na(x)] <- 0
    return(x)
}

Agora, sempre que você precisar converter NA em um vetor para zero, você pode:

na.zero(some.vector)
krishan404
fonte
22

Com o dplyr0.5.0, você pode usar a coalescefunção que pode ser facilmente integrada ao %>%pipeline fazendo isso coalesce(vec, 0). Isso substitui todas as NAs emvec por 0:

Digamos que temos um quadro de dados com NAs:

library(dplyr)
df <- data.frame(v = c(1, 2, 3, NA, 5, 6, 8))

df
#    v
# 1  1
# 2  2
# 3  3
# 4 NA
# 5  5
# 6  6
# 7  8

df %>% mutate(v = coalesce(v, 0))
#   v
# 1 1
# 2 2
# 3 3
# 4 0
# 5 5
# 6 6
# 7 8
Psidom
fonte
Eu testei coalescência e ele executa quase o mesmo que substituir. o comando coalescer é o mais simples até agora!
Arthur Yip
seria útil se você apresentasse como aplicar isso em todas as colunas de mais de 2 colunas.
Jangorecki # 30/19
21

Abordagem mais geral de utilizar replace()em matriz ou vector para substituir NAa0

Por exemplo:

> x <- c(1,2,NA,NA,1,1)
> x1 <- replace(x,is.na(x),0)
> x1
[1] 1 2 0 0 1 1

Essa também é uma alternativa ao uso ifelse()emdplyr

df = data.frame(col = c(1,2,NA,NA,1,1))
df <- df %>%
   mutate(col = replace(col,is.na(col),0))
Charleslmh
fonte
1
Minha coluna foi um fator então eu tive que adicionar o meu valor de reposiçãolevels(A$x) <- append(levels(A$x), "notAnswered") A$x <- replace(A$x,which(is.na(A$x)),"notAnswered")
Climbs_lika_Spyder
1
whichnão é necessário aqui, você pode usar x1 <- replace(x,is.na(x),1).
lmo
Eu tentei muitas maneiras propostas neste segmento para substituir NAa 0em apenas uma coluna específica em um grande quadro de dados e esta função replace()trabalhou a forma mais eficaz e ao mesmo tempo o mais simples.
Duc
19

Também é possível usar tidyr::replace_na.

    library(tidyr)
    df <- df %>% mutate_all(funs(replace_na(.,0)))
Sasha
fonte
9

Outro exemplo usando o pacote imputeTS :

library(imputeTS)
na.replace(yourDataframe, 0)
stats0007
fonte
9

Se você deseja substituir NAs em variáveis ​​fatoriais, isso pode ser útil:

n <- length(levels(data.vector))+1

data.vector <- as.numeric(data.vector)
data.vector[is.na(data.vector)] <- n
data.vector <- as.factor(data.vector)
levels(data.vector) <- c("level1","level2",...,"leveln", "NAlevel") 

Ele transforma um vetor de fator em um vetor numérico e adiciona outro nível de fator numérico artificial, que é então transformado novamente em um vetor de fator com um "nível de NA" extra de sua escolha.

user6075957
fonte
8

Teria comentado na publicação de @ ianmunoz, mas não tenho reputação suficiente. Você pode combinar dplyré mutate_eache replacepara cuidar da NAa 0substituição. Usando o dataframe da resposta de @ aL3xa ...

> m <- matrix(sample(c(NA, 1:10), 100, replace = TRUE), 10)
> d <- as.data.frame(m)
> d

    V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  8  1  9  6  9 NA  8  9   8
2   8  3  6  8  2  1 NA NA  6   3
3   6  6  3 NA  2 NA NA  5  7   7
4  10  6  1  1  7  9  1 10  3  10
5  10  6  7 10 10  3  2  5  4   6
6   2  4  1  5  7 NA NA  8  4   4
7   7  2  3  1  4 10 NA  8  7   7
8   9  5  8 10  5  3  5  8  3   2
9   9  1  8  7  6  5 NA NA  6   7
10  6 10  8  7  1  1  2  2  5   7

> d %>% mutate_each( funs_( interp( ~replace(., is.na(.),0) ) ) )

    V1 V2 V3 V4 V5 V6 V7 V8 V9 V10
1   4  8  1  9  6  9  0  8  9   8
2   8  3  6  8  2  1  0  0  6   3
3   6  6  3  0  2  0  0  5  7   7
4  10  6  1  1  7  9  1 10  3  10
5  10  6  7 10 10  3  2  5  4   6
6   2  4  1  5  7  0  0  8  4   4
7   7  2  3  1  4 10  0  8  7   7
8   9  5  8 10  5  3  5  8  3   2
9   9  1  8  7  6  5  0  0  6   7
10  6 10  8  7  1  1  2  2  5   7

Estamos usando a avaliação padrão (SE) aqui e é por isso que precisamos do sublinhado em " funs_." Também usamos lazyeval's interp/ ~e as .referências "tudo o que estamos trabalhando", ou seja, o quadro de dados. Agora existem zeros!

reidjax
fonte
4

Você pode usar replace()

Por exemplo:

> x <- c(-1,0,1,0,NA,0,1,1)
> x1 <- replace(x,5,1)
> x1
[1] -1  0  1  0  1  0  1  1

> x1 <- replace(x,5,mean(x,na.rm=T))
> x1
[1] -1.00  0.00  1.00  0.00  0.29  0.00 1.00  1.00
Zahra
fonte
6
É verdade, mas só é prático quando você conhece o índice de NAs no seu vetor. É bom para vetores pequenos, como no seu exemplo.
dardisco
4
O @dardisco x1 <- replace(x,is.na(x),1)funcionará sem listar explicitamente os valores do índice.
lmo
4

Outra dplyropção compatível com pipe com o tidyrmétodo replace_naque funciona para várias colunas:

require(dplyr)
require(tidyr)

m <- matrix(sample(c(NA, 1:10), 100, replace = TRUE), 10)
d <- as.data.frame(m)

myList <- setNames(lapply(vector("list", ncol(d)), function(x) x <- 0), names(d))

df <- d %>% replace_na(myList)

Você pode restringir facilmente a, por exemplo, colunas numéricas:

d$str <- c("string", NA)

myList <- myList[sapply(d, is.numeric)]

df <- d %>% replace_na(myList)
Antti
fonte
4

A função dedicada ( nafill/ setnafill) para esse fim está na data.tableversão recente

install.packages("data.table", repos="https://Rdatatable.gitlab.io/data.table")
library(data.table)
ans_df = nafill(df, fill=0)
setnafill(df, fill=0) # this one updates in-place
jangorecki
fonte
Para aqueles que estão com voto negativo, forneça feedback também, para que minha resposta possa ser melhorada.
jangorecki
3

Essa função simples extraída do Datacamp pode ajudar:

replace_missings <- function(x, replacement) {
  is_miss <- is.na(x)
  x[is_miss] <- replacement

  message(sum(is_miss), " missings replaced by the value ", replacement)
  x
}

Então

replace_missings(df, replacement = 0)
Fábio
fonte
3

Uma maneira fácil de escrever é if_nade hablar:

library(dplyr)
library(hablar)

df <- tibble(a = c(1, 2, 3, NA, 5, 6, 8))

df %>% 
  mutate(a = if_na(a, 0))

que retorna:

      a
  <dbl>
1     1
2     2
3     3
4     0
5     5
6     6
7     8
davsjob
fonte
2

Para substituir todas as NAs em um quadro de dados, você pode usar:

df %>% replace(is.na(.), 0)

Oliver Oliver
fonte
esta não é uma solução nova
jogo
1

se você deseja atribuir um novo nome após alterar os NAs em uma coluna específica, neste caso coluna V3, use também o seguinte:

my.data.frame$the.new.column.name <- ifelse(is.na(my.data.frame$V3),0,1)
Seyma Kalay
fonte