8  データハンドリング応用 - dplyr発展

前章でselect(), filter(), arrange()を学びました。この章では、さらに強力なデータ操作の方法を学びます。

実務でのデータ分析では、新しい列を作成したり、データを集計したりすることが頻繁にあります。dplyrmutate(), summarize(), group_by()を使うと、これらの操作を簡単に実行できます。

8.1 学習内容

この章を読み終えると、以下ができるようになります。

  • mutate(): 新しい列を作成する
  • summarize(): データを要約する
  • group_by(): グループごとに集計する
  • count(): データを数える
  • これらを組み合わせた実践的なデータ分析

8.2 データの準備

引き続きirisデータセットを使います。

library(tidyverse)
data(iris)

8.3 mutate(): 新しい列を作成する

mutate()関数は、既存の列を使って新しい列を作成したり、既存の列を変換したりします。

8.3.1 基本的な使い方

mutate()内で新しい列名を指定し、その列に代入する値を計算式で書きます。

# がく片の面積を計算して新しい列として追加
result <- iris |>
  mutate(Sepal.Area = Sepal.Length * Sepal.Width) |>
  select(Species, Sepal.Length, Sepal.Width, Sepal.Area) |>
  head()

head(result)
  Species Sepal.Length Sepal.Width Sepal.Area
1  setosa          5.1         3.5      17.85
2  setosa          4.9         3.0      14.70
3  setosa          4.7         3.2      15.04
4  setosa          4.6         3.1      14.26
5  setosa          5.0         3.6      18.00
6  setosa          5.4         3.9      21.06

8.3.2 複数の列を一度に作成

# 複数の計算を一度に実行
result <- iris |>
  mutate(
    Sepal.Area = Sepal.Length * Sepal.Width,
    Petal.Area = Petal.Length * Petal.Width,
    Total.Area = Sepal.Area + Petal.Area
  ) |>
  select(Species, Sepal.Area, Petal.Area, Total.Area)

head(result)
  Species Sepal.Area Petal.Area Total.Area
1  setosa      17.85       0.28      18.13
2  setosa      14.70       0.28      14.98
3  setosa      15.04       0.26      15.30
4  setosa      14.26       0.30      14.56
5  setosa      18.00       0.28      18.28
6  setosa      21.06       0.68      21.74

8.3.3 条件による値の変更

if_else()case_when()を使うと、条件に応じて値を変更できます。

if_else()は、第1引数に条件、第2引数に条件がTRUEの場合の値、第3引数に条件がFALSEの場合の値を指定します。

# Sepal.Lengthが5以上なら"大きい"、そうでなければ"小さい"
result <- iris |>
  mutate(
    Size = if_else(Sepal.Length >= 5, "大きい", "小さい")
  ) |>
  select(Species, Sepal.Length, Size)

head(result)
  Species Sepal.Length   Size
1  setosa          5.1 大きい
2  setosa          4.9 小さい
3  setosa          4.7 小さい
4  setosa          4.6 小さい
5  setosa          5.0 大きい
6  setosa          5.4 大きい

複数の条件がある場合はcase_when()が便利です。TRUEは、上の条件に当てはまらない場合の値を指定するために使います。

# Sepal.Lengthを3段階に分類
iris |>
  mutate(
    Size_Category = case_when(
      Sepal.Length < 5 ~ "小",
      Sepal.Length < 6 ~ "中",
      TRUE ~ "大"  # それ以外(上の条件に当てはまらない場合)
    )
  ) |>
  select(Species, Sepal.Length, Size_Category) |>
  head(10)
   Species Sepal.Length Size_Category
1   setosa          5.1            中
2   setosa          4.9            小
3   setosa          4.7            小
4   setosa          4.6            小
5   setosa          5.0            中
6   setosa          5.4            中
7   setosa          4.6            小
8   setosa          5.0            中
9   setosa          4.4            小
10  setosa          4.9            小

8.4 summarize(): データを要約する

summarize()(またはsummarise())関数は、データを集約して要約統計量を計算します。

mutate()の場合と同様に、summarize()内で新しい列名を指定し、その列に代入する値を計算式で書きます。

8.4.1 基本的な統計量

# がく片の長さの平均値を計算
iris |>
  summarize(
    mean = mean(Sepal.Length)
  )
      mean
1 5.843333

8.4.2 複数の統計量を一度に計算

# 複数の統計量を計算
iris |>
  summarize(
    mean = mean(Sepal.Length),
    median = median(Sepal.Length),
    sd = sd(Sepal.Length),
    minimum = min(Sepal.Length),
    maximum = max(Sepal.Length),
    n = n()
  )
      mean median        sd minimum maximum   n
