0. 導入(イントロ)

  • 本講の目的:

データフレームを“直感的に整形する方法”(dplyr)を学ぶ。

  • tibbleとdplyrについて:

本講では、まず前回学んだデータフレームの拡張形式であるtibbleについて学びます(ただし、見た目はほとんど変わりません)。

tibbleは、Rの従来のdata.frameを改良したデータ構造で、列の型が明示され、自動的な型変換も行われないため、安全で読みやすいデータ操作が可能になる。一方、dplyrは、このtibbleを標準的な入出力形式として設計されたデータ整形パッケージであり、行の抽出・列の選択・並べ替え・要約などを直感的な文法で記述できる。dplyrの関数は、data.frameを入力しても結果をtibbleとして返すため、両者を組み合わせることで、データの可読性と操作性が飛躍的に高まり、<-演算子を用いた処理の流れも明快になる。結果として、分析プロセスの再現性と保守性が大きく向上する。


1. tibbleとは何か

  • tibbleは、data.frameの改良版
  • Rの基本的な拡張パッケージであるtidyverseの一部として提供されており、使用前に library(tibble) または library(tidyverse) を読み込む必要がある
  • 表示が整っており、列の型もわかりやすく表示される
  • 文字列を自動でfactorに変換しない(factorについては後に学びます)
# tibbleを使うための準備
library(tibble)

データフレームとtibbleで同じデータを入れます。

# data.frameとの比較(knit時:dfはbase出力/tibbleはtibble出力)
df <- data.frame(x = 1:3, y = c("A", "B", "C"))
tb <- tibble(x = 1:3, y = c("A", "B", "C"))

こちらがデータフレームの出力

print(df)
##   x y
## 1 1 A
## 2 2 B
## 3 3 C

他方、tibbleには型の情報が標準で出力されます。

print(tb)
## # A tibble: 3 × 2
##       x y    
##   <int> <chr>
## 1     1 A    
## 2     2 B    
## 3     3 C

classを確認すると、 tibbleがデータフレーム形式を保持しつつ、 それを拡張したものであることがわかります。

class(df); class(tb)  # 型の違いを確認
## [1] "data.frame"
## [1] "tbl_df"     "tbl"        "data.frame"

実際、tibbleでも、data.frameを引数とする関数がそのまま使えます。

# 行数、列数、# 次元
nrow(tb);ncol(tb); dim(tb)
## [1] 3
## [1] 2
## [1] 3 2
# 要約統計
summary(tb)    
##        x            y            
##  Min.   :1.0   Length:3          
##  1st Qu.:1.5   Class :character  
##  Median :2.0   Mode  :character  
##  Mean   :2.0                     
##  3rd Qu.:2.5                     
##  Max.   :3.0
# データフレームとして認識される
is.data.frame(tb)  
## [1] TRUE

2. tibbleの扱い方

  • 列の参照:$[[ ]]pull()select()
  • 行の参照:slice()
# tibbleを用意
people <- tibble(
  name   = c("A", "B", "C"),
  height = c(165, 172, 180),
  weight = c(55, 65, 80),
  gender = c("F","M","M")
)
print(people)
## # A tibble: 3 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 A        165     55 F     
## 2 B        172     65 M     
## 3 C        180     80 M

◯ 列の参照

# 各列を取り出す
people$name           # $を使う(一般的)
## [1] "A" "B" "C"
people[["height"]]    # [[ ]]で取り出す
## [1] 165 172 180

pull() は、tibbleやdata.frameから「特定の1列だけをベクトルとして取り出す」関数です。列が一つと決まっているので、出力は決まってアトミックベクトルとなります。

# peopleからweight属性を取り出す。(people$weightと同じ)
pull(people, weight)  # 出力はアトミックベクトル
## [1] 55 65 80
# 列番号で指定(2列目 = height)
pull(people, 2)
## [1] 165 172 180
# 複数の列番号を指定するとエラーとなる
pull(people,3:4)
## Error in `pull()`:
## ! `!!enquo(var)` must select exactly one column.

他方で、select(df,n) は、データフレームの特定の列を参照しますが、複数の列も参照することができます。重要な点として、列の数が一つでも複数でも、出力形式はtibbleとなります。

# height列とweight列を抽出
select(people, height, weight)
## # A tibble: 3 × 2
##   height weight
##    <dbl>  <dbl>
## 1    165     55
## 2    172     65
## 3    180     80

以下は、意味としてはpeople$heightと同じですが、 出力がtibble形式となることに注意してください。これは、のちの

