Tidyr: Lidando com NA em união

Criado em 13 jun. 2016  ·  30Comentários  ·  Fonte: tidyverse/tidyr

Considere o seguinte df :

ID   d1   d2   
1    G    G
2    A    G
3    A    A
4    G    A
5    NA   NA
6    G    G

Ao unir d1 e d2 :

tidyr::unite(df, new, d1, d2, remove = FALSE, sep = "")

A linha 5 dá NANA vez do esperado NA

  ID  new   d1   d2
1  1   GG    G    G
2  2   AG    A    G
3  3   AA    A    A
4  4   GA    G    A
5  5 NANA <NA> <NA>
6  6   GG    G    G
feature strings

Comentários muito úteis

Esta não é a solução solicitada, mas uma maneira limpa de obter o resultado desejado é:

library(tidyverse)
df <- tribble(
  ~ID, ~d1, ~d2,   
    1, "G", "G",
    2, "A", "G",
    3, "A", "A",
    4, "G", "A",
    5,  NA,  NA,
    6, "G", "G")
df %>% 
  replace_na(list(d1 = "", d2 = "")) %>% 
  unite(new, d1, d2, remove = FALSE, sep = "")
#> # A tibble: 6 × 4
#>      ID   new    d1    d2
#> * <dbl> <chr> <chr> <chr>
#> 1     1    GG     G     G
#> 2     2    AG     A     G
#> 3     3    AA     A     A
#> 4     4    GA     G     A
#> 5     5                  
#> 6     6    GG     G     G

Todos 30 comentários

unite() está apenas seguindo as regras de colagem padrão:

paste(NA, NA)
#> [1] "NA NA"

Eu estava pensando em um tratamento de pré-processamento semelhante a: with(df, ifelse(is.na(d1)|is.na(d2), NA, paste0(d1, d2))) .

Acho que você precisa de um argumento convincente sobre por que unite() deveria funcionar de maneira diferente de paste()

Bem, eu acho que unite() _deve_ funcionar como paste() mas talvez pudesse fornecer um argumento adicional para lidar com NA s, à la na.rm = TRUE

Acho que, em alguns casos, a opção omitir NA pode ser útil. Meu df tem muitas colunas que contêm principalmente NA, como resultado de várias rodadas de junção.

recipe  potato  tomato  cucumber    rock
A       potato  NA      cucumber    NA
B       NA      NA      NA          rock
C       NA      tomato  NA          NA
...

Então, eu estava tentando combinar as colunas em uma e remover o NA para ver as coisas melhor.

recipe  ingredients
A       potato,cucumber
B       rock
C       tomato
...

A solução não é difícil, apenas não tão organizada.

Acabei de me deparar com esse problema e também sugerir a adição de uma opção para lidar com NA em união. Na verdade, eu sugeriria que as seguintes expressões (embora talvez com um parâmetro extra para omitir NAs na unidade) devem produzir uma saída idêntica à sua entrada:

df <- data.frame (x = c ("a", "ab", "abc", NA))
df
x
1 a
2 ab
3 abc
4
df%>% separar (x, c ("a", "b"), extra = "mesclar")%>% unite (x, a, b, sep = "")
x
1 a NA
2 ab
3 abc
4 NA NA
Mensagem de aviso:
Poucos valores em 1 local: 1

Em outras palavras, se separar e unir são complementos, um deve ser capaz de usar um deles para reverter a operação do outro.

Esta não é a solução solicitada, mas uma maneira limpa de obter o resultado desejado é:

library(tidyverse)
df <- tribble(
  ~ID, ~d1, ~d2,   
    1, "G", "G",
    2, "A", "G",
    3, "A", "A",
    4, "G", "A",
    5,  NA,  NA,
    6, "G", "G")
df %>% 
  replace_na(list(d1 = "", d2 = "")) %>% 
  unite(new, d1, d2, remove = FALSE, sep = "")
#> # A tibble: 6 × 4
#>      ID   new    d1    d2
#> * <dbl> <chr> <chr> <chr>
#> 1     1    GG     G     G
#> 2     2    AG     A     G
#> 3     3    AA     A     A
#> 4     4    GA     G     A
#> 5     5                  
#> 6     6    GG     G     G

Eu também sugeriria adicionar uma opção de na.rm = TRUE para lidar com NAs. Embora a solução alternativa de @jennybc funcione para este problema em particular, ela mostrará espaços em branco e separadores quando o separador não for "".

Meu problema é o mesmo com o de @danrlu . Existe uma solução melhor e mais limpa para ignorar os NAs? Atualmente eu apenas unite todas as colunas e então str_replace_all NAs e separadores adjacentes com strings vazias.

Aqui está uma generalização de unite que permite criar uma nova coluna a partir de uma função arbitrária aplicada às colunas selecionadas como faria com unite . A função precisa retornar um vetor de caractere porque depende de pmap_chr , mas troque pmap_* a gosto.

