R: R 数据重塑:tidyr 完整教程

最后更新:2026-08-26

真实项目里 80% 的数据是"宽表"(每行一个实体,字段是属性),但分析时常需要"长表"(每行一个观测值)。这一课我们学 R 数据的"变形术"——tidyr 重塑 + dplyr 连接,把数据从任何形态转换为分析需要的形态。

读完这一课你就能:宽表 ↔ 长表转换、拆分合并列、多表 join 合并、做任意报表。

1. 你将学到



2. 一个数据变形的痛点

(1) 痛点:宽表难分析

小赵拿到一份季度销售数据(宽表):

TEXT 📖 仅展示
地区    Q1    Q2    Q3    Q4
北京  1000  1200  1100  1500
上海  1500  1800  1700  2000
广州   800   900  1000  1200

manager要求画"季度销售趋势图"——ggplot2 需要长表

TEXT 📖 仅展示
地区   季度  销售额
北京   Q1    1000
北京   Q2    1200
...

如果用 Excel 复制粘贴要 5 分钟;Python pd.melt 一行;R tidyr——

(2) R 的解法

R
library(tidyr)

# 宽表 → 长表(一行)
sales_long <- sales_wide |>
  pivot_longer(
    cols = c(Q1, Q2, Q3, Q4),
    names_to = "季度",
    values_to = "销售额"
  )

1 行代码搞定宽长转换



3. 宽表 vs 长表

(1) 两种形态对比

100%
graph LR
    A[宽表 Wide] -->|pivot_longer| B[长表 Long]
    B -->|pivot_wider| A
    
    subgraph 宽表示例
        C[地区 Q1 Q2 Q3 Q4]
    end
    subgraph 长表示例
        D[地区 季度 销售额]
    end

(2) 何时用哪种?

场景 推荐
存储数据 宽表(节省行数)
人看报表 宽表(Excel 友好)
ggplot2 画图 长表(必需)
dplyr 分析 长表(group_by 友好)
机器学习 宽表(每行 1 个样本)


4. pivot_longer():宽 → 长

(1) 基本语法

R
pivot_longer(
  data,
  cols,                 # 要转换的列
  names_to = "name",    # 新列名(原列名)
  values_to = "value",  # 新列名(原值)
  names_prefix = "",    # 列名公共前缀
  names_sep = NULL,     # 分隔符(拆分列名)
  names_pattern = NULL, # 正则(提取列名)
  values_drop_na = FALSE
)

(2) 基本转换

R
library(tidyr)

# 宽表
sales_wide <- tibble(
  region = c("北京", "上海", "广州"),
  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 北京    1000  1200  1100  1500
# 2 上海    1500  1800  1700  2000
# 3 广州     800   900  1000  1200

# 转长表
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 北京   Q1       1000
#  2 北京   Q2       1200
#  3 北京   Q3       1100
#  4 北京   Q4       1500
#  5 上海   Q1       1500
#  6 上海   Q2       1800
#  7 上海   Q3       1700
#  8 上海   Q4       2000
# ...

(3) 4 种 cols 选择方式

R
# 1. 直接列名向量
cols = c(Q1, Q2, Q3, Q4)

# 2. starts_with()(最常用)
cols = starts_with("Q")

# 3. 数字范围
cols = 2:5  # 第 2-5 列

# 4. 列名 pattern(正则)
cols = matches("^Q\\d+$")

# 5. 排除某列
cols = -region

(4) 实战:复杂列名拆分

R
# 列名含前缀
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)
)

# 拆分 sales_2022 → 类型=销售, 年份=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) 实战:正则提取

R
# 列名格式:变量_年份_季度
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)
)

# 用正则提取
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():长 → 宽

(1) 基本语法

R
pivot_wider(
  data,
  id_cols = NULL,        # 标识列(保持为行)
  names_from = name,     # 新列名来源
  values_from = value,   # 新列值来源
  values_fill = NULL,    # 缺失值填充
  names_prefix = ""      # 新列名前缀
)

(2) 基本转换

R
# 长表转宽表
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 北京    1000  1200  1100  1500
# ...

(3) 实战:缺失值处理