1 5.843333    5.8 0.8280661     4.3     7.9 150
ノートよく使う集計関数
  • mean(): 平均値
  • median(): 中央値
  • sd(): 標準偏差
  • var(): 分散
  • min(): 最小値
  • max(): 最大値
  • sum(): 合計
  • n(): 行数を数える
  • n_distinct(): ユニークな値の数

8.5 group_by(): グループごとに集計する

group_by()summarize()を組み合わせると、グループごとの集計ができます。これが非常に強力です。

8.5.1 基本的な使い方

# 品種ごとにがく片の長さの平均を計算
iris |>
  group_by(Species) |>
  summarize(
    mean = mean(Sepal.Length)
  )
# A tibble: 3 × 2
  Species     mean
  <fct>      <dbl>
1 setosa      5.01
2 versicolor  5.94
3 virginica   6.59

8.5.2 複数の統計量をグループごとに計算

# 品種ごとに複数の統計量を計算
iris |>
  group_by(Species) |>
  summarize(
    n = n(),
    mean = mean(Sepal.Length),
    sd = sd(Sepal.Length),
    minimum = min(Sepal.Length),
    maximum = max(Sepal.Length)
  )
# A tibble: 3 × 6
  Species        n  mean    sd minimum maximum
  <fct>      <int> <dbl> <dbl>   <dbl>   <dbl>
1 setosa        50  5.01 0.352     4.3     5.8
2 versicolor    50  5.94 0.516     4.9     7  
3 virginica     50  6.59 0.636     4.9     7.9

8.5.3 グループ化したまま新しい列を作成

mutate()と組み合わせると、グループごとの計算結果を元のデータに追加できます。

# 品種ごとの平均値を各行に追加
result <- iris |>
  group_by(Species) |>
  mutate(
    Species_Mean = mean(Sepal.Length),
    Diff_from_Mean = Sepal.Length - Species_Mean
  ) |>
  select(Species, Sepal.Length, Species_Mean, Diff_from_Mean)

head(result)
# A tibble: 6 × 4
# Groups:   Species [1]
  Species Sepal.Length Species_Mean Diff_from_Mean
  <fct>          <dbl>        <dbl>          <dbl>
1 setosa           5.1         5.01        0.0940 
2 setosa           4.9         5.01       -0.106  
3 setosa           4.7         5.01       -0.306  
4 setosa           4.6         5.01       -0.406  
5 setosa           5           5.01       -0.00600
6 setosa           5.4         5.01        0.394  

ページの都合上Speciesがsetosaしか表示されていませんが、virginicaとversicolorも同様に平均値と平均との差分が計算されます。ポイントは、mutate()はグループ化した状態で計算するため、同じ品種の行はそれぞれ同じ平均値が入ることです。

注意ungroup()を忘れずに

group_by()を使った後、グループ化を解除したい場合はungroup()を使います。

iris |>
  group_by(Species) |>
  summarize(平均 = mean(Sepal.Length)) |>
  ungroup()  # グループ化を解除

グループ化したまま次の操作を行うと、予期しない結果になることがあるため注意が必要です。グループ化して計算したい範囲が終わったら、ungroup()でグループ化を解除する習慣をつけましょう。

8.5.4 .by引数でグループ化と集計を同時に行う

group_by()summarize()を組み合わせる代わりに、summarize().by引数を使うと、グループ化と集計を同時に行うことができます。

関数内でのみグループ化が行われるため、先述したungroup()を使う必要もなく、コードがシンプルになります。

# .by引数を使ってグループ化と集計を同時に
iris |>
  summarize(
    n = n(),
    mean = mean(Sepal.Length),
    sd = sd(Sepal.Length),
    minimum = min(Sepal.Length),
    maximum = max(Sepal.Length),
    .by = Species
  )
     Species  n  mean        sd minimum maximum
1     setosa 50 5.006 0.3524897     4.3     5.8
2 versicolor 50 5.936 0.5161711     4.9     7.0
3  virginica 50 6.588 0.6358796     4.9     7.9

ちなみに.by引数は、summarize()以外にもmutate()filter()などでも使えます。グループ化と集計を同時に行いたい場合は、.by引数を活用してコードをシンプルにすることも検討してみてください。

8.6 count(): データを数える

count()関数は、値の出現回数を簡単に数えられます。

8.6.1 基本的な使い方

以下の例では、Speciesごとにデータの数を数えています。

# 品種ごとのデータ数を数える
iris |>
  count(Species)
     Species  n
1     setosa 50
2 versicolor 50
3  virginica 50