`` `{r}
biblioteca (tidyverse)

df <- tribble (
~ ID, ~ d1, ~ d2, ~ d3,
1, "G", "G", "C",
2, NA, "G", "T",
3, "A", NA, "G",
4, "G", "A", "A",
5, NA, NA, NA,
6, "G", "G", "G")

'Semi-general \ code {unite} para vetorizar uma função em colunas de dataframe

'

'Aceita colunas de um dataframe e vetoriza / mapeia paralelamente uma função

'entre eles, retornando o resultado em uma nova coluna. A função deve retornar um

'vetor de caracteres porque \ code {purrr :: pmap_char} reforça a segurança de tipo.

'

' @param df A dataframe

' @param col Nome em branco (sem aspas) da coluna de resultados

' @param ... Nomes simples (sem aspas) de colunas de argumento

' @param fun Uma função que aceita tantos argumentos quanto o argumento fornecido

'colunas. É passado para \ code {purrr :: pmap_chr} então lambda em estilo de fórmula

'especificação também funciona.

' @param remove Se deve ou não remover as colunas de argumento (por omissão

'para \ code {true})

'

' @return Dataframe com nova coluna gerada pela aplicação de \ code {fun} para

'colunas de argumento elemento a elemento

combine <- função (df, col, ..., diversão, remover = TRUE) {
to_merge <- quos (...)
new_col <- quo_name (enquo (col))
merge_cols <- map_chr (to_merge, quo_name)

df <- mutate (df, !! new_col: = pmap_chr (list (!!! to_merge), fun))
if (remove) df <- select (df, -one_of (merge_cols))
df
}

combine (df, new, d1, d2, d3, fun = paste0)

> # Uma mesa: 6 x 2

> ID novo

>

> 1 1 GGC

> 2 2 NAGT

> 3 3 ANAG

> 4 4 GAA

> 5 5 NANANA

> 6 6 GGG

`` `

Não sou um grande fã dessa interface, mas achei mais útil do que unite algumas vezes. Eu também me peguei fazendo coisas estúpidas com esta função e sinto que é algum tipo de anti-padrão.

Se houver alguma mudança unite interface

Não estou convencido de que unite _deve_ funcionar como paste , pois é uma situação muito rara quando um usuário realmente deseja transformar NA valores em strings. Mais preocupantemente, em termos de consistência da API, separate apresentará NA s de uma forma que unite não pode reverter:

library(tidyr)

example <- tibble::data_frame(x = c('foo', 'foo bar', 'foo bar baz'))

example %>% separate(x, c('foo', 'bar', 'baz'), fill = 'right')    # without `fill = 'right'` same result with a message 
#> # A tibble: 3 x 3
#>     foo   bar   baz
#> * <chr> <chr> <chr>
#> 1   foo  <NA>  <NA>
#> 2   foo   bar  <NA>
#> 3   foo   bar   baz

example %>% 
    separate(x, c('foo', 'bar', 'baz'), fill = 'right') %>% 
    unite(x, foo:baz, sep = ' ')
#> # A tibble: 3 x 1
#>             x
#> *       <chr>
#> 1   foo NA NA
#> 2  foo bar NA
#> 3 foo bar baz

Se NA s estiverem no meio das colunas que obtêm unite d e então separate d, então o comportamento semelhante a paste permitiria o NA local a ser salvo (ao custo de exigir que sejam convertidos de strings em NA reais novamente), mas na maioria das vezes o tratamento de NA evita que as funções sejam inversas. Tornar na.rm = TRUE o padrão seria uma alteração importante, mas provavelmente não quebraria muito código.

Na verdade, existem duas solicitações de recursos neste tópico:

  1. Faça infecções de NAs de modo que se qualquer entrada for NA , a saída será NA
  2. Fornece uma maneira fácil de perder NA s.

2. parece ser a opção mais útil, então vou implementá-la.

@alexpghayes o plano é extrair um auxiliar geral para transformar as funções vetorizadas que acionam muitas funções tidyr em uma versão pública

Será muito fácil (e mais rápido) se isso puder ser implementado no nível stringi, então vou deixar isso de lado até que https://github.com/gagolews/stringi/issues/289 seja resolvido.

https://github.com/gagolews/stringi/issues/289 está fechado :)

@moodymudskipper está fechado, mas não implementado em stri_paste() .

Reexpressão mínima

library(tidyr)
df <- expand_grid(x = c("a", NA), y = c("b", NA))
unite(df, z, c("x", "y"), remove = FALSE)
#> # A tibble: 4 x 3
#>   z     x     y    
#>   <chr> <chr> <chr>
#> 1 a_b   a     b    
#> 2 a_NA  a     <NA> 
#> 3 NA_b  <NA>  b    
#> 4 NA_NA <NA>  <NA>