R
# 数据有缺失(不是所有 region 都有所有 quarter)
df <- tibble(
  region = c("A", "A", "B", "C", "C"),
  quarter = c("Q1", "Q2", "Q1", "Q2", "Q3"),
  sales = c(100, 200, 150, 250, 300)
)

# 转宽表(缺失位置填 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():拆分合并列

(1) separate() 拆分

R
# 拆分日期列
df <- tibble(date = c("2024-01-15", "2024-02-20"))

# 按分隔符拆分
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

# 按位置拆分
df |> separate(date, into = c("year", "rest"), sep = 4)
# year  rest
# "2024" "-01-15"

# 用正则拆分
df |> separate(date, into = c("year", "month", "day"),
               sep = "(-)")

(2) unite() 合并

R
# 合并年/月/日
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 种 join

(1) join 类型速查表

100%
graph TB
    A[dplyr 4 种 join] --> B[left_join<br/>保左表全部]
    A --> C[right_join<br/>保右表全部]
    A --> D[inner_join<br/>保交集]
    A --> E[full_join<br/>保并集]
    
    style A fill:#fff3cd
    style B fill:#cce5ff
    style C fill:#d4edda
    style D fill:#f8d7da
    style E fill:#e1d4ff

(2) SQL 类比

dplyr SQL 含义
left_join(a, b, by) LEFT JOIN 保留 a 全部
right_join(a, b, by) RIGHT JOIN 保留 b 全部
inner_join(a, b, by) INNER JOIN 保留 a∩b
full_join(a, b, by) FULL OUTER JOIN 保留 a∪b

(3) 实战:2 表 join

R
# 客户表
customers <- tibble(
  id = 1:5,
  name = c("Alice", "Bob", "Charlie", "Diana", "Eve")
)

# 订单表
orders <- tibble(
  customer_id = c(1, 1, 2, 4, 4, 6),
  amount = c(100, 200, 150, 300, 250, 500)
)

# 1. left_join:保客户全部(无订单的为 NA)
customers |> left_join(orders, by = c("id" = "customer_id"))
# # A tibble: 7 × 3  ← 5 客户 + 6 订单 = 7 行(Charlie/Eve 无订单)
#    id name    amount
# 1  1 Alice     100
# 2  1 Alice     200
# 3  2 Bob       150
# 4  3 Charlie    NA  ← 无订单
# 5  4 Diana     300
# 6  4 Diana     250
# 7  5 Eve        NA  ← 无订单

# 2. inner_join:保交集
customers |> inner_join(orders, by = c("id" = "customer_id"))
# # A tibble: 6 × 3  ← 4 个有订单的客户

# 3. full_join:保并集
customers |> full_join(orders, by = c("id" = "customer_id"))
# # A tibble: 7 × 3  ← 包含 id=6 的"无客户"订单

# 4. right_join:保订单全部
customers |> right_join(orders, by = c("id" = "customer_id"))
# # A tibble: 6 × 3  ← 6 个订单

(4) 多键 join

R
# 按多个列 join
df1 |> left_join(df2, by = c("year", "month", "id"))


8. 实战:销售数据多表分析

下面是一个完整工作流示例,把本课所有重塑知识串起来。

▶ 示例:4 季度销售数据深度分析

R 📖 仅展示
# ============================================
# 4 季度销售数据深度分析
# 功能:宽↔长转换 + 多表 join + 报表
# ============================================

library(tidyr)
library(dplyr)

