Rデータのリモデリング:tidyrの完全ガイド
実際のプロジェクトでは、データの 80% は「ワイドテーブル」(1 行につき 1 つのエンティティ、各フィールドが属性を表す)で構成されていますが、分析には「ロングテーブル」(1 行につき 1 つの観測値)が必要になることがよくあります。このレッスンでは、tidyr による再構成と dplyr による結合を用いて、R データを「変換」し、どのような形式のデータでも分析に必要な形式に変換する方法を学びます。
このレッスンを修了すると、幅広テーブルと縦長テーブルの相互変換、列の分割と結合、ジョインを用いた複数のテーブルの結合、およびあらゆる種類のレポートの作成ができるようになります。
1. 学習内容
- 幅の広いテーブルと長いテーブルの違い
- pivot_longer 幅 → 長さ
- pivot_wider 長さ → 幅
- 分割/結合:列の分割と結合
- dplyr の 4 種類の結合(左結合/右結合/内結合/全結合)
- 実践編:販売データを「長さ」から「幅」に変換する
- レポートの作成
2. データ変換における課題
(1) 課題:幅の広い表は分析が難しい
シャオ・ジャオは四半期ごとの売上報告書(横長の表)を受け取った:
Region Q1 Q2 Q3 Q4
Beijing 1000 1200 1100 1500
Shanghai 1500 1800 1700 2000
Guangzhou 800 900 1000 1200
マネージャーから「四半期ごとの売上推移グラフ」の作成を依頼されました。ggplot2では、長い表が必要です:
Region Quarter Sales
Beijing Q1 1000
Beijing Q2 1200
...
Excelでコピー&ペーストするのに5分かかる場合、Python pd.meltでは1行、R tidyrでは—
(2) 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"
)
たった1行のコードで幅を高さに変換する。
3. 幅の広いテーブルと長いテーブル
(1) 2つの形式の比較
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) どちらをいつ使うべきか?
| シナリオ | 推奨事項 |
|---|---|
| データの保存 | ワイドテーブル(行数を削減) |
| レポートを表示 | ワイドテーブル(Excel対応) |
| ggplot2 によるプロット | 長い表(必須) |
| dplyrによる分析 | 長い表(group_byに適した形式) |
| 機械学習 | 幅の広い表(1行につき1サンプル) |
4. pivot_longer(): ワイド → ロング
(1) 基本的な構文
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) 基本的な単位換算
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つの方法
# 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) 実践編:複雑な列名の分割
# 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) 実践編:正規表現を使ったデータの抽出
# 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(): 長さ → 幅
(1) 基本的な構文
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) 基本的な単位換算
# 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) 実践的な応用:欠損値の取り扱い
# 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(): 列の分割と結合
(1) separate() 分割
# 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() 結合
# 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種類の結合
(1) 結合タイプのクイックリファレンス表
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) SQLの例え
| dplyr | SQL | 意味 |
|---|---|---|
left_join(a, b, by) |
LEFT JOIN |
すべて保持 |
right_join(a, b, by) |
RIGHT JOIN |
すべて保持 |
inner_join(a, b, by) |
INNER JOIN |
a∩bを予約 |
full_join(a, b, by) |
FULL OUTER JOIN |
a∪bを予約 |
(3) 実践編:2つのテーブルの結合
# 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) マルチキー結合
# Sort by multiple columns join
df1 |> left_join(df2, by = c("year", "month", "id"))
8. 実践編:複数のテーブルにわたる販売データの分析
以下は、このレッスンで取り上げたすべてのリファクタリングの概念を結びつけた完全なワークフローの例です。
▶ サンプル:第4四半期の売上データの詳細分析
# ============================================
# 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)
期待される出力(抜粋):
=== 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
❓ よくある質問
cols を pivot_longer と記述するにはどうすればよいですか?c(Q1, Q2, Q3, Q4) 明示的なリスト / starts_with("Q") プレフィックスマッチ / 2:5 数値範囲 / matches("^Q\\d+$") 正規表現。names_sep = "_" を使用して(固定区切り文字による)分割を行うか、names_pattern = "(\\w+)_(\\d+)" を使用して(正規表現による)分割を行います。分割後の列は names_to = c("a", "b") を使用して宣言してください。📖 まとめ
- ワイドテーブル:1行につき1つのエンティティで、各フィールドが属性を表す(ユーザーフレンドリー)
- 長い表:1行につき1つの観測値(分析しやすい形式)
pivot_longer()幅 → 長さ、pivot_wider()長さ → 幅pivot_longer主要パラメータ:cols/names_to/values_to/names_sep/names_patternpivot_wider主要パラメータ:id_cols/names_from/values_from/values_fillseparate()列の分割(区切り文字または位置による)、unite()列の結合- dplyr の 4 種類の結合:
left_join(最も一般的) /right_join/inner_join/full_join - tidyverseのデータフロー:ワイドテーブルの読み込み → pivot_longer → dplyrによる分析 → ggplot2による可視化 → pivot_widerによるレポート作成
📝 練習問題
-
基本問題:幅の広い表(3行×4列:地域/Q1/Q2/Q3/Q4)を作成し、
pivot_longerを使用して縦長の表に変換した後、pivot_widerを使用して再び幅の広い表に戻し、データが完全かつ無傷のままであることを確認する。 -
基本的な問題:
year_month(「2024-01」)という名前の列を含むtibbleを作成し、separate()を使用してそれをyearとmonthの2つの列に分割し、その後unite()を使用してそれらを再び結合する。 -
基本演習:2つのテーブル(顧客テーブルと注文テーブル)をシミュレートし、4種類の異なる結合を使用してそれらを結合してください。各結合タイプごとに返される行数の違いを記録してください。
-
応用演習:複雑なワイドテーブル(列名の形式:variable_year_quarter)を作成し、
pivot_longer + names_sepを使用して一工程でロングテーブルに変換した後、pivot_wider + names_glueを使用して接頭辞を追加した状態でワイドテーブルに戻します。 -
課題:完全なシナリオをシミュレートする:① 4つの支店の4四半期にわたる売上データを含むワイドテーブルをインポートする ② 支店の基本情報(地域/マネージャー/部門)を含むテーブルをインポートする ③ LEFT JOIN を使用して結合する ④ 以下の手順でロングテーブルに変換する
pivot_longer⑤ マネージャーごとにグループ化して集計する ⑥ 集計レポート(ワイドテーブル)を生成する ⑦ ggplot2 を使用して「四半期別売上推移グラフ」(ロングテーブル)を作成する。全プロセスのコードと結果のスクリーンショットを保存してください。



