🔄
CHAPTER 04

データ再構造化の魔術

tidyrでデータを自在に操る。Wide/Long形式の変換、欠損値の優雅な処理、複雑なデータ構造の整理術をマスターし、データサイエンスの真の力を解き放とう。

tidyrとは?

tidyrは「tidy data」の原則に基づいて設計された、データ再構造化の専門パッケージです。混沌としたデータを美しく整理し、分析しやすい形に変換する強力なツールを提供します。

現実のデータは常に理想的な形ではありません。Excel形式の幅の広いテーブル、不規則な欠損値、ネストされた複雑な構造...。tidyrは、これらすべてを「tidy」な形に変換する魔法を提供します。

tidyr_setup.R
# tidyrの読み込み(tidyverseに含まれる) library(tidyr) library(dplyr) # Tidy Dataの3つの原則 # 1. 各変数は1つの列 # 2. 各観測値は1つの行 # 3. 各値は1つのセル

データ形状の変換

データの形状変換は、tidyrの最も重要な機能です。Wide形式(横に広い)とLong形式(縦に長い)を自在に変換し、分析の目的に最適な形にデータを整形できます。

pivot_longer()

Wide形式からLong形式への変換。複数の列を1つの列にまとめ、データを「長く」します。

pivot_longer(cols, names_to, values_to)

pivot_wider()

Long形式からWide形式への変換。1つの列の値を複数の列に展開し、データを「広く」します。

pivot_wider(names_from, values_from)

📊 pivot_longer(): Wide → Long変換

売上データをExcel形式(Wide)から分析用形式(Long)に変換する実例を見てみましょう。

pivot_longer_example.R
# Wide形式の売上データ(Excel形式) sales_wide <- data.frame( 店舗 = c("東京店", "大阪店", "名古屋店"), Q1 = c(1200, 980, 750), Q2 = c(1350, 1100, 820), Q3 = c(1180, 1250, 890), Q4 = c(1420, 1380, 950) ) # Long形式に変換(分析しやすい形に) sales_long <- sales_wide %>% pivot_longer( cols = starts_with("Q"), # Q1,Q2,Q3,Q4の列を対象 names_to = "四半期", # 列名が入る新しい列 values_to = "売上" # 値が入る新しい列 ) print(sales_long)

🔄 データ変換結果

Wide形式(変換前)
店舗 Q1 Q2 Q3 Q4
東京店 1200 1350 1180 1420
大阪店 980 1100 1250 1380
名古屋店 750 820 890 950
Long形式(変換後)
店舗 四半期 売上
東京店 Q1 1200
東京店 Q2 1350
東京店 Q3 1180
東京店 Q4 1420
大阪店 Q1 980
... ... ...

📈 pivot_wider(): Long → Wide変換

アンケートデータをクロス集計表形式に変換する例を見てみましょう。

pivot_wider_example.R
# Long形式のアンケートデータ survey_long <- data.frame( 回答者ID = rep(1:4, each = 3), 質問項目 = rep(c("満足度", "利便性", "価格"), 4), 評価 = c(5, 4, 3, 4, 5, 4, 3, 3, 5, 5, 4, 4) ) # Wide形式に変換(クロス集計表形式) survey_wide <- survey_long %>% pivot_wider( names_from = 質問項目, # この列の値が新しい列名になる values_from = 評価 # この列の値が各セルに入る ) print(survey_wide)

🔄 データ変換結果

Long形式(変換前)
回答者ID 質問項目 評価
1 満足度 5
1 利便性 4
1 価格 3
2 満足度 4
... ... ...
Wide形式(変換後)
回答者ID 満足度 利便性 価格
1 5 4 3
2 4 5 4
3 3 3 5
4 5 4 4

欠損値の優雅な処理

現実のデータには欠損値が付き物です。tidyrは、欠損値を適切に処理し、データの完全性を保ちながら分析を進めるための強力なツールを提供します。

drop_na()

欠損値を含む行を削除します。シンプルで直接的なアプローチです。

drop_na(columns)

fill()

前後の値で欠損値を埋めます。時系列データに特に有効です。

fill(columns, .direction)

replace_na()

欠損値を指定した値で置換します。カスタマイズされた補完が可能です。

replace_na(list(col = value))

complete()

データの組み合わせを完全にし、暗黙的な欠損値を明示的にします。

complete(columns, fill)

🔍 欠損値処理の実践

センサーデータの欠損値を様々な方法で処理する例を見てみましょう。

missing_data_handling.R
# 欠損値を含むセンサーデータ sensor_data <- data.frame( timestamp = seq(as.POSIXct("2023-01-01 00:00:00"), by = "hour", length.out = 10), temperature = c(22.5, NA, 23.1, 22.8, NA, 24.2, 24.5, NA, 23.9, 23.7), humidity = c(65, 68, NA, 70, 72, NA, 69, 67, 65, NA) ) # 方法1: 欠損値のある行を削除 clean_data <- sensor_data %>% drop_na() # 方法2: 前の値で埋める(温度センサー) filled_data <- sensor_data %>% fill(temperature, .direction = "down") %>% fill(humidity, .direction = "up") # 方法3: 平均値で置換 imputed_data <- sensor_data %>% replace_na(list( temperature = mean(sensor_data$temperature, na.rm = TRUE), humidity = mean(sensor_data$humidity, na.rm = TRUE) )) print("補完後のデータ:") print(imputed_data)

