メカとら / tidyverseで学ぶ人事データ分析 記事一覧 運営者について お問い合わせ
🔄
CHAPTER 04

tidyrによるデータ再構造化

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(.)))) }

💡 実践的アドバイス

📊 データ再構造化のコツ

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

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

📚 学習におすすめの書籍

📊

Rユーザのためのtidymodels[実践]入門

〜モダンな統計・機械学習モデリングの世界

松村 優哉 ほか

🛒 Amazonで見る
📈

改訂2版 RユーザのためのRStudio[実践]入門

〜tidyverseによるモダンな分析フローの世界

松村 優哉 ほか

🛒 Amazonで見る

※ 当サイトはAmazonアソシエイトプログラムに参加しています