これは以下と同じ意味です。

iris |>
  group_by(Species) |>
  summarize(n = n())
# A tibble: 3 × 2
  Species        n
  <fct>      <int>
1 setosa        50
2 versicolor    50
3 virginica     50

8.7 実践例:売上データの分析

実務でよくある売上データの分析を例に、これまでの知識を総合的に使ってみましょう。

8.7.1 サンプルデータの作成

# 売上データの作成
sales <- data.frame(
  日付 = rep(c("2025-01-01", "2025-01-02", "2025-01-03"), each = 4),
  店舗 = rep(c("東京店", "大阪店", "東京店", "大阪店"), 3),
  商品 = rep(c("商品A", "商品A", "商品B", "商品B"), 3),
  売上数量 = c(10, 15, 8, 12, 12, 18, 10, 14, 15, 20, 12, 16),
  単価 = c(1000, 1000, 1500, 1500, 1000, 1000, 1500, 1500, 1000, 1000, 1500, 1500)
)

print(sales)
         日付   店舗  商品 売上数量 単価
1  2025-01-01 東京店 商品A       10 1000
2  2025-01-01 大阪店 商品A       15 1000
3  2025-01-01 東京店 商品B        8 1500
4  2025-01-01 大阪店 商品B       12 1500
5  2025-01-02 東京店 商品A       12 1000
6  2025-01-02 大阪店 商品A       18 1000
7  2025-01-02 東京店 商品B       10 1500
8  2025-01-02 大阪店 商品B       14 1500
9  2025-01-03 東京店 商品A       15 1000
10 2025-01-03 大阪店 商品A       20 1000
11 2025-01-03 東京店 商品B       12 1500
12 2025-01-03 大阪店 商品B       16 1500

8.7.2 分析1: 売上金額の計算

# 売上金額を計算
sales_with_amount <- sales |>
  mutate(
    売上金額 = 売上数量 * 単価
  )

print(sales_with_amount)
         日付   店舗  商品 売上数量 単価 売上金額
1  2025-01-01 東京店 商品A       10 1000    10000
2  2025-01-01 大阪店 商品A       15 1000    15000
3  2025-01-01 東京店 商品B        8 1500    12000
4  2025-01-01 大阪店 商品B       12 1500    18000
5  2025-01-02 東京店 商品A       12 1000    12000
6  2025-01-02 大阪店 商品A       18 1000    18000
7  2025-01-02 東京店 商品B       10 1500    15000
8  2025-01-02 大阪店 商品B       14 1500    21000
9  2025-01-03 東京店 商品A       15 1000    15000
10 2025-01-03 大阪店 商品A       20 1000    20000
11 2025-01-03 東京店 商品B       12 1500    18000
12 2025-01-03 大阪店 商品B       16 1500    24000

8.7.3 分析2: 店舗ごとの売上集計

# 店舗ごとの売上を集計
store_summary <- sales_with_amount |>
  group_by(店舗) |>
  summarize(
    総売上数量 = sum(売上数量),
    総売上金額 = sum(売上金額),
    平均売上数量 = mean(売上数量)
    # group_by()を使わない場合
    # .by = 店舗
  )

print(store_summary)
# A tibble: 2 × 4
  店舗   総売上数量 総売上金額 平均売上数量
  <chr>       <dbl>      <dbl>        <dbl>
1 大阪店         95     116000         15.8
2 東京店         67      82000         11.2

8.7.4 分析3: 商品・店舗ごとの売上集計

# 商品と店舗の組み合わせで集計
product_store_summary <- sales_with_amount |>
  group_by(商品, 店舗) |>
  summarize(
    総売上金額 = sum(売上金額),
    .groups = "drop"  # グループ化を自動で解除
  ) |>
  arrange(desc(総売上金額))

print(product_store_summary)
# A tibble: 4 × 3
  商品  店舗   総売上金額
  <chr> <chr>       <dbl>
1 商品B 大阪店      63000
2 商品A 大阪店      53000
3 商品B 東京店      45000
4 商品A 東京店      37000

8.7.5 分析4: 日付ごとの売上推移

# 日付ごとの売上推移
daily_sales <- sales_with_amount |>
  group_by(日付) |>
  summarize(
    日次売上金額 = sum(売上金額),
    日次販売数量 = sum(売上数量)
  )

print(daily_sales)
# A tibble: 3 × 3
  日付       日次売上金額 日次販売数量
  <chr>             <dbl>        <dbl>
1 2025-01-01        55000           45
2 2025-01-02        66000           54
3 2025-01-03        77000           63

8.8 応用テクニック