Criado em 07/03/2019 pelo pacote reprex (v0.2.1.9000)

Observe que você precisará de na.rm = TRUE (deixei o padrão como está para preservar a compatibilidade com versões anteriores, pois parece que muitas pessoas provavelmente contornaram o comportamento anterior de várias maneiras)

library(tidyr)
df <- expand_grid(x = c("a", NA), y = c("b", NA))
df %>% unite("z", x:y, na.rm = TRUE, remove = FALSE)
#> # A tibble: 4 x 3
#>   z     x     y    
#>   <chr> <chr> <chr>
#> 1 a_b   a     b    
#> 2 a     a     <NA> 
#> 3 b     <NA>  b    
#> 4 ""    <NA>  <NA>

Criado em 07/03/2019 pelo pacote reprex (v0.2.1.9000)

Olá @hadley ,

Estou tendo problemas para fazer com que na.rm = TRUE funcione dentro da função unite ().

Tentei o seguinte:

  1. Atualize R de 3.5.1 para 3.5.3
  2. Exclua os pacotes tidyverse e tidyr antigos
  3. instale o pacote tidyverse fresco
  4. execute o seguinte código:
> library("tidyr")
> df <- expand.grid(x = c("a", NA), y = c("b", NA))
> df
     x    y
1    a    b
2 <NA>    b
3    a <NA>
4 <NA> <NA>
> df %>% unite("z", x:y, na.rm = TRUE, remove = FALSE)
Error: `TRUE` must evaluate to column positions or names, not a logical vector
Call `rlang::last_error()` to see a backtrace

O que me dá este erro:

Error: `TRUE` must evaluate to column positions or names, not a logical vector
Call `rlang::last_error()` to see a backtrace

Erro de rastreamento:

> rlang::last_error()
<error>
message: `TRUE` must evaluate to column positions or names, not a logical vector
class:   `rlang_error`
backtrace:
  1. tidyr::unite(., "z", x:y, na.rm = TRUE, remove = FALSE)
 10. tidyselect::vars_select(colnames(data), ...)
 11. tidyselect:::bad_calls(bad, "must evaluate to { singular(.vars) } positions or names, \\\n       not { first_type }")
 12. tidyselect:::glubort(fmt_calls(calls), ..., .envir = .envir)
 13. tidyr::unite(., "z", x:y, na.rm = TRUE, remove = FALSE)
Call `rlang::last_trace()` to see the full backtrace

> rlang::last_trace()
     x
  1. \-df %>% unite("z", x:y, na.rm = TRUE, remove = FALSE)
  2.   +-base::withVisible(eval(quote(`_fseq`(`_lhs`)), env, env))
  3.   \-base::eval(quote(`_fseq`(`_lhs`)), env, env)
  4.     \-base::eval(quote(`_fseq`(`_lhs`)), env, env)
  5.       \-global::`_fseq`(`_lhs`)
  6.         \-magrittr::freduce(value, `_function_list`)
  7.           +-base::withVisible(function_list[[k]](value))
  8.           \-function_list[[k]](value)
  9.             +-tidyr::unite(., "z", x:y, na.rm = TRUE, remove = FALSE)
 10.             \-tidyr:::unite.data.frame(., "z", x:y, na.rm = TRUE, remove = FALSE)
 11.               \-tidyselect::vars_select(colnames(data), ...)
 12.                 \-tidyselect:::bad_calls(bad, "must evaluate to { singular(.vars) } positions or names, \\\n       not { first_type }")
 13.                   \-tidyselect:::glubort(fmt_calls(calls), ..., .envir = .envir)

@kasperav você provavelmente não instalou a versão de desenvolvimento do tidyr.

@hadley você está certo! Não tenho sorte em instalar a versão dev, então vou esperar que isso seja implementado em uma versão CRAN do tidyr :)

FWIW, descobri que o comportamento em que unir assume dois valores NA e produz uma string vazia é muito confuso e inesperado. Parece claro para mim que a união de dois valores de NA deve produzir um valor de NA.

Estou supondo que isso é mais claro para as pessoas que usaram paste muito :) Simples de consertar com na_if("") (mas é preciso esperar que a string vazia não fosse um valor distinto significativo de _NA_character nas colunas originais!)

Eu tenho um caso de uso em que preciso usar na.rm = TRUE e unite para 8 colunas. Uma das colunas é toda NA. Usar na.rm = T com unite parece ter um comportamento diferente quando uma das colunas é toda NA. Esse é o comportamento esperado? Devo simplesmente ignorar as colunas que são todas NA antes de usar unite ?

library(tidyr)
df_notwork <- expand_grid(x = c("a", NA), y = c(NA, NA))
df_notwork %>% unite("z", x:y, na.rm = TRUE, remove = FALSE)