select(people,height)
## # A tibble: 3 × 1
##   height
##    <dbl>
## 1    165
## 2    172
## 3    180

◯ 行・列の操作

slice(df, n)はデータフレームからn行目のみを切り出します。出力は、再びデータフレームとなります。スライスは横方向に切り取る感じですね。

# 2行目だけを抽出
slice(people, 2)
## # A tibble: 1 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 B        172     65 M
# 2〜3行目を抽出
slice(people,2:3)
## # A tibble: 2 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 B        172     65 M     
## 2 C        180     80 M

relocate関数は、列の順番を入れ替えることができます。`

# height列を先頭に移動
print(people); relocate(people, height)
## # A tibble: 3 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 A        165     55 F     
## 2 B        172     65 M     
## 3 C        180     80 M
## # A tibble: 3 × 4
##   height name  weight gender
##    <dbl> <chr>  <dbl> <chr> 
## 1    165 A         55 F     
## 2    172 B         65 M     
## 3    180 C         80 M
# name列をgender列の前に移動
relocate(people,name,.before = gender )
## # A tibble: 3 × 4
##   height weight name  gender
##    <dbl>  <dbl> <chr> <chr> 
## 1    165     55 A     F     
## 2    172     65 B     M     
## 3    180     80 C     M
# name列をgender列の後に移動
relocate(people,name,.after = gender )
## # A tibble: 3 × 4
##   height weight gender name 
##    <dbl>  <dbl> <chr>  <chr>
## 1    165     55 F      A    
## 2    172     65 M      B    
## 3    180     80 M      C

3. dplyrの基本構文

  • データ整形の基本5関数:
    • filter():行を絞る(条件で抽出)
    • select():列を選ぶ(必要な列だけ残す)
    • mutate():列を作る・書き換える(新しい情報を加える)
    • summarise():要約する(平均や合計などを1行にまとめる)
    • arrange():並べ替える(昇順・降順)
  • パイプ |> を使うと、処理の流れを上から下へ読めるようになります。

◯ 例題データ

library(dplyr)

people = tibble(
  name   = c("A", "B", "C", "D", "E"),
  height = c(165, 172, 180, 175, 160),
  weight = c(55, 65, 80, 70, 50),
  gender = c("F","M","M","F","F")
)
people
## # A tibble: 5 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 A        165     55 F     
## 2 B        172     65 M     
## 3 C        180     80 M     
## 4 D        175     70 F     
## 5 E        160     50 F

filter():行を絞る(条件で抽出)

filter(df,条件)は、dfを第2引数の条件に従って、特定の行だけをsliceする関数です。

# 以下は同じ結果となります。
filter(people, row_number() == 2); slice(people,2)
## # A tibble: 1 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 B        172     65 M
## # A tibble: 1 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 B        172     65 M
# 身長が170cm以上の人だけ抽出
filter(people, height >= 170)
## # A tibble: 3 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 B        172     65 M     
## 2 C        180     80 M     
## 3 D        175     70 F
# 条件を複数指定することも可能(AND条件)
filter(people, height >= 170, gender == "M")
## # A tibble: 2 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 B        172     65 M     
## 2 C        180     80 M
# OR条件の場合は | を使う
filter(people, height >= 180 | weight >= 70)
## # A tibble: 2 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 C        180     80 M     
## 2 D        175     70 F

select():列を選ぶ(必要な列だけ)

既に説明していますが復習も兼ねて。
ここでも重要なのは、仮に出力の列数が一つでも、
常にtibble形式で扱われる点にあります。

# 特定の列を残す
select(people, name, height)
## # A tibble: 5 × 2
##   name  height
##   <chr>  <dbl>
## 1 A        165
## 2 B        172
## 3 C        180
## 4 D        175
## 5 E        160
# 列を除外する場合はマイナス指定
select(people, -gender)
## # A tibble: 5 × 3
##   name  height weight
##   <chr>  <dbl>  <dbl>
## 1 A        165     55
## 2 B        172     65
## 3 C        180     80
## 4 D        175     70
## 5 E        160     50
# ヘルパー関数も使える
select(people, starts_with("h"))  # "height"列を抽出
## # A tibble: 5 × 1
##   height
##    <dbl>
## 1    165
## 2    172
## 3    180
## 4    175
## 5    160

mutate():列を新しく作る・書き換える

mutate() は、既存のデータに新しい列を追加したり、既存の列を計算で書き換えたりする関数です。 Excelの新しい列に「計算式を入力して値を作る」ようなイメージです。

  • 数式を使って新しい列を作成できる
  • 既存の列を上書きして変換することも可能
  • 結果は tibble として返され、元データを自動的には書き換えません(代入が必要)

# BMI列を新しく作成
people2 = mutate(people,
  BMI = round(weight / (height/100)^2, 1)
)
people2
## # A tibble: 5 × 5
##   name  height weight gender   BMI
##   <chr>  <dbl>  <dbl> <chr>  <dbl>
## 1 A        165     55 F       20.2
## 2 B        172     65 M       22  
## 3 C        180     80 M       24.7
## 4 D        175     70 F       22.9
## 5 E        160     50 F       19.5
# 既存列の変換も可能(heightをm単位に)
mutate(people2, height = height / 100)
## # A tibble: 5 × 5
##   name  height weight gender   BMI
##   <chr>  <dbl>  <dbl> <chr>  <dbl>
## 1 A       1.65     55 F       20.2
## 2 B       1.72     65 M       22  
## 3 C       1.8      80 M       24.7
## 4 D       1.75     70 F       22.9
## 5 E       1.6      50 F       19.5

summarise():要約(グループごとに集計)

summarise() は、データ全体やグループごとに、平均・合計・最大値などの要約統計量を計算する関数です。

  • mean(), sum(), max(), min(), sd() などの関数と組み合わせて使う
  • データ全体をまとめて、1行に要約することも、
    group_by() と組み合わせて グループごとに1行ずつ要約することもできる
  • つまり、「グループの数 = 出力される行数」 になる
  • group_by() と組み合わせることで、カテゴリごとの平均・件数・最大値などの集計が可能になる

summarise() は「データを要約する」関数、
group_by() は「グループ分けする」関数です。
この2つを組み合わせることで、「性別ごとの平均」や「条件ごとの件数」などを簡単に計算でき、
グループが多いほど出力行も増える
という仕組みになっています。


# 全体の統計量をまとめる
summarise(people2,
  mean_height = mean(height),    # 平均
  mean_weight = mean(weight),
  max_height  = max(height),     # 最大値
  sd_height   = sd(height),      # 標準偏差
  total_weight = sum(weight),    # 合計
  n = n()                        # 件数
)
## # A tibble: 1 × 6
##   mean_height mean_weight max_height sd_height total_weight     n
##         <dbl>       <dbl>      <dbl>     <dbl>        <dbl> <int>
## 1        170.          64        180      7.96          320     5
# group_by() で性別ごとにグループ化してから要約
people_grouped = group_by(people2, gender)
people_grouped #内部的にグループ化されていますが(2行目)、見た目はほぼ変わらない。
## # A tibble: 5 × 5
## # Groups:   gender [2]
##   name  height weight gender   BMI
##   <chr>  <dbl>  <dbl> <chr>  <dbl>
## 1 A        165     55 F       20.2
## 2 B        172     65 M       22  
## 3 C        180     80 M       24.7
## 4 D        175     70 F       22.9
## 5 E        160     50 F       19.5
#男女別に各統計量を出力(2行となる!)
summarise(people_grouped,
  mean_height = mean(height),
  max_weight  = max(weight),
  mean_BMI    = mean(BMI),
  sd_BMI      = sd(BMI),
  n = n()  # 各グループの件数
)
## # A tibble: 2 × 6
##   gender mean_height max_weight mean_BMI sd_BMI     n
##   <chr>        <dbl>      <dbl>    <dbl>  <dbl> <int>
## 1 F             167.         70     20.9   1.80     3
## 2 M             176          80     23.4   1.91     2

arrange():並べ替え(行の順序を変える)

arrange() は、指定した列の値をもとに行の並び順を変える関数です。
Excelで「並べ替え」ボタンを押すのと同じイメージで、
昇順(小さい順)・降順(大きい順)・複数条件でのソートが可能です。

  • デフォルトでは昇順(小さい順)で並べ替える
  • 降順にしたい場合は desc() を使う
  • 複数列を指定すれば、第1→第2→第3条件の優先順で並べ替えが行われる
  • 文字列の場合はアルファベット順(A→Z)
  • 並べ替え後もtibble形式のまま返される

# 身長の昇順(デフォルト)
arrange(people2, height)
## # A tibble: 5 × 5
##   name  height weight gender   BMI
##   <chr>  <dbl>  <dbl> <chr>  <dbl>
## 1 E        160     50 F       19.5
## 2 A        165     55 F       20.2
## 3 B        172     65 M       22  
## 4 D        175     70 F       22.9
## 5 C        180     80 M       24.7
# BMIの降順(大きい順)
arrange(people2, desc(BMI))
## # A tibble: 5 × 5
##   name  height weight gender   BMI
##   <chr>  <dbl>  <dbl> <chr>  <dbl>
## 1 C        180     80 M       24.7
## 2 D        175     70 F       22.9
## 3 B        172     65 M       22  
## 4 A        165     55 F       20.2
## 5 E        160     50 F       19.5
# 複数の条件で並べ替え(性別ごとに、BMIの降順)
arrange(people2, gender, desc(BMI))
## # A tibble: 5 × 5
##   name  height weight gender   BMI
##   <chr>  <dbl>  <dbl> <chr>  <dbl>
## 1 D        175     70 F       22.9
## 2 A        165     55 F       20.2
## 3 E        160     50 F       19.5
## 4 C        180     80 M       24.7
## 5 B        172     65 M       22
# 名前順(アルファベット順)に並べ替え
arrange(people2, name)
## # A tibble: 5 × 5
##   name  height weight gender   BMI
##   <chr>  <dbl>  <dbl> <chr>  <dbl>
## 1 A        165     55 F       20.2
## 2 B        172     65 M       22  
## 3 C        180     80 M       24.7
## 4 D        175     70 F       22.9
## 5 E        160     50 F       19.5
# 並べ替え結果を保存したい場合
people_sorted = arrange(people2, desc(height))
people_sorted
## # A tibble: 5 × 5
##   name  height weight gender   BMI
##   <chr>  <dbl>  <dbl> <chr>  <dbl>
## 1 C        180     80 M       24.7
## 2 D        175     70 F       22.9
## 3 B        172     65 M       22  
## 4 A        165     55 F       20.2
## 5 E        160     50 F       19.5

arrange() は、データを「並べ替えて見やすくする」関数です。 filter()mutate() と組み合わせて、条件抽出 → 計算 → 並べ替え のように使うことで、整った順序の結果を得ることができます。

4. パイプ構文(%>%)を使う

  • dplyrの真価は、複数の処理をパイプでつなげて書ける点にあります。
  • パイプ %>% は「そして次に」を意味します。
  • つまり、x %>% f() は「xf()に渡す」という構文です。

◯ 例題データ

library(dplyr)

people = tibble(
  name   = c("A", "B", "C", "D", "E"),
  height = c(165, 172, 180, 175, 160),
  weight = c(55, 65, 80, 70, 50),
  gender = c("F","M","M","F","F")
)
people
## # A tibble: 5 × 4
##   name  height weight gender
##   <chr>  <dbl>  <dbl> <chr> 
## 1 A        165     55 F     
## 2 B        172     65 M     
## 3 C        180     80 M     
## 4 D        175     70 F     
## 5 E        160     50 F

◯ パイプなしの書き方(従来のR)

people2 = mutate(people,
  BMI = round(weight / (height/100)^2, 1)
)
people3 = filter(people2, BMI >= 22)
people4 = arrange(people3, desc(BMI))
people5 = select(people4, name, gender, BMI)
people5
## # A tibble: 3 × 3
##   name  gender   BMI
##   <chr> <chr>  <dbl>
## 1 C     M       24.7
## 2 D     F       22.9
## 3 B     M       22

手順は正しいけれど、途中でデータ名が増えてしまい読みにくい…。
ちなみに、これを一つの式で書くとさらに大変なことになります。

select(arrange(filter(mutate(people,
  BMI = round(weight / (height/100)^2, 1)),
  BMI >= 22),
  desc(BMI)),
  name, gender, BMI)

◯ パイプでつなげると(読みやすくなる)

people %>% 
  mutate(BMI = round(weight / (height/100)^2, 1)) %>% 
  filter(BMI >= 22) %>% 
  arrange(desc(BMI)) %>% 
  select(name, gender, BMI)
## # A tibble: 3 × 3
##   name  gender   BMI
##   <chr> <chr>  <dbl>
## 1 C     M       24.7
## 2 D     F       22.9
## 3 B     M       22

データの流れが「上から下へ」読めるようになり、 一連の処理が自然に理解できます。

💡 RStudio のショートカット

OS 操作キー 出力
macOS ⌘ + Shift + M %>%
Windows / Linux Ctrl + Shift + M %>%

このショートカットは、magrittrdplyr で使われる
tidyverse パイプ演算子 %>% を自動で挿入します。

◯ 処理の途中を確認したい場合

people %>% 
  mutate(BMI = round(weight / (height/100)^2, 1)) %>% 
  filter(BMI >= 2) %>% 
  head()  # 途中で確認
## # A tibble: 5 × 5
##   name  height weight gender   BMI
##   <chr>  <dbl>  <dbl> <chr>  <dbl>
## 1 A        165     55 F       20.2
## 2 B        172     65 M       22  
## 3 C        180     80 M       24.7
## 4 D        175     70 F       22.9
## 5 E        160     50 F       19.5

group_by() + summarise() もスッキリ書ける

# パイプを使わないとネスト(入れ子)が深くなる
people %>% 
  mutate(BMI = round(weight / (height/100)^2, 1)) %>% 
  group_by(gender) %>% 
  summarise(
    mean_height = mean(height),
    mean_BMI = mean(BMI),
    n = n()
  )
## # A tibble: 2 × 4
##   gender mean_height mean_BMI     n
##   <chr>        <dbl>    <dbl> <int>
## 1 F             167.     20.9     3
## 2 M             176      23.4     2

以下はgroup_by()mutate()を組み合わせた例です(グループ毎の平均値と最大スコア)。

新たな変数を追加するときにも、グループ毎の集計結果を返すことは可能です。summarize()と異なり、グループ毎に、統計値が重複することに注意してください。

data <- tibble(
  group = c("A", "A", "A", "B", "B", "B"),
  score = c(80, 90, 85, 70, 75, 65)
)

data %>% 
  group_by(group) %>% 
  mutate(mean_score = mean(score), max_score = max(score))
## # A tibble: 6 × 4
## # Groups:   group [2]
##   group score mean_score max_score
##   <chr> <dbl>      <dbl>     <dbl>
## 1 A        80         85        90
## 2 A        90         85        90
## 3 A        85         85        90
## 4 B        70         70        75
## 5 B        75         70        75
## 6 B        65         70        75

◯ パイプの概念まとめ

構文 意味 読み方
x %>% f() f(x) 「xをfに渡す」
x %>% f(y) f(x, y) 「xをfに渡してyを使う」
x %>% f() %>% g() g(f(x)) 「xをfに通してgに渡す」
x %>% f() %>% g() %>% h() h(g(f(x))) 省略

パイプは“関数をつなぐ線路”。
これまで学んだ関数を「流れ」として表現できるようになります。


5. 発展(他の主なdplyrの使い方)

ここまでで、filter()select()mutate()summarise()arrange()
パイプ %>% の使い方を学びました。
ここでは、実務や研究でよく使う追加関数をまとめて紹介します。


① データの結合(join系)

複数のデータフレームを共通の列(キー)で結合します。
すべての関数で基本構文は同じで、 *_join(x, y, by = "共通列名") の形で使います。

関数名 内容
left_join() 左側の行をすべて保持(対応しない右側はNA)
inner_join() 両方に共通する行のみ残す
full_join() すべての行を結合(存在しない要素はNA補完)
semi_join() 左側に「対応のある行」だけ残す(右側列は持たない)
anti_join() 左側に「対応のない行」だけ残す

💡 代表的な例

# 顧客データと注文データを作成
customers = tibble(
  id = 1:3,
  name = c("A", "B", "C")
)

orders = tibble(
  id = c(1, 1, 3, 4),
  item = c("Book", "Pen", "Bag", "Lamp")
)

customers
## # A tibble: 3 × 2
##      id name 
##   <int> <chr>
## 1     1 A    
## 2     2 B    
## 3     3 C
orders
## # A tibble: 4 × 2
##      id item 
##   <dbl> <chr>
## 1     1 Book 
## 2     1 Pen  
## 3     3 Bag  
## 4     4 Lamp
# left_join:左側(customers)の行をすべて保持
left_join(customers, orders, by = "id")
## # A tibble: 4 × 3
##      id name  item 
##   <dbl> <chr> <chr>
## 1     1 A     Book 
## 2     1 A     Pen  
## 3     2 B     <NA> 
## 4     3 C     Bag
# inner_join:両方に共通するidのみ残す
inner_join(customers, orders, by = "id")
## # A tibble: 3 × 3
##      id name  item 
##   <dbl> <chr> <chr>
## 1     1 A     Book 
## 2     1 A     Pen  
## 3     3 C     Bag

💬 補足
- left_join() は最もよく使われる結合。欠損があっても左側の情報を保持できる。
- inner_join() は両方に存在するデータのみ抽出。
- いずれも出力は tibble形式
- semi_join() / anti_join() は「対応の有無でフィルタしたいとき」に使う。
- 結合前に distinct() でキー重複を確認しておくと安全。


② 条件分岐で列を作る

mutate() と組み合わせて、値に応じてカテゴリを付けることができます。
case_when() は複数条件に対応し、if_else() はシンプルな二分岐に向いています。


💡 case_when():複数条件に基づいてカテゴリを作成

# テスト得点データ
scores = tibble(
  name = c("A", "B", "C", "D", "E"),
  score = c(92, 75, 63, 88, 55)
)
scores
## # A tibble: 5 × 2
##   name  score
##   <chr> <dbl>
## 1 A        92
## 2 B        75
## 3 C        63
## 4 D        88
## 5 E        55
# 成績を条件で分類
mutate(scores,
  grade = case_when(
    score >= 80 ~ "A",
    score >= 65 ~ "B",
    TRUE ~ "C"
  )
)
## # A tibble: 5 × 3
##   name  score grade
##   <chr> <dbl> <chr>
## 1 A        92 A    
## 2 B        75 B    
## 3 C        63 C    
## 4 D        88 A    
## 5 E        55 C

💬 解説
- case_when() は「if / else if / else」を一括で書ける構文。
- 条件は上から順に評価され、最初にTRUEになった式が採用されます。
- TRUE ~ "C" は「どれにも当てはまらない場合」のデフォルト処理です。


💡 if_else():単純な二分岐(TRUE/FALSEで分ける)

mutate(scores,
  pass = if_else(score >= 70, "合格", "不合格")
)
## # A tibble: 5 × 3
##   name  score pass  
##   <chr> <dbl> <chr> 
## 1 A        92 合格  
## 2 B        75 合格  
## 3 C        63 不合格
## 4 D        88 合格  
## 5 E        55 不合格

💬 解説
- if_else(条件, TRUEの値, FALSEの値) の形で使います。
- ベクトル全体に適用され、結果はtibbleの新しい列として追加されます。
- if_else()ifelse() よりも型が厳密で、安全に使える点が特徴です。


💡 応用例:条件を組み合わせる

mutate(scores,
  grade = case_when(
    score >= 90 ~ "S",
    score >= 80 ~ "A",
    score >= 65 ~ "B",
    TRUE ~ "C"
  ),
  result = if_else(score >= 70, "Pass", "Fail")
)
## # A tibble: 5 × 4
##   name  score grade result
##   <chr> <dbl> <chr> <chr> 
## 1 A        92 S     Pass  
## 2 B        75 B     Pass  
## 3 C        63 C     Fail  
## 4 D        88 A     Pass  
## 5 E        55 C     Fail

💬 まとめ
- 単純な条件 → if_else()
- 複数条件 → case_when()
- いずれも mutate() 内で使うと列を自動的に追加できる。
- 条件は論理値(TRUE/FALSE)で評価され、結果はベクトルとして返る。


③ 複数列をまとめて処理:across()

複数の列に同じ処理を一括で適用できる関数です。
mutate()summarise() の中で使われ、
「同じ演算を何列にも繰り返したい」場合に便利です。


💡 例1:数値列の平均をまとめて計算

# サンプルデータ
data = tibble(
  name = c("A", "B", "C"),
  math = c(80, 90, 70),
  english = c(85, 88, 78),
  science = c(82, 95, 75)
)
data
## # A tibble: 3 × 4
##   name   math english science
##   <chr> <dbl>   <dbl>   <dbl>
## 1 A        80      85      82
## 2 B        90      88      95
## 3 C        70      78      75
# 数値列だけを抽出して平均を計算
summarise(data,
  across(where(is.numeric), mean)
)
## # A tibble: 1 × 3
##    math english science
##   <dbl>   <dbl>   <dbl>
## 1    80    83.7      84

💬 where(is.numeric) は「数値列をすべて選ぶ」という条件指定。
文字列列(name)は自動的に除外されます。


💡 例2:複数列を一括で変換(単位変換やスケーリング)

# すべての数値列を100で割る
mutate(data,
  across(where(is.numeric), ~ .x / 100)
)
## # A tibble: 3 × 4
##   name   math english science
##   <chr> <dbl>   <dbl>   <dbl>
## 1 A       0.8    0.85    0.82
## 2 B       0.9    0.88    0.95
## 3 C       0.7    0.78    0.75

💬 ~ .x / 100 は「各列の値(.x)を100で割る」という意味。
across() は「繰り返し書かずにすむ」ための仕組みです。


💡 例3:列名条件で選択

# "m"で始まる列だけ2倍に
mutate(data,
  across(starts_with("m"), ~ .x * 2)
)
## # A tibble: 3 × 4
##   name   math english science
##   <chr> <dbl>   <dbl>   <dbl>
## 1 A       160      85      82
## 2 B       180      88      95
## 3 C       140      78      75

💬 starts_with("m") のほかに、ends_with()contains() なども使えます。
列名パターンに基づく一括処理が可能です。


💬 まとめ

  • 同じ処理を複数列に適用したいときに使う(forループ不要)
  • mutate() → 値を変換・作成
  • summarise() → 要約統計
  • よく使う組み合わせ:where(is.numeric)starts_with()contains()

④ 新しいグループ指定 .by

group_by() を使わず、簡潔にグループ集計できます(dplyr 1.1以降)。今のところ、本授業ではgroup_by()の使用していくつもりです。

summarise(df, mean_height = mean(height), .by = gender)
mutate(df, z = scale(weight), .by = gender)

⑤ 行を抽出する関数

関数 内容 使用例
slice_min() 指定列の最小値行を取得 slice_min(df, order_by = BMI, n = 3)
slice_max() 指定列の最大値行を取得 slice_max(df, order_by = BMI, n = 3)
slice_sample() ランダムに行を抽出 slice_sample(df, n = 5)

⑥ 列や値を一括操作する関数

機能 関数 使用例
列名を一括変更 rename_with() rename_with(df, toupper, starts_with("col"))
グループ内の件数やユニーク数 n() / n_distinct() summarise(df, n = n(), uniq = n_distinct(name), .by = gender)
条件付きフィルタ if_any() / if_all() filter(df, if_any(ends_with("_flag"), ~ .x == TRUE))
欠損値補完 coalesce() mutate(df, x = coalesce(x, 0))

⑦ グループ処理の解除と安全設計

  • グループ化されたデータを元に戻すには ungroup()
  • 結合の前に anti_join() でキーの不一致を確認
  • 一意な行を抽出するには distinct(df, id, .keep_all = TRUE)
people_grouped %>%  ungroup() %>%  summarise(mean_BMI = mean(BMI))

6. 確認課題

[準備] データフレームをインポート

以下から、奇数偶数の好み実験結果の架空(N=2865)のデータをインポートしてください。

url = "https://lab.kenrikodaka.com/_download/csv/oddeven_2865.csv"
data.df = read.csv(url)
head(data.df) #最初の6行をちょっと出し(NAは未定)
##   month day preference gender age domhand
## 1    10  26        ODD   MALE  19   RIGHT
## 2     4   6        ODD   MALE  NA    <NA>
## 3     9  14       EVEN FEMALE  20   RIGHT
## 4     1  21        ODD FEMALE  17   RIGHT
## 5     4   7       EVEN FEMALE  20   RIGHT
## 6     7  27       EVEN FEMALE  18   RIGHT
library(tidyverse) # csvをtibble形式で読み込めるようになります。
data.tb = read_csv(url) #読み込み関数が微妙に違うので注意
head(data.tb)
## # A tibble: 6 × 6
##   month   day preference gender   age domhand
##   <dbl> <dbl> <chr>      <chr>  <dbl> <chr>  
## 1    10    26 ODD        MALE      19 RIGHT  
## 2     4     6 ODD        MALE      NA <NA>   
## 3     9    14 EVEN       FEMALE    20 RIGHT  
## 4     1    21 ODD        FEMALE    17 RIGHT  
## 5     4     7 EVEN       FEMALE    20 RIGHT  
## 6     7    27 EVEN       FEMALE    18 RIGHT
# 以下では、単にdataとします。
data = data.tb
head(data, 10)
## # A tibble: 10 × 6
##    month   day preference gender   age domhand
##    <dbl> <dbl> <chr>      <chr>  <dbl> <chr>  
##  1    10    26 ODD        MALE      19 RIGHT  
##  2     4     6 ODD        MALE      NA <NA>   
##  3     9    14 EVEN       FEMALE    20 RIGHT  
##  4     1    21 ODD        FEMALE    17 RIGHT  
##  5     4     7 EVEN       FEMALE    20 RIGHT  
##  6     7    27 EVEN       FEMALE    18 RIGHT  
##  7     6    15 ODD        MALE      19 RIGHT  
##  8     5    26 EVEN       FEMALE    19 RIGHT  
##  9     2     5 EVEN       MALE      18 RIGHT  
## 10     7    23 EVEN       MALE      18 RIGHT

翻訳すると以下の様になります。

  • 10月26日、奇数好き、男性、19歳、右利き
  • 04月06日、奇数好き、男性、不明、不明
  • 09月14日、偶数好き、女性、18歳、右利き
  • 01月21日、奇数好き、女性、18歳、右利き
  • 04月07日、偶数好き、女性、18歳、右利き
  • 07月27日、偶数好き、女性、19歳、右利き

誕生日と奇数偶数の好みに関するアンケートの架空のデータ(2865人分)です。 month(誕生月)・day(誕生日)・preference(奇数が好き:ODD, 偶数が好き:EVEN) gender(男性:MALE, 女性:FEMALE)・$domhand(利き手が左:LEFT, 利き手が右:RIGHT) age(年齢)


[確認演習1] 偶数好きの割合を男女別に示してください。

data2 = data %>% 
  group_by(gender) %>% 
  summarise(
    n_even = sum(preference == "EVEN"),
    n_odd = sum(preference == "ODD"),
    prop_even = n_even / (n_even + n_odd)
  ) 

print(data2)
## # A tibble: 2 × 4
##   gender n_even n_odd prop_even
##   <chr>   <int> <int>     <dbl>
## 1 FEMALE   1044   559     0.651
## 2 MALE      650   612     0.515

[確認演習2] 女性の偶数好きの多い月ごと、その割合がわかるように並べてください。

data3 = data %>% 
  filter(gender == "FEMALE") %>% 
  group_by(month) %>% 
  summarise(
    n_even = sum(preference == "EVEN"),
    n_odd = sum(preference == "ODD"),
    prop_even = n_even / (n_even + n_odd)
  )   %>% 
  arrange(desc(prop_even))

print(data3)
## # A tibble: 12 × 4
##    month n_even n_odd prop_even
##    <dbl>  <int> <int>     <dbl>
##  1     4     84    29     0.743
##  2    12     92    32     0.742
##  3     2     74    29     0.718
##  4     6    110    45     0.710
##  5     8    109    49     0.690
##  6    11     84    48     0.636
##  7     1     93    57     0.62 
##  8     9     83    52     0.615
##  9    10     78    49     0.614
## 10     3     87    56     0.608
## 11     7     84    59     0.587
## 12     5     66    54     0.55

[確認演習3] 誕生日の月を奇数と偶数で分けたときに、偶数好きの割合を男女別に示してください。

data4 = data %>% 
  mutate(month2 = if_else(month%%2==0, "EVEN", "ODD")) %>% 
  group_by(month2, gender) %>% 
  summarise(
    n_even = sum(preference == "EVEN"),
    n_odd = sum(preference == "ODD"),
    prop_even = n_even / (n_even + n_odd)
  ) %>% 
  relocate(gender) %>% 
  arrange(gender)

print(data4)
## # A tibble: 4 × 5
## # Groups:   month2 [2]
##   gender month2 n_even n_odd prop_even
##   <chr>  <chr>   <int> <int>     <dbl>
## 1 FEMALE EVEN      547   233     0.701
## 2 FEMALE ODD       497   326     0.604
## 3 MALE   EVEN      334   273     0.550
## 4 MALE   ODD       316   339     0.482

[確認演習1] 女性の偶数好きの多い誕生日の日にちを、その割合がわかるように降順に並べてください(tibble形式で出力すること)。


[確認演習2] 誕生日の月と日にちを奇数と偶数で分けたときに、その組み合わせごと(奇数・奇数、奇数・偶数、偶数・奇数、偶数・偶数の4種類)に偶数好きの割合を男女別に示してください(tibble形式で出力すること)。


[確認演習3] 誕生日の月または日にちに(一つでも)「2」が含まれているグループと、そうでないグループに分けたとき、それぞれの偶数好きの割合を男女別に示してください(tibble形式で出力すること)。


[確認演習4] 誕生日の日にちを「素数(“PRIME”)」・「素数でない奇数(“ODD”)」・「偶数(“EVEN”)」の3カテゴリーに分けて、それぞれの偶数好きの割合を男女別に示してください。ただし、「2」は素数ではなく、偶数に含まれるものとして計算してください(tibble形式で出力すること)。