🔄 欠損値処理結果

元データ(欠損値あり)
時刻 温度 湿度
00:00 22.5 65
01:00 NA 68
02:00 23.1 NA
03:00 22.8 70
04:00 NA 72
補完後データ
時刻 温度 湿度
00:00 22.5 65
01:00 23.3 68
02:00 23.1 68.0
03:00 22.8 70
04:00 23.3 72

🎯 高度なデータ整理術

separate()とunite()を使って、複雑な文字列データを分割・結合する方法を学びましょう。

advanced_tidyr.R
# 複雑な文字列データ user_data <- data.frame( user_info = c("田中太郎_25_東京", "佐藤花子_30_大阪", "山田次郎_28_名古屋"), score = c(85, 92, 78) ) # 1つの列を複数の列に分割 separated_data <- user_data %>% separate( col = user_info, into = c("名前", "年齢", "都市"), sep = "_" ) %>% mutate(年齢 = as.numeric(年齢)) # 複数の列を1つに結合 united_data <- separated_data %>% unite( col = "ID", 名前, 都市, sep = "@", remove = FALSE # 元の列を残す ) print(united_data)

🔬 分析のための高度なデータ再構造化

基本的なtidyr操作を習得したら、複雑な実世界データの再構造化に挑戦しましょう。 統計分析や機械学習に適したデータ形式への変換は、分析の成功を左右する重要なスキルです。

📊 時系列データの高度な再構造化

time_series_reshaping.R
# 時系列データの高度な再構造化 library(tidyverse) library(lubridate) # 複雑な時系列データの例 raw_sales <- tibble( date = rep(seq(ymd("2023-01-01"), ymd("2023-12-31"), by = "month"), 3), region = rep(c("North", "South", "East"), each = 12), product_A = runif(36, 1000, 5000), product_B = runif(36, 800, 4000), product_C = runif(36, 1200, 6000) ) # 分析用のロング形式に変換 long_sales <- raw_sales %>% pivot_longer( cols = starts_with("product_"), names_to = "product", values_to = "sales", names_prefix = "product_" ) %>% mutate( year = year(date), month = month(date, label = TRUE), quarter = quarter(date, with_year = TRUE) ) # 比較分析用のワイド形式に変換 comparative_analysis <- long_sales %>% pivot_wider( names_from = region, values_from = sales, names_prefix = "region_" ) %>% mutate( # 地域間格差の計算 north_south_ratio = region_North / region_South, total_sales = region_North + region_South + region_East, max_region = pmax(region_North, region_South, region_East) )

🧬 ネストされたデータの高度な操作

nested_data_advanced.R
# ネストされたデータの高度な操作 library(tidyverse) library(broom) # 複雑な階層データの処理 customer_data <- tibble( customer_id = rep(1:100, each = 12), month = rep(1:12, 100), purchases = rpois(1200, 3), amount = purchases * runif(1200, 50, 200), segment = sample(c("Premium", "Standard", "Basic"), 1200, replace = TRUE) ) # 顧客セグメント別の分析 nested_analysis <- customer_data %>% group_by(segment, customer_id) %>% summarise( total_amount = sum(amount), avg_monthly = mean(amount), .groups = "drop_last" ) %>% nest(customer_data = -segment) %>% mutate( # 各セグメントの統計サマリー summary_stats = map(customer_data, ~ { tibble( n_customers = nrow(.x), mean_ltv = mean(.x$total_amount), median_ltv = median(.x$total_amount), sd_ltv = sd(.x$total_amount) ) }), # 回帰分析(月次傾向) trend_model = map(customer_data, ~ lm(total_amount ~ avg_monthly, data = .x)), model_summary = map(trend_model, broom::glance) )

🔍 欠損値の高度な処理戦略

missing_data_strategies.R
# 欠損値の高度な処理戦略 library(tidyverse) library(VIM) # 欠損値パターンの分析 analyze_missing_patterns <- function(data) { data %>% summarise_all(~ sum(is.na(.))) %>% pivot_longer(everything(), names_to = "variable", values_to = "missing_count") %>% mutate(missing_pct = missing_count / nrow(data) * 100) %>% arrange(desc(missing_pct)) } # 条件付き欠損値補完 smart_imputation <- function(data, group_vars, target_var) { data %>% group_by(across(all_of(group_vars))) %>% mutate( # グループ内平均で補完 {{target_var}} := coalesce({{target_var}}, mean({{target_var}}, na.rm = TRUE)) ) %>% ungroup() %>% mutate( # 全体平均で補完(グループ内で補完できない場合) {{target_var}} := coalesce({{target_var}}, mean({{target_var}}, na.rm = TRUE)) ) } # 欠損値フラグの作成 create_missing_flags <- function(data) { data %>% mutate_all(list(missing = ~ as.numeric(is.na(.)))) }

💡 実践的アドバイス

📊 データ再構造化のコツ

  • 目的を明確にする:どの分析手法に使うかで最適な形式が決まる
  • メモリ効率を考慮:大きなデータセットでは処理順序が重要
  • 検証を怠らない:変換前後でデータの整合性を確認
  • 再現可能性を確保:変換ステップを関数化して文書化
  • エラーハンドリング:想定外のデータ構造に対応する

これらの高度なデータ再構造化技法により、複雑な実世界のデータを分析に適した形式に効率的に変換できるようになります。 次の章では、テキストデータの処理技法を学びましょう。