# A tibble: 4 x 3
  z     x     y    
  <chr> <chr> <lgl>
1 a_NA  a     NA   
2 a_NA  a     NA   
3 NA    NA    NA   
4 NA    NA    NA 

Qual versão você está usando? Não é esse o resultado que obtenho (em 1.0.2.9000)

suppressPackageStartupMessages(require(tidyverse))
df_notwork <- expand_grid(x = c("a", NA), y = c(NA, NA))
df_notwork %>% unite("z", x:y, na.rm = T, remove = FALSE)
#> # A tibble: 4 x 3
#>   z     x     y    
#>   <chr> <chr> <lgl>
#> 1 "a"   a     NA   
#> 2 "a"   a     NA   
#> 3 ""    <NA>  NA   
#> 4 ""    <NA>  NA

Criado em 25/02/2020 pelo pacote reprex (v0.3.0)

Estou usando uma versão mais recente.

packageVersion("tidyverse")
[1] ‘1.3.0’

tidyverse é diferente de tidyr ; é uma coleção de outros pacotes reunidos para facilitar o carregamento. Portanto, ele terá uma versão diferente de todos os pacotes dentro dele. Verifique sua versão de tidyr .

Oh, desculpe, eu vi que você estava carregando tidyverse então presumi que essa era a versão à qual você estava se referindo. Sempre presumi que atualizar tidyverse atualizaria os pacotes contidos nele, então normalmente apenas atualizo aquele. Acho que é uma suposição inadequada!

Mesmo com a atualização do tidyr usando a versão GitHub, ainda tenho esse problema. Talvez seja outro pacote desatualizado?

packageVersion("tidyr")
[1] ‘1.0.2.9000’
> library(tidyr)
> df_notwork <- expand_grid(x = c("a", NA), y = c(NA, NA))
> df_notwork %>% unite("z", x:y, na.rm = TRUE, remove = FALSE)
# A tibble: 4 x 3
  z     x     y    
  <chr> <chr> <lgl>
1 a_NA  a     NA   
2 a_NA  a     NA   
3 NA    NA    NA   
4 NA    NA    NA  

Interessante. Não sei por que estamos obtendo resultados diferentes.

Independentemente disso, me parece que seus NA não estão sendo removidos, apesar de na.rm = F .

Sim, eu tentaria atualizar seus outros pacotes e ver se isso resolve o problema. Mas, como expand_grid e unite são de tidyr não tenho certeza de por que esse seria o caso.

Parece que minha versão de tidyselect estava bastante desatualizada (<1.0). Eu atualizei isso e agora está funcionando conforme o esperado.

packageVersion("tidyr")
[1] ‘1.0.2.9000’

packageVersion("tidyselect")
[1] ‘1.0.0’

library(tidyr)
df_notwork <- expand_grid(x = c("a", NA), y = c(NA, NA))
df_notwork %>% unite("z", x:y, na.rm = TRUE, remove = FALSE)

# A tibble: 4 x 3
  z     x     y    
  <chr> <chr> <lgl>
1 "a"   a     NA   
2 "a"   a     NA   
3 ""    NA    NA   
4 ""    NA    NA 

Olá,

Atualizei para todas as versões mais recentes dos pacotes (tidyr 1.0.2.900, tidyselect 1.0.0) e ainda estou recebendo o mesmo erro. Eu experimentei o df_notwork de Lindsay e obtive a mesma versão que ela tinha antes das atualizações. Qualquer ajuda seria apreciada!

@anjaollodart - talvez você possa tentar atualizar pacotes adicionais dos quais tidyr depende. É apenas um palpite, mas a necessidade de atualizar separadamente tidyselect de tidyr foi surpreendente para mim, então talvez haja outra dependência de pacote que tenha o mesmo problema.

Querida Lindsay,

Resolvi esse problema sozinho quando usei meu próprio quadro de dados (não aquele que
estava no exemplo). E assim que fiz isso, em 10-15 minutos, eu apaguei
o comentário porque o problema não era sobre esta função.
É estranho que o GitHub ainda envie esse comentário.

Obrigado,

Julia

Em Qui, 2 de abril de 2020 às 16h02 Lindsay (Carr) Platt <
notificaçõ[email protected]> escreveu:

@anjaollodart https://github.com/anjaollodart - talvez você possa tentar
atualizar pacotes adicionais dos quais o tidyr depende. É só um palpite,
mas a necessidade de atualizar separadamente o tidyselect do tidyr foi surpreendente para
eu, então talvez haja outra dependência de pacote que tenha o mesmo problema.

-
Você está recebendo isto porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/tidyverse/tidyr/issues/203#issuecomment-607865602 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/AFUQAKM6SJFDD6MZGPN2B6DRKSLHHANCNFSM4CGRBQRQ
.

Esta página foi útil?
0 / 5 - 0 avaliações