R: Remodelação de dados no R: Um guia completo sobre o tidyr
Última atualização: 2026-08-26
Em projetos reais, 80% dos dados consistem em “tabelas largas” (uma entidade por linha, com campos que representam atributos), mas a análise geralmente requer “tabelas longas” (uma observação por linha). Nesta aula, aprenderemos como “transformar” dados no R — usando a reformulação do tidyr + a junção do dplyr — para converter dados de qualquer formato para o formato necessário para a análise.
Ao concluir esta lição, você será capaz de: converter entre tabelas largas e longas, dividir e unir colunas, unir várias tabelas por meio de junções e criar qualquer tipo de relatório.
1. O que você vai aprender
- A diferença entre mesas largas e mesas compridas
- pivot_longer largura → comprimento
- pivot_wider Comprimento → Largura
- separar / unir: divisão e fusão de colunas
- dplyr: 4 tipos de junção (esquerda/direita/interna/completa)
- Prático: Convertendo dados de vendas de comprimento para largura
- Geração de relatórios
2. Um ponto crítico na transformação de dados
(1) Desafio: tabelas extensas são difíceis de analisar
Xiao Zhao recebeu um relatório trimestral de vendas (tabela ampla):
Region Q1 Q2 Q3 Q4
Beijing 1000 1200 1100 1500
Shanghai 1500 1800 1700 2000
Guangzhou 800 900 1000 1200
O gerente solicitou um “Gráfico de tendência de vendas trimestrais” — o ggplot2 requer uma tabela longa:
Region Quarter Sales
Beijing Q1 1000
Beijing Q2 1200
...
Se levar 5 minutos para copiar e colar no Excel; Python pd.melt — uma linha; R tidyr —
(2) Solução usando R
library(tidyr)
# Wide Table → Long Table(One line)
sales_long <- sales_wide |>
pivot_longer(
cols = c(Q1, Q2, Q3, Q4),
names_to = "Quarter",
values_to = "Sales"
)
Converta largura em altura com apenas uma linha de código.
3. Mesas largas x mesas compridas
(1) Comparação entre as duas formas
graph LR
A[Wide Table Wide] -->|pivot_longer| B[Long Table Long]
B -->|pivot_wider| A
subgraph Width Examples
C[Region Q1 Q2 Q3 Q4]
end
subgraph Long Example
D[Region Quarter Sales]
end
(2) Quando usar cada um?
| Cenário | Recomendação |
|---|---|
| Armazenamento de dados | Tabela ampla (reduz o número de linhas) |
| Visualizar relatórios | Tabela ampla (compatível com o Excel) |
| Gráficos com ggplot2 | Tabela longa (obrigatória) |
| Análise com dplyr | Tabela longa (compatível com group_by) |
| Aprendizado de máquina | Tabela ampla (1 amostra por linha) |
4. pivot_longer(): Largura → Comprimento
(1) Sintaxe básica
pivot_longer(
data,
cols, # Columns to convert
names_to = "name", # Newly Listed(Originally listed)
values_to = "value", # Newly Listed(Original value)
names_prefix = "", # Listed Public Prefixes
names_sep = NULL, # Delimiter(Split Column Names)
names_pattern = NULL, # Regular(Extract Column Names)
values_drop_na = FALSE
)
(2) Conversões básicas
library(tidyr)
# Wide Table
sales_wide <- tibble(
region = c("Beijing", "Shanghai", "Guangzhou"),
Q1 = c(1000, 1500, 800),
Q2 = c(1200, 1800, 900),
Q3 = c(1100, 1700, 1000),
Q4 = c(1500, 2000, 1200)
)
print(sales_wide)
# # A tibble: 3 × 5
# region Q1 Q2 Q3 Q4
# <chr> <dbl> <dbl> <dbl> <dbl>
# 1 Beijing 1000 1200 1100 1500
# 2 Shanghai 1500 1800 1700 2000
# 3 Guangzhou 800 900 1000 1200
# Rotate the Long Table
sales_long <- sales_wide |>
pivot_longer(
cols = c(Q1, Q2, Q3, Q4),
names_to = "quarter",
values_to = "sales"
)
print(sales_long)
# # A tibble: 12 × 3
# region quarter sales
# <chr> <chr> <dbl>
# 1 Beijing Q1 1000
# 2 Beijing Q2 1200
# 3 Beijing Q3 1100
# 4 Beijing Q4 1500
# 5 Shanghai Q1 1500
# 6 Shanghai Q2 1800
# 7 Shanghai Q3 1700
# 8 Shanghai Q4 2000
# ...
(3) 4 maneiras de selecionar colunas
# 1. Explicitly listed vectors
cols = c(Q1, Q2, Q3, Q4)
# 2. starts_with()(Most Commonly Used)
cols = starts_with("Q")
# 3. Number Range
cols = 2:5 # Columns 2-5
# 4. Listed pattern(Regular)
cols = matches("^Q\\d+$")
# 5. Exclude a specific column
cols = -region
(4) Prática: Dividindo nomes de colunas complexos
# Listing includes prefixes
df <- tibble(
id = 1:3,
sales_2022 = c(100, 200, 300),
sales_2023 = c(150, 250, 350),
profit_2022 = c(10, 20, 30),
profit_2023 = c(15, 25, 35)
)
# Split sales_2022 → Type=Sales, Year=2022
df_long <- df |>
pivot_longer(
cols = -id,
names_to = c("type", "year"),
names_sep = "_",
values_to = "value"
)
print(df_long)
# # A tibble: 12 × 4
# id type year value
# <int> <chr> <chr> <dbl>
# 1 1 sales 2022 100
# 2 1 sales 2023 150
# 3 1 profit 2022 10
# 4 1 profit 2023 15
# ...
(5) Prática: Extração de dados com expressões regulares
# Listing Format:Variable_Year_Quarter
df <- tibble(
id = 1:2,
sales_2024_Q1 = c(100, 200),
sales_2024_Q2 = c(150, 250),
cost_2024_Q1 = c(50, 80),
cost_2024_Q2 = c(70, 100)
)
# Extract using regular expressions
df_long <- df |>
pivot_longer(
cols = -id,
names_to = c("type", "year", "quarter"),
names_pattern = "(\\w+)_(\\d+)_(\\w+)",
values_to = "value"
)
print(df_long)
5. pivot_wider(): Comprimento → Largura
(1) Sintaxe básica
pivot_wider(
data,
id_cols = NULL, # Identifier Column(Keep as a line)
names_from = name, # Sources of New Listings
values_from = value, # Source of Newly Listed Values
values_fill = NULL, # Missing Value Imputation
names_prefix = "" # New Listing Prefix
)
(2) Conversões básicas
# Converting a Long Table to a Wide Table
sales_wide_again <- sales_long |>
pivot_wider(
id_cols = region,
names_from = quarter,
values_from = sales
)
print(sales_wide_again)
# # A tibble: 3 × 5
# region Q1 Q2 Q3 Q4
# <chr> <dbl> <dbl> <dbl> <dbl>
# 1 Beijing 1000 1200 1100 1500
# ...
(3) Aplicação prática: como lidar com valores ausentes
# There are missing data points(Not all region All of them have everything quarter)
df <- tibble(
region = c("A", "A", "B", "C", "C"),
quarter = c("Q1", "Q2", "Q1", "Q2", "Q3"),
sales = c(100, 200, 150, 250, 300)
)
# Width Conversion Table(Fill in the blank 0)
df_wide <- df |>
pivot_wider(
id_cols = region,
names_from = quarter,
values_from = sales,
values_fill = 0
)
print(df_wide)
# # A tibble: 3 × 4
# region Q1 Q2 Q3
# <chr> <dbl> <dbl> <dbl>
# 1 A 100 200 0
# 2 B 150 0 0
# 3 C 0 250 300
6. separate() / unite(): Dividir e mesclar colunas
(1) separate() Dividir
# Split the Date Column
df <- tibble(date = c("2024-01-15", "2024-02-20"))
# Split by delimiter
df |> separate(date, into = c("year", "month", "day"), sep = "-")
# # A tibble: 2 × 3
# year month day
# <chr> <chr> <chr>
# 1 2024 01 15
# 2 2024 02 20
# Breakdown by Location
df |> separate(date, into = c("year", "rest"), sep = 4)
# year rest
# "2024" "-01-15"
# Split using regular expressions
df |> separate(date, into = c("year", "month", "day"),
sep = "(-)")
(2) unite() Mesclar
# Merge Year/Month/Day
df_split <- tibble(
year = c("2024", "2024"),
month = c("01", "02"),
day = c("15", "20")
)
df_split |> unite("date", year, month, day, sep = "-")
# # A tibble: 2 × 1
# date
# <chr>
# 1 2024-01-15
# 2 2024-02-20
7. dplyr: 4 tipos de junções
(1) Tabela de referência rápida para tipos de junção
graph TB
A[dplyr 4 types of join] --> B[left_join<br/>Keep the entire table]
A --> C[right_join<br/>Keep the entire right-hand table]
A --> D[inner_join<br/>Find the Intersection]
A --> E[full_join<br/>Union-Disjoint Set]
style A fill:#fff3cd
style B fill:#cce5ff
style C fill:#d4edda
style D fill:#f8d7da
style E fill:#e1d4ff
(2) Analogia com SQL
| dplyr | SQL | Significado |
|---|---|---|
left_join(a, b, by) |
LEFT JOIN |
Manter tudo como está |
right_join(a, b, by) |
RIGHT JOIN |
Manter todos os b |
inner_join(a, b, by) |
INNER JOIN |
Reservar a∩b |
full_join(a, b, by) |
FULL OUTER JOIN |
Reservar a∪b |
(3) Prática: Unindo duas tabelas
# Customer Table
customers <- tibble(
id = 1:5,
name = c("Alice", "Bob", "Charlie", "Diana", "Eve")
)
# Orders Table
orders <- tibble(
customer_id = c(1, 1, 2, 4, 4, 6),
amount = c(100, 200, 150, 300, 250, 500)
)
# 1. left_join:Retain all customers(Those with no orders are NA)
customers |> left_join(orders, by = c("id" = "customer_id"))
# # A tibble: 7 × 3 ← 5 Customer + 6 Order = 7 row(Charlie/Eve No orders)
# id name amount
# 1 1 Alice 100
# 2 1 Alice 200
# 3 2 Bob 150
# 4 3 Charlie NA ← No orders
# 5 4 Diana 300
# 6 4 Diana 250
# 7 5 Eve NA ← No orders
# 2. inner_join:Find the Intersection
customers |> inner_join(orders, by = c("id" = "customer_id"))
# # A tibble: 6 × 3 ← 4 A customer with an order
# 3. full_join:Union-Disjoint Set
customers |> full_join(orders, by = c("id" = "customer_id"))
# # A tibble: 7 × 3 ← Includes id=6 "No customer" Order
# 4. right_join:Keep all orders
customers |> right_join(orders, by = c("id" = "customer_id"))
# # A tibble: 6 × 3 ← 6 Orders
(4) Junção com várias chaves
# Sort by multiple columns join
df1 |> left_join(df2, by = c("year", "month", "id"))
8. Prática: Analisando dados de vendas em várias tabelas
A seguir, apresentamos um exemplo de um fluxo de trabalho completo que reúne todos os conceitos de refatoração abordados nesta lição.
▶ Exemplo: Análise detalhada dos dados de vendas do 4º trimestre
# ============================================
# 4 In-Depth Analysis of Quarterly Sales Data
# Features:Wide↔Long Conversion + Multiple Tables join + Reports
# ============================================
library(tidyr)
library(dplyr)
# 1. Prepare the raw data(Wide Table)
sales_wide <- tibble(
region = c("Beijing", "Shanghai", "Guangzhou", "Shenzhen"),
Q1 = c(1000, 1500, 800, 1200),
Q2 = c(1200, 1800, 900, 1400),
Q3 = c(1100, 1700, 1000, 1300),
Q4 = c(1500, 2000, 1200, 1600)
)
# 2. Wide Table → Long Table
sales_long <- sales_wide |>
pivot_longer(
cols = c(Q1, Q2, Q3, Q4),
names_to = "quarter",
values_to = "sales"
)
cat("=== Long Table(First 8 rows)===\n")
print(head(sales_long, 8))
# 3. Calculate the year-over-year growth
sales_growth <- sales_long |>
group_by(region) |>
arrange(quarter) |>
mutate(
prev_sales = lag(sales),
growth = round((sales - prev_sales) / prev_sales * 100, 2)
)
cat("\n=== Year-over-year increase ===\n")
print(sales_growth)
# 4. Long Table → Wide Table(Generate a Comparison Report)
report_wide <- sales_growth |>
select(region, quarter, sales, growth) |>
pivot_wider(
id_cols = region,
names_from = quarter,
values_from = c(sales, growth),
names_glue = "{quarter}_{.value}"
)
cat("\n=== Wide-Format Reports ===\n")
print(report_wide)
# 5. Breakdown by Quarter → Quarter+Numbers
sales_long2 <- sales_long |>
separate(quarter, into = c("q_prefix", "q_num"), sep = 1, remove = FALSE)
cat("\n=== Breakdown by Quarter ===\n")
print(sales_long2)
# 6. Multiple Tables join:Merge Customer Information
customers <- tibble(
region = c("Beijing", "Shanghai", "Guangzhou", "Shenzhen", "Hangzhou"),
manager = c("Alice", "Bob", "Charlie", "Diana", "Eve"),
tier = c("A", "A", "B", "A", "C")
)
# left_join:Retain all sales regions
sales_with_manager <- sales_long |>
left_join(customers, by = "region")
cat("\n=== Sales + Account Manager ===\n")
print(head(sales_with_manager, 8))
# 7. Grouped by Manager
manager_summary <- sales_with_manager |>
group_by(manager, tier) |>
summarise(
Total Sales = sum(sales),
Average Quarter = round(mean(sales), 2),
Highest Quarterly = max(sales)
) |>
arrange(desc(Total Sales))
cat("\n=== Manager Performance ===\n")
print(manager_summary)
# 8. Quarter-by-Quarter Comparison Report
quarter_comparison <- sales_long |>
group_by(quarter) |>
summarise(
Total Sales = sum(sales),
Average = round(mean(sales), 2),
Maximum = max(sales),
Minimum = min(sales)
)
cat("\n=== Quarter-over-Quarter Comparison ===\n")
print(quarter_comparison)
# 9. Complex Wide Table(Multi-indicator)
complex_report <- sales_with_manager |>
group_by(region) |>
summarise(
Total Sales = sum(sales),
Average Quarter = round(mean(sales), 2)
) |>
pivot_wider(
names_from = region,
values_from = c(Total Sales, Average Quarter)
)
cat("\n=== Complex Wide Table ===\n")
print(complex_report)
# 10. Reverse Operation:Convert the width table above back to the length table(For drawing)
viz_data <- complex_report |>
pivot_longer(
cols = everything(),
names_to = c("metric", "region"),
names_sep = "_",
values_to = "value"
)
cat("\n=== Data Visualization ===\n")
print(viz_data)
Resultado esperado (trecho):
=== Manager Performance ===
# A tibble: 4 × 5
manager tier Total Sales Average Quarter Highest Quarterly
<chr> <chr> <int> <dbl> <int>
1 Bob A 7000 1750 2000
2 Alice A 4800 1200 1500
3 Diana A 5500 1375 1600
4 Charlie B 3900 975 1200
=== Quarter-over-Quarter Comparison ===
# A tibble: 4 × 5
quarter Total Sales Average Maximum Minimum
<chr> <int> <dbl> <int> <int>
1 Q1 4500 1125 1500 800
2 Q2 5300 1325 1800 900
3 Q3 5100 1275 1700 1000
4 Q4 6300 1575 2000 1200
❓ Perguntas Frequentes
P: Quando devo usar tabelas largas em vez de tabelas longas? R: Use tabelas largas para armazenamento e exibição (mais fáceis de usar) e tabelas longas para análise e criação de gráficos (exigidas pelo ggplot2). Todas as funções do tidyverse pressupõem tabelas longas.
P: Como se escreve
pivot_longerparacols? R: Existem 4 maneiras:c(Q1, Q2, Q3, Q4)lista explícita /starts_with("Q")correspondência de prefixo /2:5intervalo numérico /matches("^Q\\d+$")expressão regular.
P: Qual é a diferença entre
pivot_longeregather? R:gatheré a função tidyr antiga (obsoleta), enquantopivot_longeré a nova versão. Ela é mais poderosa e consistente; o novo código usapivot_longer.
P: Qual dos quatro tipos de junção devo usar? R: O mais utilizado é o
left_join(mantém todos os dados da tabela primária). Oinner_joinrealiza uma correspondência exata, ofull_joinmantém todos os dados e oright_joindeve ser usado com moderação (basta trocar as tabelas da esquerda e da direita).
P: O que devo fazer se o número de linhas aumentar após uma junção? R: Verifique se a chave de junção é única. Uma junção um-para-muitos é normal, mas uma junção muitos-para-muitos resultará em um produto cartesiano. Use
relationship = "many-to-many"para especificá-la explicitamente.
P: Como faço para dividir os nomes das colunas após converter uma tabela larga em uma tabela longa? R: Use
names_sep = "_"para dividir (delimitador fixo) ounames_pattern = "(\\w+)_(\\d+)"para dividir (expressão regular). Declare as colunas resultantes usandonames_to = c("a", "b").
📖 Resumo
- Tabela ampla: uma entidade por linha, com campos que representam atributos (fácil de usar)
- Tabela longa: Uma observação por linha (fácil de analisar)
pivot_longer()Largura → Comprimento,pivot_wider()Comprimento → Largurapivot_longerParâmetros principais:cols/names_to/values_to/names_sep/names_patternpivot_widerParâmetros principais:id_cols/names_from/values_from/values_fillseparate()Dividir colunas (por delimitador ou posição),unite()Unir colunas- 4 tipos de junções no dplyr:
left_join(a mais comum) /right_join/inner_join/full_join - Fluxo de dados do tidyverse: Leitura de tabela ampla → pivot_longer → análise com dplyr → visualização com ggplot2 → relatório com pivot_wider
📝 Exercícios
-
Problema básico: Construa uma tabela larga (3 linhas × 4 colunas: Região/Q1/Q2/Q3/Q4), converta-a em uma tabela longa usando
pivot_longer, depois converta-a de volta em uma tabela larga usandopivot_widere verifique se os dados permanecem completos e intactos. -
Problema básico: Crie um tibble contendo uma coluna chamada
year_month(“2024-01”); useseparate()para dividi-la em duas colunas,yearemonth; e, em seguida, useunite()para juntá-las novamente. -
Exercício básico: Simule duas tabelas (Tabela de Clientes + Tabela de Pedidos) e combine-as utilizando quatro tipos diferentes de junções. Registre a diferença no número de linhas retornadas para cada tipo de junção.
-
Exercício avançado: Crie uma tabela larga complexa (formato dos nomes das colunas: variável_ano_trimestre), use
pivot_longer + names_seppara convertê-la em uma tabela longa em uma única etapa e, em seguida, usepivot_wider + names_gluepara convertê-la de volta em uma tabela larga com um prefixo adicionado. -
Desafio: Simule um cenário completo: ① Importe uma tabela larga contendo dados de vendas de 4 filiais ao longo de 4 trimestres ② Importe uma tabela contendo informações básicas das filiais (região/gerente/departamento) ③ Faça a junção usando um LEFT JOIN ④ Converta para uma tabela longa usando
pivot_longer⑤ Agrupe e resuma por gerente ⑥ Gere um relatório resumido (tabela larga) ⑦ Use o ggplot2 para traçar um “Gráfico de Tendência de Vendas Trimestrais” (tabela longa). Salve o código de todo o processo e as capturas de tela dos resultados.