Pacote R tidyr e suas novas funções pivot_longer e pivot_wider
Pacote arrumador incluído no núcleo de uma das bibliotecas mais populares da linguagem R - arrumado.
O principal objetivo do pacote é trazer os dados de forma precisa.
Já disponível no Habré publicação dedicado a este pacote, mas remonta a 2015. E quero falar sobre as mudanças mais atuais, que foram anunciadas há poucos dias por seu autor, Hedley Wickham.
SJK: Reunir() e espalhar() serão obsoletos?
Hadley Wickham: Até certo ponto. Não recomendaremos mais o uso dessas funções e corrigiremos bugs nelas, mas elas continuarão presentes no pacote em seu estado atual.
Conteúdo
Se você estiver interessado em análise de dados, talvez esteja interessado em meu telegrama и Youtube canais. A maior parte do conteúdo é dedicada à linguagem R.
Meta arrumador - ajudá-lo a trazer os dados para um formato chamado organizado. Dados puros são dados onde:
Cada variável está em uma coluna.
Cada observação é uma string.
Cada valor é uma célula.
É muito mais fácil e conveniente trabalhar com dados apresentados de forma organizada ao realizar análises.
Principais funções incluídas no pacote tidyr
tidyr contém um conjunto de funções projetadas para transformar tabelas:
fill() — preencher valores faltantes em uma coluna com valores anteriores;
separate() — divide um campo em vários usando um separador;
unite() — realiza a operação de combinar vários campos em um, a ação inversa da função separate();
pivot_longer() — uma função que converte dados de formato largo para formato longo;
pivot_wider() - uma função que converte dados de formato longo para formato largo. A operação inversa daquela realizada pela função pivot_longer().
gather()obsoleto — uma função que converte dados de formato largo para formato longo;
spread()obsoleto - uma função que converte dados de formato longo para formato largo. A operação inversa daquela realizada pela função gather().
Novo conceito para conversão de dados de formato largo para formato longo e vice-versa
Anteriormente, funções eram usadas para esse tipo de transformação gather() и spread(). Ao longo dos anos de existência destas funções, tornou-se óbvio que para a maioria dos utilizadores, incluindo o autor do pacote, os nomes destas funções e os seus argumentos não eram totalmente óbvios, e causavam dificuldades em encontrá-las e compreender qual destas funções converte um quadro de data do formato largo para o formato longo e vice-versa.
A este respeito, em arrumador Foram adicionadas duas funções novas e importantes, projetadas para transformar quadros de datas.
Novos recursos pivot_longer() и pivot_wider() foram inspirados por alguns dos recursos do pacote cdata, criado por John Mount e Nina Zumel.
Instalando a versão mais atual do tidyr 0.8.3.9000
Para instalar a versão nova e mais atual do pacote arrumador0.8.3.9000, onde novos recursos estiverem disponíveis, use o código a seguir.
devtools::install_github("tidyverse/tidyr")
No momento em que este artigo foi escrito, essas funções estavam disponíveis apenas na versão dev do pacote no GitHub.
Transição para novos recursos
Na verdade, não é difícil transferir scripts antigos para trabalhar com novas funções, para melhor compreensão pegarei um exemplo da documentação de funções antigas e mostrarei como as mesmas operações são realizadas usando novas pivot_*() funções.
Converta formato largo em formato longo.
Código de exemplo da documentação da função de coleta
# example
library(dplyr)
stocks <- data.frame(
time = as.Date('2009-01-01') + 0:9,
X = rnorm(10, 0, 1),
Y = rnorm(10, 0, 2),
Z = rnorm(10, 0, 4)
)
# old
stocks_gather <- stocks %>% gather(key = stock,
value = price,
-time)
# new
stocks_long <- stocks %>% pivot_longer(cols = -time,
names_to = "stock",
values_to = "price")
Convertendo formato longo em formato largo.
Código de exemplo da documentação da função spread
# old
stocks_spread <- stocks_gather %>% spread(key = stock,
value = price)
# new
stock_wide <- stocks_long %>% pivot_wider(names_from = "stock",
values_from = "price")
Porque nos exemplos acima de trabalho com pivot_longer() и pivot_wider(), na tabela original AÇÕES nenhuma coluna listada nos argumentos nomes_para и valores_para seus nomes devem estar entre aspas.
Uma tabela que o ajudará a descobrir mais facilmente como passar a trabalhar com um novo conceito arrumador.
Nota do autor
Todo o texto abaixo é adaptativo, diria até tradução livre vinhetas do site oficial da biblioteca tidyverse.
Um exemplo simples de conversão de dados de formato largo para formato longo
pivot_longer () — torna os conjuntos de dados mais longos, reduzindo o número de colunas e aumentando o número de linhas.
Para executar os exemplos apresentados no artigo, primeiro você precisa conectar os pacotes necessários:
library(tidyr)
library(dplyr)
library(readr)
Digamos que temos uma tabela com os resultados de uma pesquisa que (entre outras coisas) perguntou às pessoas sobre sua religião e renda anual:
Esta tabela contém os dados religiosos dos entrevistados em linhas, e os níveis de renda estão espalhados pelos nomes das colunas. O número de entrevistados de cada categoria é armazenado nos valores das células na intersecção da religião e do nível de renda. Para deixar a tabela em um formato limpo e correto, basta usar pivot_longer():
Primeiro argumento cols, descreve quais colunas precisam ser mescladas. Neste caso, todas as colunas, exceto tempo.
Argumento nomes_para dá o nome da variável que será criada a partir dos nomes das colunas que concatenamos.
valores_para dá o nome de uma variável que será criada a partir dos dados armazenados nos valores das células das colunas mescladas.
Especificações
Esta é uma nova funcionalidade do pacote arrumador, que anteriormente não estava disponível ao trabalhar com funções legadas.
Uma especificação é um quadro de dados, cada linha corresponde a uma coluna no novo quadro de data de saída e duas colunas especiais que começam com:
. Nome contém o nome da coluna original.
.valor contém o nome da coluna que conterá os valores das células.
As colunas restantes da especificação refletem como a nova coluna exibirá o nome das colunas compactadas de . Nome.
A especificação descreve os metadados armazenados em um nome de coluna, com uma linha para cada coluna e uma coluna para cada variável, combinados com o nome da coluna, esta definição pode parecer confusa no momento, mas depois de olhar alguns exemplos ela se tornará muito mais claro.
O objetivo da especificação é que você pode recuperar, modificar e definir novos metadados para o dataframe que está sendo convertido.
Para trabalhar com especificações ao converter uma tabela de formato largo para formato longo, use a função pivot_longer_spec().
O funcionamento dessa função é que ela pega qualquer período de data e gera seus metadados da maneira descrita acima.
Como exemplo, vamos pegar o conjunto de dados who fornecido com o pacote arrumador. Este conjunto de dados contém informações fornecidas pela organização internacional de saúde sobre a incidência da tuberculose.
who
#> # A tibble: 7,240 x 60
#> country iso2 iso3 year new_sp_m014 new_sp_m1524 new_sp_m2534
#> <chr> <chr> <chr> <int> <int> <int> <int>
#> 1 Afghan… AF AFG 1980 NA NA NA
#> 2 Afghan… AF AFG 1981 NA NA NA
#> 3 Afghan… AF AFG 1982 NA NA NA
#> 4 Afghan… AF AFG 1983 NA NA NA
#> 5 Afghan… AF AFG 1984 NA NA NA
#> 6 Afghan… AF AFG 1985 NA NA NA
#> 7 Afghan… AF AFG 1986 NA NA NA
#> 8 Afghan… AF AFG 1987 NA NA NA
#> 9 Afghan… AF AFG 1988 NA NA NA
#> 10 Afghan… AF AFG 1989 NA NA NA
#> # … with 7,230 more rows, and 53 more variables
Vamos construir sua especificação.
spec <- who %>%
pivot_longer_spec(new_sp_m014:newrel_f65, values_to = "count")
campos país, iso2, iso3 já são variáveis. Nossa tarefa é inverter as colunas com novo_sp_m014 em newrel_f65.
Os nomes dessas colunas armazenam as seguintes informações:
Prefixo new_ indica que a coluna contém dados sobre novos casos de tuberculose, o quadro de datas atual contém informações apenas sobre novas doenças, portanto este prefixo no contexto atual não tem qualquer significado.
sp/rel/sp/ep descreve um método para diagnosticar uma doença.
m/f sexo do paciente.
014/1524/2535/3544/4554/65 faixa etária do paciente.
Podemos dividir essas colunas usando a função extract()usando expressão regular.
Finalmente, para aplicar a especificação que criamos ao período original que precisamos usar um argumento especulação em função pivot_longer().
who %>% pivot_longer(spec = spec)
#> # A tibble: 405,440 x 8
#> country iso2 iso3 year diagnosis gender age count
#> <chr> <chr> <chr> <int> <chr> <fct> <ord> <int>
#> 1 Afghanistan AF AFG 1980 sp m 014 NA
#> 2 Afghanistan AF AFG 1980 sp m 1524 NA
#> 3 Afghanistan AF AFG 1980 sp m 2534 NA
#> 4 Afghanistan AF AFG 1980 sp m 3544 NA
#> 5 Afghanistan AF AFG 1980 sp m 4554 NA
#> 6 Afghanistan AF AFG 1980 sp m 5564 NA
#> 7 Afghanistan AF AFG 1980 sp m 65 NA
#> 8 Afghanistan AF AFG 1980 sp f 014 NA
#> 9 Afghanistan AF AFG 1980 sp f 1524 NA
#> 10 Afghanistan AF AFG 1980 sp f 2534 NA
#> # … with 405,430 more rows
Tudo o que acabamos de fazer pode ser esquematicamente representado da seguinte forma:
Especificação usando vários valores (.value)
No exemplo acima, a coluna de especificação .valor continha apenas um valor; na maioria dos casos, esse é o caso.
Mas ocasionalmente pode surgir uma situação em que você precisa coletar dados de colunas com diferentes tipos de dados em valores. Usando uma função legada spread() isso seria muito difícil de fazer.
O exemplo abaixo foi retirado de vinhetas para o pacote Tabela de dados.
O período criado contém dados sobre os filhos de uma família em cada linha. As famílias podem ter um ou dois filhos. Para cada criança, os dados são fornecidos sobre a data de nascimento e sexo, e os dados de cada criança estão em colunas separadas; nossa tarefa é trazer esses dados para o formato correto para análise.
Observe que temos duas variáveis com informações sobre cada criança: seu sexo e data de nascimento (colunas com o prefixo rolha contém data de nascimento, colunas com prefixo gênero conter o sexo da criança). O resultado esperado é que eles apareçam em colunas separadas. Podemos fazer isso gerando uma especificação na qual a coluna .value terá dois significados diferentes.
spec <- family %>%
pivot_longer_spec(-family) %>%
separate(col = name, into = c(".value", "child"))%>%
mutate(child = parse_number(child))
#> # A tibble: 4 x 3
#> .name .value child
#> <chr> <chr> <dbl>
#> 1 dob_child1 dob 1
#> 2 dob_child2 dob 2
#> 3 gender_child1 gender 1
#> 4 gender_child2 gender 2
Então, vamos dar uma olhada passo a passo nas ações executadas pelo código acima.
pivot_longer_spec(-family) — crie uma especificação que comprima todas as colunas existentes, exceto a coluna da família.
separate(col = name, into = c(".value", "child")) - dividir a coluna . Nome, que contém os nomes dos campos de origem, usando o sublinhado e inserindo os valores resultantes nas colunas .valor и criança.
mutate(child = parse_number(child)) — transformar os valores do campo criança do tipo de dados de texto para numérico.
Agora podemos aplicar a especificação resultante ao dataframe original e trazer a tabela para o formato desejado.
Usamos argumento na.rm = TRUE, porque a forma atual dos dados força a criação de linhas extras para observações inexistentes. Porque a família 2 tem apenas um filho, na.rm = TRUE garante que a família 2 terá uma linha na saída.
Convertendo quadros de data de formato longo para largo
pivot_wider() - é a transformação inversa e vice-versa aumenta o número de colunas do quadro de data reduzindo o número de linhas.
Esse tipo de transformação raramente é usado para deixar os dados em um formato preciso; no entanto, essa técnica pode ser útil para criar tabelas dinâmicas usadas em apresentações ou para integração com outras ferramentas.
Na verdade as funções pivot_longer() и pivot_wider() são simétricos e produzem ações inversas entre si, ou seja: df %>% pivot_longer(spec = spec) %>% pivot_wider(spec = spec) и df %>% pivot_wider(spec = spec) %>% pivot_longer(spec = spec) retornará o df original.
O exemplo mais simples de conversão de uma tabela para um formato amplo
Para demonstrar como a função funciona pivot_wider() usaremos o conjunto de dados encontros_peixe, que armazena informações sobre como diferentes estações registram o movimento dos peixes ao longo do rio.
#> # A tibble: 114 x 3
#> fish station seen
#> <fct> <fct> <int>
#> 1 4842 Release 1
#> 2 4842 I80_1 1
#> 3 4842 Lisbon 1
#> 4 4842 Rstr 1
#> 5 4842 Base_TD 1
#> 6 4842 BCE 1
#> 7 4842 BCW 1
#> 8 4842 BCE2 1
#> 9 4842 BCW2 1
#> 10 4842 MAE 1
#> # … with 104 more rows
Na maioria dos casos, esta tabela será mais informativa e mais fácil de usar se você apresentar informações para cada estação em uma coluna separada.
fish_encounters %>% pivot_wider(names_from = station, values_from = seen)
#> # A tibble: 19 x 12
#> fish Release I80_1 Lisbon Rstr Base_TD BCE BCW BCE2 BCW2 MAE
#> <fct> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 4842 1 1 1 1 1 1 1 1 1 1
#> 2 4843 1 1 1 1 1 1 1 1 1 1
#> 3 4844 1 1 1 1 1 1 1 1 1 1
#> 4 4845 1 1 1 1 1 NA NA NA NA NA
#> 5 4847 1 1 1 NA NA NA NA NA NA NA
#> 6 4848 1 1 1 1 NA NA NA NA NA NA
#> 7 4849 1 1 NA NA NA NA NA NA NA NA
#> 8 4850 1 1 NA 1 1 1 1 NA NA NA
#> 9 4851 1 1 NA NA NA NA NA NA NA NA
#> 10 4854 1 1 NA NA NA NA NA NA NA NA
#> # … with 9 more rows, and 1 more variable: MAW <int>
Este conjunto de dados apenas registra informações quando peixes são detectados pela estação, ou seja, se algum peixe não foi registrado por alguma estação, então esse dado não estará na tabela. Isso significa que a saída será preenchida com NA.
Porém, neste caso sabemos que a ausência de registro significa que o peixe não foi avistado, então podemos usar o argumento valores_preenchimento em função pivot_wider() e preencha esses valores ausentes com zeros:
Gerando um nome de coluna a partir de diversas variáveis de origem
Imagine que temos uma tabela contendo uma combinação de produto, país e ano. Para gerar um período de teste, você pode executar o seguinte código:
df <- expand_grid(
product = c("A", "B"),
country = c("AI", "EI"),
year = 2000:2014
) %>%
filter((product == "A" & country == "AI") | product == "B") %>%
mutate(value = rnorm(nrow(.)))
#> # A tibble: 45 x 4
#> product country year value
#> <chr> <chr> <int> <dbl>
#> 1 A AI 2000 -2.05
#> 2 A AI 2001 -0.676
#> 3 A AI 2002 1.60
#> 4 A AI 2003 -0.353
#> 5 A AI 2004 -0.00530
#> 6 A AI 2005 0.442
#> 7 A AI 2006 -0.610
#> 8 A AI 2007 -2.77
#> 9 A AI 2008 0.899
#> 10 A AI 2009 -0.106
#> # … with 35 more rows
Nossa tarefa é expandir o quadro de dados para que uma coluna contenha dados para cada combinação de produto e país. Para fazer isso, basta passar o argumento nomes_de um vetor contendo os nomes dos campos a serem mesclados.
Você também pode aplicar especificações a uma função pivot_wider(). Mas quando submetido a pivot_wider() a especificação faz a conversão oposta pivot_longer(): As colunas especificadas em . Nome, usando valores de .valor e outras colunas.
Para este conjunto de dados, você pode gerar uma especificação personalizada se quiser que cada combinação possível de país e produto tenha sua própria coluna, não apenas aquelas presentes nos dados:
#> # A tibble: 4 x 4
#> .name product country .value
#> <chr> <chr> <chr> <chr>
#> 1 A_AI A AI value
#> 2 A_EI A EI value
#> 3 B_AI B AI value
#> 4 B_EI B EI value
df %>% pivot_wider(spec = spec) %>% head()
#> # A tibble: 6 x 5
#> year A_AI A_EI B_AI B_EI
#> <int> <dbl> <dbl> <dbl> <dbl>
#> 1 2000 -2.05 NA 0.607 1.20
#> 2 2001 -0.676 NA 1.65 -0.114
#> 3 2002 1.60 NA -0.0245 0.501
#> 4 2003 -0.353 NA 1.30 -0.459
#> 5 2004 -0.00530 NA 0.921 -0.0589
#> 6 2005 0.442 NA -1.55 0.594
Vários exemplos avançados de trabalho com o novo conceito tidyr
Limpeza de dados usando o conjunto de dados de Renda e Aluguel do Censo dos EUA como exemplo.
Conjunto de dados us_rent_income contém informações sobre renda mediana e aluguel para todos os estados dos EUA em 2017 (conjunto de dados disponível no pacote arrumado).
us_rent_income
#> # A tibble: 104 x 5
#> GEOID NAME variable estimate moe
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 01 Alabama income 24476 136
#> 2 01 Alabama rent 747 3
#> 3 02 Alaska income 32940 508
#> 4 02 Alaska rent 1200 13
#> 5 04 Arizona income 27517 148
#> 6 04 Arizona rent 972 4
#> 7 05 Arkansas income 23789 165
#> 8 05 Arkansas rent 709 5
#> 9 06 California income 29454 109
#> 10 06 California rent 1358 3
#> # … with 94 more rows
Na forma em que os dados são armazenados no conjunto de dados us_rent_income trabalhar com eles é extremamente inconveniente, por isso gostaríamos de criar um conjunto de dados com colunas: alugar, rent_moe, como, renda_moe. Existem muitas maneiras de criar essa especificação, mas o ponto principal é que precisamos gerar cada combinação de valores de variáveis e estimativa/moee, em seguida, gere o nome da coluna.
Às vezes, trazer um conjunto de dados para o formato desejado requer várias etapas.
Conjunto de dados banco_mundo_pop contém dados do Banco Mundial sobre a população de cada país entre 2000 e 2018.
Nosso objetivo é criar um conjunto de dados organizado com cada variável em sua própria coluna. Não está claro exatamente quais etapas são necessárias, mas começaremos com o problema mais óbvio: o ano está espalhado por várias colunas.
Para corrigir isso você precisa usar a função pivot_longer().
O próximo passo é observar a variável indicadora. pop2 %>% count(indicator)
#> # A tibble: 4 x 2
#> indicator n
#> <chr> <int>
#> 1 SP.POP.GROW 4752
#> 2 SP.POP.TOTL 4752
#> 3 SP.URB.GROW 4752
#> 4 SP.URB.TOTL 4752
Onde SP.POP.GROW é o crescimento populacional, SP.POP.TOTL é a população total e SP.URB. * a mesma coisa, mas apenas para áreas urbanas. Vamos dividir esses valores em duas variáveis: área - área (total ou urbana) e uma variável contendo dados reais (população ou crescimento):
Tabular esta lista é bastante difícil porque não existe uma variável que identifique quais dados pertencem a qual contato. Podemos corrigir isso observando que os dados de cada novo contato começam com “nome”, então podemos criar um identificador único e incrementá-lo em um cada vez que a coluna do campo contiver o valor “nome”:
#> # A tibble: 6 x 3
#> field value person_id
#> <chr> <chr> <int>
#> 1 name Jiena McLellan 1
#> 2 company Toyota 1
#> 3 name John Smith 2
#> 4 company google 2
#> 5 email [email protected] 2
#> 6 name Huxley Ratcliffe 3
Agora que temos um ID exclusivo para cada contato, podemos transformar o campo e o valor em colunas:
#> # A tibble: 3 x 4
#> person_id name company email
#> <int> <chr> <chr> <chr>
#> 1 1 Jiena McLellan Toyota <NA>
#> 2 2 John Smith google [email protected]
#> 3 3 Huxley Ratcliffe <NA> <NA>
Conclusão
Minha opinião pessoal é que o novo conceito arrumador verdadeiramente mais intuitivo e significativamente superior em funcionalidade às funções legadas spread() и gather(). Espero que este artigo tenha ajudado você a lidar com pivot_longer() и pivot_wider().