8.8.1 across()を使った複数列への同じ処理

複数の列に同じ処理を適用したい場合はacross()が便利です。

少々使い方は難しいですが、例えば数値列(numeric型)すべての平均を品種ごとに計算する場合は以下のように書けます。

# すべての数値列の平均を品種ごとに計算
iris |>
  group_by(Species) |>
  summarize(
    across(where(is.numeric), mean)
  )
# A tibble: 3 × 5
  Species    Sepal.Length Sepal.Width Petal.Length Petal.Width
  <fct>             <dbl>       <dbl>        <dbl>       <dbl>
1 setosa             5.01        3.43         1.46       0.246
2 versicolor         5.94        2.77         4.26       1.33 
3 virginica          6.59        2.97         5.55       2.03 

where()は条件に合う列を選択するための関数で、is.numericは数値列を選択する条件です。across()は、選択した列すべてに同じ関数(この場合はmean)を適用します。

たくさんの列に同じ処理を適用したい場合は、across()を使うとコードがシンプルになります。

8.8.2 slice()で行を選択

グループごとに上位N件を取得する場合はslice()が使えます。

# 品種ごとにSepal.Lengthが最も大きい3件を取得
iris |>
  group_by(Species) |>
  arrange(desc(Sepal.Length)) |>
  slice(1:3)
# A tibble: 9 × 5
# Groups:   Species [3]
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species   
         <dbl>       <dbl>        <dbl>       <dbl> <fct>     
1          5.8         4            1.2         0.2 setosa    
2          5.7         4.4          1.5         0.4 setosa    
3          5.7         3.8          1.7         0.3 setosa    
4          7           3.2          4.7         1.4 versicolor
5          6.9         3.1          4.9         1.5 versicolor
6          6.8         2.8          4.8         1.4 versicolor
7          7.9         3.8          6.4         2   virginica 
8          7.7         3.8          6.7         2.2 virginica 
9          7.7         2.6          6.9         2.3 virginica 

SpeciesごとにSepal.Lengthが大きい順に並び替えた後、上位3件を取得しています。slice()は行番号で指定するため、1:3は上位3件を意味します。

ちなみにungroup()をしていないので、slice()はグループごとに行を選択します。グループ化していない場合は、全体の上位3件を取得することになります。

# 全体の上位3件を取得(グループ化していない場合)
iris |>
  group_by(Species) |>
  arrange(desc(Sepal.Length)) |>
  ungroup() |>
  slice(1:3)
# A tibble: 3 × 5
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species  
         <dbl>       <dbl>        <dbl>       <dbl> <fct>    
1          7.9         3.8          6.4         2   virginica
2          7.7         3.8          6.7         2.2 virginica
3          7.7         2.6          6.9         2.3 virginica

8.9 練習問題

irisデータで、がく片とFlowerの長さの合計をTotal.Lengthという列名で追加してください。

解答例

iris |>
  mutate(Total.Length = Sepal.Length + Petal.Length) |>
  select(Species, Sepal.Length, Petal.Length, Total.Length)

irisデータで、品種ごとにPetal.Widthの平均、最小値、最大値を計算してください。

解答例

iris |>
  group_by(Species) |>
  summarize(
    平均 = mean(Petal.Width),
    最小値 = min(Petal.Width),
    最大値 = max(Petal.Width)
  )

以下の従業員データで、部署ごとの平均年齢と売上合計を計算し、売上合計の降順に並び替えてください。

employees <- data.frame(
  名前 = c("田中", "佐藤", "鈴木", "高橋", "伊藤", "渡辺"),
  部署 = c("営業", "開発", "営業", "開発", "総務", "営業"),
  年齢 = c(25, 30, 35, 28, 42, 31),
  売上 = c(1200, 1500, 980, 1350, 1100, 1450)
)

解答例

employees |>
  group_by(部署) |>
  summarize(
    平均年齢 = mean(年齢),
    売上合計 = sum(売上)
  ) |>
  arrange(desc(売上合計))

8.10 まとめ

この章では、dplyrの発展的な機能を学びました。

  • mutate(): 新しい列の作成・既存列の変換
  • summarize(): データの要約統計
  • group_by(): グループごとの集計
  • count(): データのカウント
  • ✅ これらを組み合わせた実践的なデータ分析

これで、データの読み込みから加工、集計まで一連の流れができるようになりました。次の章では、分析結果を視覚的に表現する方法を学びます。

ノート次のステップ

次の章「データ可視化入門 - ggplot2基礎」では、ggplot2パッケージを使ったグラフ作成を学びます。データの特徴を視覚的に理解し、他者に伝える力を身につけましょう。