# 1. 准备原始数据(宽表)
sales_wide <- tibble(
  region = c("北京", "上海", "广州", "深圳"),
  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. 宽表 → 长表
sales_long <- sales_wide |>
  pivot_longer(
    cols = c(Q1, Q2, Q3, Q4),
    names_to = "quarter",
    values_to = "sales"
  )

cat("=== 长表(前 8 行)===\n")
print(head(sales_long, 8))

# 3. 计算同比增长
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=== 同比增长 ===\n")
print(sales_growth)

# 4. 长表 → 宽表(生成对比报表)
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=== 宽表报表 ===\n")
print(report_wide)

# 5. 拆分季度 → 季度+数字
sales_long2 <- sales_long |>
  separate(quarter, into = c("q_prefix", "q_num"), sep = 1, remove = FALSE)

cat("\n=== 拆分季度 ===\n")
print(sales_long2)

# 6. 多表 join:合并客户信息
customers <- tibble(
  region = c("北京", "上海", "广州", "深圳", "杭州"),
  manager = c("Alice", "Bob", "Charlie", "赵六", "钱七"),
  tier = c("A", "A", "B", "A", "C")
)

# left_join:保留所有销售地区
sales_with_manager <- sales_long |>
  left_join(customers, by = "region")

cat("\n=== 销售 + 客户经理 ===\n")
print(head(sales_with_manager, 8))

# 7. 按经理汇总
manager_summary <- sales_with_manager |>
  group_by(manager, tier) |>
  summarise(
    总销售 = sum(sales),
    平均季度 = round(mean(sales), 2),
    最大季度 = max(sales)
  ) |>
  arrange(desc(总销售))

cat("\n=== 经理业绩 ===\n")
print(manager_summary)

# 8. 季度对比报表
quarter_comparison <- sales_long |>
  group_by(quarter) |>
  summarise(
    总销售 = sum(sales),
    平均 = round(mean(sales), 2),
    最大 = max(sales),
    最小 = min(sales)
  )

cat("\n=== 季度对比 ===\n")
print(quarter_comparison)

# 9. 复杂宽表(多指标)
complex_report <- sales_with_manager |>
  group_by(region) |>
  summarise(
    总销售 = sum(sales),
    平均季度 = round(mean(sales), 2)
  ) |>
  pivot_wider(
    names_from = region,
    values_from = c(总销售, 平均季度)
  )

cat("\n=== 复杂宽表 ===\n")
print(complex_report)

# 10. 反向操作:把上面宽表转回长表(用于画图)
viz_data <- complex_report |>
  pivot_longer(
    cols = everything(),
    names_to = c("metric", "region"),
    names_sep = "_",
    values_to = "value"
  )

cat("\n=== 可视化数据 ===\n")
print(viz_data)
逻辑代码 90 行(超过 40 行限制,仅展示)

预期输出(节选):

TEXT 📖 仅展示
=== 经理业绩 ===
# A tibble: 4 × 5
  manager tier  总销售 平均季度 最大季度
  <chr>   <chr>  <int>    <dbl>    <int>
1 Bob    A       7000    1750       2000
2 Alice    A       4800    1200       1500
3 赵六    A       5500    1375       1600
4 Charlie    B       3900     975       1200

=== 季度对比 ===
# A tibble: 4 × 5
  quarter 总销售   平均  最大  最小
  <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

❓ 常见问题

Q pivot_longercols 怎么写?
A 4 种方式:c(Q1, Q2, Q3, Q4) 显式列名 / starts_with("Q") 前缀匹配 / 2:5 数字范围 / matches("^Q\\d+$") 正则。
Q 宽表转长表后列名怎么拆?
Anames_sep = "_" 拆分(固定分隔符)或 names_pattern = "(\\w+)_(\\d+)" 拆分(正则)。拆出的多列在 names_to = c("a", "b") 声明。

📖 小节


📝 作业

  1. 基础题:构造 1 个宽表(3 行 × 4 列:地区/Q1/Q2/Q3/Q4),用 pivot_longer 转长表,再用 pivot_wider 转回宽表,验证数据完整无损。

  2. 基础题:构造 1 个 tibble 含 year_month 列("2024-01"),用 separate() 拆成 yearmonth 两列,再用 unite() 合并回去。

  3. 基础题:模拟 2 个表(客户表 + 订单表),分别用 4 种 join 合并,记录每种 join 的结果行数差异。

  4. 进阶题:构造 1 个复杂宽表(列名格式:变量_年份_季度),用 pivot_longer + names_sep 一步转长表,再用 pivot_wider + names_glue 转回宽表加前缀。

  5. 挑战题:模拟 1 个完整场景:① 读入 4 个分公司 × 4 季度的销售宽表 ② 读入分公司基本信息表(地区/经理/部门)③ left_join 合并 ④ 用 pivot_longer 转长 ⑤ 按经理分组汇总 ⑥ 写出汇总报表(宽表)⑦ 用 ggplot2 画"季度销售趋势图"(长表)。把整个流程的代码和结果截图保存。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