• ここで使う Rパッケージ {tidyverse} をロードする
library(haven)
library(readxl)
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

ここで学ぶこと

・変数を手作りする方法を解説します
・変数をマージしてデータフレームの作り方を説明します
・複数のデータフレームをマージする方法を説明します
・様々な拡張子のデータを RStudio に取り込む方法を紹介します
・データ分析を行う前段階として、基礎的な専門用語の解説をします

ここで解説する専門用語

テキストデータ、バイナリデータ、拡張子、パス、ファイル、フォルダ、Rプロジェクト、Rプロジェクトフォルダ、作業ディレクトリ、欠損値、変数の型、R付属データ、パイプ演算子、変数の型 (class)、AND演算子、OR演算子、%in%演算子、欠損値

分析で使うデータの読み込み
  • 衆議院議員総選挙の得票データ hr96-21.csv をダウンロードして読み込んでみる
  • Rプロジェクトフォルダの中に data というフォルダを作成
  • ダウンロードした hr96-21.csv ファイルを data の中に入れる
  • .csvファイルの読み取りには {tidyverse} の中に含まれる read_csv()関数を使う
hr <- read_csv("data/hr96-21.csv", 
               na = ".")  # 欠損値処理のコマンド  

1. 行のソート: arrange()

  • 第一引数はデータフレームのオブジェクト
  • 第二引数以降は並び替えの基準となる変数名
  • 値が小さい行が上に表示される
  • 例えば、hr を使って当選者の中で (wl > 0) 獲得票数 (vote) の少ない候補者を表示してみる
hr |> 
  filter(wl > 0) |> 
  arrange(vote) |> 
  select(pref, kun, seito, j_name, wl, gender, rank, vote)
# A tibble: 3,659 × 8
   pref    kun seito        j_name      wl gender  rank  vote
   <chr> <dbl> <chr>        <chr>    <dbl> <chr>  <dbl> <dbl>
 1 東京     22 社民         保坂展人     2 male       5 13904
 2 京都      3 日本維新の会 森夏枝       2 female     4 16511
 3 埼玉      6 社民         深田肇       2 male       4 17909
 4 埼玉      8 共産         塩川鉄也     2 male       4 18512
 5 奈良      1 民主         家西悟       2 male       4 18994
 6 沖縄      1 共産         赤嶺政賢     2 male       4 19528
 7 高知      3 共産         春名真章     2 male       3 19549
 8 京都      5 希望         井上一徳     2 male       4 19586
 9 徳島      1 維新         吉田知代     2 female     3 20065
10 愛媛      2 維新         横山博幸     2 male       3 22677
# ℹ 3,649 more rows

欠損値が含まれているケースを 予め除外する: !is.na()

  • ある値が NA か否かを判定するには is.na() 関数を使う
hr |> 
  filter(is.na(exp)) |> 
  select(year, j_name, exp)
# A tibble: 1,016 × 3
    year j_name       exp
   <dbl> <chr>      <dbl>
 1  1996 伊東マサコ    NA
 2  1996 山田浩        NA
 3  1996 浅野光雪      NA
 4  1996 石川和己      NA
 5  1996 村松陽一      NA
 6  1996 山崎義章      NA
 7  1996 中野庸子      NA
 8  1996 小川修        NA
 9  1996 阿閉正雄      NA
10  1996 伊東敬芳      NA
# ℹ 1,006 more rows
  • expNA が 2831 もあることがわかる
  • 1996年から2021年までの総選挙の立候補者数 N と変数の数を確認する
dim(hr)
[1] 9660   22
  • 9660 のうち exp が 2831 も欠損であることがわかる
  • ここでは exp の欠損値を除外したいのだから、否定を表す ! を使う
hr |> 
  filter(!is.na(exp)) |> 
  select(year, j_name, exp)
# A tibble: 8,644 × 3
    year j_name          exp
   <dbl> <chr>         <dbl>
 1  1996 河村たかし  9828097
 2  1996 今枝敬雄    9311555
 3  1996 佐藤泰介    9231284
 4  1996 岩中美保子  2177203
 5  1996 青木宏之   12940178
 6  1996 田辺広雄   16512426
 7  1996 古川元久   11435567
 8  1996 石山淳一    2128510
 9  1996 藤原美智子  3270533
10  1996 吉田幸弘   11245219
# ℹ 8,634 more rows
  • exp が欠損していない 6829 行を抽出できた

2. 行のソート: arrange()

値の小さい順に表示

  • 行のソートには arrange() 関数を使う
  • 第一引数はデータフレームのオブジェクト
  • 第二引数以降は並び替えの基準となる変数名
  • 値の小さい行が上に表示される
  • 例えば、hr から当選者 (wl > 0) の行のみを抽出し
  • 得票数 (vote) が少ない当選者を上から順に表示してみる
hr |> 
  filter(wl > 0) |> 
  arrange(vote) |> 
  select(year, pref, kun, seito, j_name, wl, gender, rank, vote)
# A tibble: 3,659 × 9
    year pref    kun seito        j_name      wl gender  rank  vote
   <dbl> <chr> <dbl> <chr>        <chr>    <dbl> <chr>  <dbl> <dbl>
 1  1996 東京     22 社民         保坂展人     2 male       5 13904
 2  2017 京都      3 日本維新の会 森夏枝       2 female     4 16511
 3  1996 埼玉      6 社民         深田肇       2 male       4 17909
 4  2003 埼玉      8 共産         塩川鉄也     2 male       4 18512
 5  1996 奈良      1 民主         家西悟       2 male       4 18994
 6  2003 沖縄      1 共産         赤嶺政賢     2 male       4 19528
 7  1996 高知      3 共産         春名真章     2 male       3 19549
 8  2017 京都      5 希望         井上一徳     2 male       4 19586
 9  2021 徳島      1 維新         吉田知代     2 female     3 20065
10  2014 愛媛      2 維新         横山博幸     2 male       3 22677
# ℹ 3,649 more rows
  • 1996年以降の総選挙において、最も少ない票で当選したのは1996年東京22区の保坂展人さんで 13,904票(復活当選)
  • 復活当選ではなく、小選挙区での最小の当選者を知りたければ条件を wl == 1 と指定する
hr |> 
  filter(wl == 1) |> 
  arrange(vote) |> 
  select(year, pref, kun, seito, j_name, wl, gender, rank, vote)
# A tibble: 2,674 × 9
    year pref     kun seito j_name        wl gender  rank  vote
   <dbl> <chr>  <dbl> <chr> <chr>      <dbl> <chr>  <dbl> <dbl>
 1  1996 高知       1 共産  山原健二郎     1 male       1 33523
 2  2000 高知       1 自民  福井照         1 male       1 40765
 3  2000 大阪      17 自民  岡下信子       1 female     1 41781
 4  1996 京都       2 自民  奥田幹生       1 male       1 43060
 5  2003 高知       1 自民  福井照         1 male       1 43232
 6  2012 高知       1 自民  福井照         1 male       1 44027
 7  2009 高知       1 自民  福井照         1 male       1 44068
 8  1996 神奈川     4 自民  飯島忠義       1 male       1 46389
 9  1996 徳島       1 民主  仙谷由人       1 male       1 47057
10  1996 福井       1 新進  笹木竜三       1 male       1 48214
# ℹ 2,664 more rows
  • 1996年以降の総選挙において、最も少ない票で当選したのは1996年高知1区の山原健二郎さんで 33,523票(小選挙区当選)

値の大きい順に表示: arrange(desc())

  • 票数が多い当選者を上位に表示したい場合は、変数名を desc() 関数で囲む
hr |> 
  arrange(desc(vote)) |> 
  select(year, pref, kun, seito, j_name, wl, gender, rank, vote)
# A tibble: 9,660 × 9
    year pref     kun seito j_name        wl gender  rank   vote
   <dbl> <chr>  <dbl> <chr> <chr>      <dbl> <chr>  <dbl>  <dbl>
 1  2021 神奈川    15 自民  河野太郎       1 male       1 210515
 2  2009 北海道     9 民主  鳩山由紀夫     1 male       1 201461
 3  2009 静岡       6 民主  渡辺周         1 male       1 197688
 4  2005 神奈川    11 自民  小泉純一郎     1 male       1 197037
 5  2012 神奈川    15 自民  河野太郎       1 male       1 192604
 6  2009 埼玉       6 民主  大島敦         1 male       1 186993
 7  2005 神奈川    15 自民  河野太郎       1 male       1 186770
 8  2009 北海道     3 民主  荒井聰         1 male       1 186081
 9  2012 神奈川    11 自民  小泉進次郎     1 male       1 184360
10  2009 静岡       5 民主  細野豪志       1 male       1 184328
# ℹ 9,650 more rows
  • 1996年以降の総選挙において、最も多い票で当選したのは2021年神奈川 15 区の河野太郎さんで 210,515票(小選挙区当選)
  • 上位に含まれない候補者を知りたければ DT::datatable() 関数を使うと便利
hr2 <- hr |> 
  filter(wl == 1) |>  # 小選挙区当選者だけを指定
  arrange(desc(vote)) |> 
  select(year, pref, kun, seito, j_name, wl, gender, rank, vote, voteshare)

DT::datatable(hr2)

3. 行の結合: bind_rows()

  • 複数の data.frame を縦に結合する場合は、bind_rows() を使う
  • 2 つの data.frame があるとする
df1 <- data.frame(id = 1:5,
                  name = c("A", "B", "C", "D", "E"),
                  score = c(100, 90, 80, 70, 60))

df2 <- data.frame(id = 6:8,
                  name = c("F", "G", "H"),
                  score = c(50, 40, 30))
  • それぞれの data.frame を表示させてみる
df1
  id name score
1  1    A   100
2  2    B    90
3  3    C    80
4  4    D    70
5  5    E    60
df2
  id name score
1  6    F    50
2  7    G    40
3  8    H    30
  • 2つの data.frame は同じ変数名を共有している
    → bind_rows() 関数を使って、縦に積み重ねることが可能
bind_rows()・・・rows (行を) bind (結びつける)
  • データを縦に結合する時にはそれぞれの data.frame の変数名が一致する必要あり
  • ここでは id, name, score は一致している
bind_rows(df1, df2)
  id name score
1  1    A   100
2  2    B    90
3  3    C    80
4  4    D    70
5  5    E    60
6  6    F    50
7  7    G    40
8  8    H    30

list() 関数

  • df1df2 がそれぞれ 1 年ゼミと 2 年ゼミの学生データだとする
  • bind_rows() を使って縦に結合すると、学生の学年が分からない
  • データを縦に結合すると同時に、学生の識別変数(1年、2年)を追加したい場合
    → データを list() 関数を使ってまとめ
    → .id 引数を追加する
bind_rows(list("1年" = df1,
               "2年" = df2),
          .id = "学年")
  学年 id name score
1  1年  1    A   100
2  1年  2    B    90
3  1年  3    C    80
4  1年  4    D    70
5  1年  5    E    60
6  2年  6    F    50
7  2年  7    G    40
8  2年  8    H    30
  • これでそれぞれの学生の学年が識別できるようになった

4. 列の結合:

  • データを列ごとに横に結合する際にはバリエーションがある
  • ここで重要なこと
  • 結合に使う識別用の変数(キー変数)が必要
  • 識別用の変数(キー変数)・・・2 つの data.frame に共通する変数(下の例では univ
df1 <- data.frame(univ  = c("拓殖大学", "早稲田大学", "UCLA"),
                  city   = c("東京", "東京", "LA"),
                  pop  = c(8600, 47000, 45000))

df2 <- data.frame(univ  = c("拓殖大学", "早稲田大学", "UCLA"),
                  color  = c("オレンジ", "えんじ", "黄色と青"))
  • この2つのデータを結合する方法:
  1. left_join()
  2. right_join()
  3. inner_join()
  4. full_join()
  • いずれも使い方は同じ
  • 結合する 2 つの data.frame のオブジェクト名を入力
  • by = "キー変数名"の引数を追加

4.1 left_join() 関数のメカニズム

  • left_join(x, y)x を温存させる関数
  • データフレーム x・・・拓殖大学, 早稲田大学, UCLA が含まれる
  • データフレーム y・・・拓殖大学, 早稲田大学, 東北大学 が含まれる
  • 変数名が一致している拓殖大学と早稲田大学は問題なく結合できる
  • x の UCLA と y の東北大学はどうなるか?
  • left_join(x, y) を使うと、x の UCLA が優先されて残される
  • 東北大学は消え → UCLA の founder は欠損値 (NA) と表示される


変数名が一致したキー変数が両データに含まれている場合
x <- data.frame(univ  = c("拓殖大学", "早稲田大学", "UCLA"),
                pop  = c(8600, 47000, 45000))

y <- data.frame(univ  = c("拓殖大学", "早稲田大学", "東北大学"),
                founder  = c("桂太郎", "大隈重信", "日本国"))
left_join(x, y, by = "univ")
        univ   pop  founder
1   拓殖大学  8600   桂太郎
2 早稲田大学 47000 大隈重信
3       UCLA 45000     <NA>

4.2 right_join() 関数のメカニズム

  • right_join(x, y)y を温存させる関数
  • データフレーム x・・・拓殖大学, 早稲田大学, UCLA が含まれる
  • データフレーム y・・・拓殖大学, 早稲田大学, 東北大学 が含まれる
  • 変数名が一致している拓殖大学と早稲田大学は問題なく結合できる
  • x の UCLA と y の東北大学はどうなるか?
  • right_join(x, y) を使うと、y の東北大学が優先されて残される
  • UCLA は消え → 東北大学の pop は欠損値 (NA) と表示される


変数名が一致したキー変数が両データに含まれている場合
x <- data.frame(pop  = c(8600, 47000, 45000),
                univ  = c("拓殖大学", "早稲田大学", "UCLA"))

y <- data.frame(univ  = c("拓殖大学", "早稲田大学", "東北大学"),
                  founder  = c("桂太郎", "大隈重信", "日本国"))
right_join(x, y, by = "univ")
    pop       univ  founder
1  8600   拓殖大学   桂太郎
2 47000 早稲田大学 大隈重信
3    NA   東北大学   日本国

4.3 inner_join() 関数のメカニズム

  • データフレーム x・・・拓殖大学, 早稲田大学, UCLA が含まれる
  • データフレーム y・・・拓殖大学, 早稲田大学, 東北大学 が含まれる
  • inner_join(x, y)xy 両データに同時に存在する行のみが結合対象
    → 拓殖大学と早稲田大学だけが結合される


x <- data.frame(pop  = c(8600, 47000, 45000),
                univ  = c("拓殖大学", "早稲田大学", "UCLA"))

y <- data.frame(univ  = c("拓殖大学", "早稲田大学", "東北大学"),
                  founder  = c("桂太郎", "大隈重信", "日本国"))
inner_join(x, y, by = "univ")
    pop       univ  founder
1  8600   拓殖大学   桂太郎
2 47000 早稲田大学 大隈重信

4.4 full_join() 関数のメカニズム

  • データフレーム x・・・拓殖大学, 早稲田大学, UCLA が含まれる
  • データフレーム y・・・拓殖大学, 早稲田大学, 東北大学 が含まれる
  • full_join(x, y)xy 全てを温存させる
    → 欠損しているセルは欠損値 (NA) と表示される


x <- data.frame(pop  = c(8600, 47000, 45000),
                univ  = c("拓殖大学", "早稲田大学", "UCLA"))

y <- data.frame(univ  = c("拓殖大学", "早稲田大学", "東北大学"),
                  founder  = c("桂太郎", "大隈重信", "日本国"))
full_join(x, y, by = "univ")
    pop       univ  founder
1  8600   拓殖大学   桂太郎
2 47000 早稲田大学 大隈重信
3 45000       UCLA     <NA>
4    NA   東北大学   日本国

5. tidy data 構造

  • tidy data とはHadley Wickham が提唱したデータ分析に適したデータ構造
  • 日本語に訳すと「整然データ」または「簡潔データ」
  • tidy data はパソコンにとって読みやすいデータ
  • R における多くの分析は整然データを基づいて行われる
  • (ただし、人間にとって読みやすいデータとはいえない)
  • tidyr パッケージ・・・整然ではないデータ(=雑然データ)を整然データへ変形

tidy data の 4 つの原則

1. 1 つの「列」 = 1 つの「変数」
2. 1 つの「行」 = 1 つの「観測」
3. 1 つの「セル」 = 1 つの「値」
4. 1 つの「表」 = 1 つの「分析単位」

11.1 「1 列」=「1 変数」原則

  • 当たり前にように見える原則だが、ほとんどのデータはこの原則を満たしていない
  • 下の表は10人にモスバーガーとマックを食べてもらい点数を付けてもらったデータ
  • このデータを構成する変数は 3 つ: 被験者番号、mos、mc
雑然データ
雑然データ
  • このデータの問題は「バーガーの点数」という変数が 2 列(mosmc という2 つの変数)に分かれていること
  • 「1 列」に「バーガーの点数」を表す「2 変数」(mos, mc)が入っている
    → 「バーガーの点数 (score)」と「バーガーの種類 (burger)」を表す変数をそれぞれ作る必要がある
  • この雑然データは下のように整然データに変換する必用がある
「雑然データ」から「整然データ」への変換
「雑然データ」から「整然データ」への変換
  • 「1 列」に「バーガーの点数」を表すのは「1 変数」(score)だけ
  • 「1 列」に「バーガーの種類」を表すのは「1 変数」(burger)だけ

5.2 「1 行」=「1 観察」原則

  • 1 行に 1 つの観察が入るのは当たり前のように思える
  • 上の図の左側「雑然データ」の「1 行」には「バーガーの点数」に関して 2 つの観察 (mos, mc) が入っている
  • score と burger という変数を新たに作る
    → 「1 行」には「バーガーの点数」に関して 1 つの観察 (score) だけ
    → 「1 行」には「バーガーの種類」に関して 1 つの観察 (burger) だけ

5.3 「1 セル」=「1 値」原則

  • 1 つのセルに 1 つの値のみが含まれるといった原則 3 は、通常、保たれている
  • 例えば、被験者番号 3 の人はモスとマックの点数がどちらも「80点」だからとって、下のようにまとめることはめったにない

5.4 「1 表」=「1 分析単位」原則

  • 1 つの表に複数の分析単位が含まれるケースが多い(特に政府統計など)
  • 1 つの表に「国」、「都道府県」、「市区町村」、「行政区」の分析単位が混在していることが多い

6. 列の結合の実例

チェックテストの平均点を示す変数 (ave) の作成

  • 塾で複数回行うチェックテストの平均点を計算してみよう

ここでやりたいこと ・50人の生徒が履修している
・春学期にチェックテストを5回実施した
・生徒は全てのチェックテストを受験したとは限らない
・公平にチェックテストの点数を計算するにはどうすればいいのか

  • ここでは学期末に次のような架空のデータが得られたとする
    ・履修者 50名の 5 回分のチェックテストデータ:checktest.csv, (N=50)

データの準備

チェックテストデータ: checktest.csv

  • 分析に必要な {tidyverse} パッケージを読み込む
library(tidyverse)
  • checktest.csv をダウンロードする

  • 期末試験のデータを読み込み df_checktest と名前を付ける

  • na = "." を指定し、欠損値を「.」で表示すると指定

df_check <- read_csv("data/checktest.csv", 
  na = ".")
  • df_checktest の中を確認してみる
DT::datatable(df_check)
  • 50 人分の学生のチェックテスト結果が確認できる
  • df_check の記述統計を表示させる
  • チャンクオプションで {r, results = "asis"} と指定する
stargazer::stargazer(as.data.frame(df_check),
  type = "html")
Statistic N Mean St. Dev. Min Max
test_1 43 80.256 11.146 60 100
test_2 46 77.804 11.268 55 100
test_3 42 80.357 8.647 60 100
test_4 44 78.182 10.308 50 100
test_5 43 80.721 9.407 55 100

・それぞれのチェックテストには 4〜8 の欠損値 (NA) がある

  • 50人の生徒が受けた5回のチェックテストの平均値を計算する
df_check1 <- df_check |> 
  mutate(ave = (test_1 + test_2 + test_3 + test_4 + test_5) / 5)
DT::datatable(df_check1)
  • 5回のチェックテスト全てを提出している生徒の平均値を計算できた
  • しかし、2回、3回、4回しか提出していない生徒の平均値は計算されていない

解決策

rowwise()na.rm = TRUE を加える

→ 欠損値を考慮した平均値を計算できる

df_check1 <- df_check1 |> 
  rowwise() |> 
  mutate(ave = mean(c(test_1, test_2, test_3, test_4, test_5), 
    na.rm = TRUE))
DT::datatable(df_check1)
  • 必要な変数だけに絞る
df_check2 <- df_check1 |> 
  select(name, ave)
DT::datatable(df_check2)

チェックテストの提出回数を示す変数 (submission) の作成

Step 1

  • ワイド型データ df_check をロング型データ df_check_long に変換する
df_check_long <- df_check |> 
    pivot_longer(cols      = test_1:test_5,
                 names_to  = "test_number", # テストの種類を入力  
                 values_to = "score") # テストスコアを入力  
DT::datatable(df_check_long)

Step 2

df_check_longscore に欠損値がある生徒を除外する

df_check_long <-  df_check_long |> 
  filter(!is.na(score))
DT::datatable(df_check_long)

Step 3

・学生ごとに (Student_1 〜 Student_50まで)チェックテストの「受験回数」を計算し表示させる
→ group_by() 関数を使って df_check_longname に現れた生徒の名前の数を計算する
→ これがチェックテストの「受験回数」
→ 「受験回数」を submission という変数に格納する

df_check_nosub <- df_check_long |> 
  group_by(name) |> 
  summarise(submission = n())
DT::datatable(df_check_nosub)

Step 4

  • チェックテストの「平均値」と「提出回数」をマージさせる
  • df_check2 + df_check_nosub => df_st_list
  • 両者に共通する変数 (name) を手がかりにマージする

df_check2

DT::datatable(df_check2)

df_check_nosub

DT::datatable(df_check_nosub)
df_st_list <- left_join(df_check2, df_check_nosub, by = "name")
DT::datatable(df_st_list)

チェックテストの受験回数と平均点の関係を確認

  • マックユーザーのみ次のコマンドを入力する
  theme_set(theme_bw(base_family = "HiraKakuProN-W3"))
df_st_list  |>  
  ggplot(aes(submission, ave)) +
  geom_point() +
  labs(x = "チェックテストの受験回数", y = "チェックテストの平均点",
         title = "チェックテストの受験回数と平均点の散布図") + 
  stat_smooth(method = lm) +  # (method = lm, se = FALSE) → 95% 信頼区間が消える
  geom_text(aes(y = ave + 0.2, 
                label = name), 
            size = 4, 
            vjust = 0)

  • チェックテストの受験回数が多い生徒ほど、平均点が高い傾向が認められる

履修科目の成績評価

  • 大学で履修する授業科目のシラバスに記載された成績評価を事例に考えてみよう

成績評価基準 ・期末試験(60%)
・宿題 (40%)

  • ここでは学期末に次のような 2 つの架空のデータが得られたとする
    ・履修者 100名の期末試験データ:R01_exam_score.csv, (N=100)
    ・提出された 5 回分の宿題データ:R01_hw.csv, (N=500)

データの準備

期末試験のデータ: R01_exam_score.csv

  • 分析に必要な {tidyverse} パッケージを読み込む
library(tidyverse)
  • R01_exam_score.csv をダウンロードする

  • 期末試験のデータを読み込み df_exam と名前を付ける

  • na = "." を指定し、欠損値を「.」で表示すると指定

df_exam <- read_csv("data/R01_exam_score.csv", na = ".")
  • df_exam の中を確認してみる
DT::datatable(df_exam)
  • 100 人分の学生の期末試験結果が確認できる
  • df_exam の記述統計を表示させる
summary(df_exam)
     name               score       
 Length:100         Min.   : 45.00  
 Class :character   1st Qu.: 68.00  
 Mode  :character   Median : 74.00  
                    Mean   : 75.23  
                    3rd Qu.: 84.00  
                    Max.   :100.00  
                    NA's   :9       
  • score には 9 の欠損値 (NA) を確認

  • 期末試験結果の分布をヒストグラムで表示してみる

theme_update(text = element_text(family = "HiraginoSans-W3"))
df_exam |> 
  filter(!is.na(score)) |> 
  ggplot() +
  geom_histogram(aes(x = score), color = "white", 
                 binwidth = 10, boundary = 0) +
  labs(x = "期末試験点数", y = "学生数") +
  geom_vline(xintercept = mean(df_exam$score, 
                               na.rm = TRUE), # score には欠損値が含まれるので、na.rm = TRUE を指定
             col = "magenta", # 平均値に真ジェンタ色の縦線を引く
    linetype = "dotted")   

  • きれいな正規分布ではないものの、ほぼ左右対象で平均が 75 点程度だとわかる

宿題提出データ: R01_hw.csv

  • R01_df_hw.csv をダウンロードする
  • na = "." を指定し、欠損値を「.」で表示すると指定
df_hw <- read_csv("data/R01_hw.csv", na = ".")
  • df_hw の中を確認してみる
DT::datatable(df_hw)
  • 100人の学生が 5 回の宿題を提出したかどうかが分かる  
  • 100人の学生が 5 回の宿題を提出した結果なので N = 500

ここでの問題:

  • df_examN = 100, df_hwN = 500

  • 成績を付けるために必要なのは 100人分の成績データ
    → 観測数を N = 100 に統一する必要がある

  • 学生ごとに (Student_1 〜 Student_100まで)何回宿題を「提出」したかを計算し表示させる → group_by() 関数を使って df_hwnamehw をグループ化し「提出」の合計を計算
    → 「提出」の合計値を submission という変数に格納する

df_hw <- df_hw |> 
  group_by(name, hw) |> 
  summarise(submission = n())
DT::datatable(df_hw)
  • submission には「提出」した回数ばかりでなく「未提出」の回数も表示されている
  • 「未提出」の回数は不要なので、filter() 関数を使って、非表示にする
  • 変数 hw も不要なので select() 関数を使って非表示にする
df_hw <- df_hw |> 
  filter(hw == "提出") |> 
  select(name, submission)
DT::datatable(df_hw)
  • 履修学生は 100人いるはずだが、92人分しかデータがない
    → 7 人の学生が宿題を 1 回も提出していないことがわかる
  • 宿題を提出した 92 人の学生の分布をヒストグラムで表示してみる
df_hw |> 
  filter(!is.na(submission)) |>  # 欠損値処理
  ggplot() +
  geom_histogram(aes(x = submission), color = "white", 
                 binwidth = 1, boundary = 0) +
  labs(x = "宿題の提出回数", y = "学生数") +
  geom_vline(xintercept = mean(df_hw$submission,
    na.rm = T), 
    col = "yellow",
    linetype = "dotted") # 平均値に黄色の縦線を引く 

  • ほとんどの学生が 3 回もしくは 4 回提出しており、平均が 3.6 回程度だとわかる
  • summary() 関数を使って、正確な記述統計を表示してみる
summary(df_hw$submission)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   3.000   4.000   3.652   4.000   5.000 
  • ここでは成績を付けるために必要な 2 つのデータフレーム (df_examdf_hw)を得た  
  • 問題は、これら 2 つのデータフレームを次のどの関数を使って結合するかということ
  1. left_join()
  2. right_join()
  3. inner_join()
  4. full_join()
  • どのような基準で成績を確定するかによって、使う関数が変わってくる

6.1 データフレームの結合: left_join()

  • 例えば、成績評価基準が次のような場合

成績評価基準_1 ・期末試験(60%)
・宿題 (40%)
期末試験を受けた人だけが採点対象

  • つまり「宿題を提出するしないに関わらず、期末試験を受験した人だけを採点する」
    → データフレームx (ここでは df_exam) を温存させて結合する
    left_join() 関数を使う

  • この場合、結合されるデータフレームは次のような構造になる

  • left_join()x を温存する結合方法
  • x に含まれる Student_1, Student_2, Student_3 だけが温存される
  • y に含まれても x には含まれない Student_4 は除外される
    → ここでは name がキー変数
df_left <- left_join(df_exam, df_hw, by = "name")
DT::datatable(df_left)
  • left_join()x を優先した 100 人全員が結合対象
    → 全員が期末試験を受けた

成績確定に必要な変数を作る

  • 成績を確定するために必要な 2 つの変数 (scoresubmission) は作れた
  • しかし、score は 100点満点換算の値だが、submission は 5 点満点の値 → submission を 100点満点換算の値 (sub) に変換する必要がある
df_left <- df_left |> 
  mutate(sub = submission * 100/5) |> 
  select(name, score, sub) # submission  を非表示にする 
DT::datatable(df_left)

成績を計算する

  • 成績を確定するためのアルゴリズムは次のとおり

成績評価基準 ・期末試験(60%)
・宿題 (40%)

  • この基準に沿って、総合点を計算する
df_left <- df_left |> 
  mutate(grade = score * 0.6 + sub * 0.4)
DT::datatable(df_left)
  • grade が小数点表示になっているので、round() 関数を使って、小数点以下を四捨五入して非表示にする
  • 表示する変数を namegrade に絞る
df_left <- df_left |> 
  mutate(grade = score * 0.6 + sub * 0.4) |> 
  mutate(grade = round(grade, digits = 0)) |> 
  select(name, grade)
DT::datatable(df_left)
  • これで、最終的な成績が計算できた

  • 学生の最終成績の分布をヒストグラムで表示してみる

df_left |> 
  filter(!is.na(grade)) |> 
  ggplot() +
  geom_histogram(aes(x = grade), color = "white", 
                 binwidth = 10, boundary = 0) +
  labs(x = "最終成績", y = "学生数") +
  geom_vline(xintercept = mean(df_left$grade, 
    na.rm = T), 
    col = "yellow",
    linetype = "dotted")# 平均値に黄色の縦線を引く  

  • 最終成績の記述統計は次のとおり
summary(df_left$grade)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  53.00   67.75   75.00   74.62   81.00  100.00      16 
  • 期末試験を受けた 84人の成績表が完成した

6.2 データフレームの結合: full_join()

  • 例えば、成績評価基準が次のような場合

成績評価基準_2 ・期末試験(60%)
・宿題 (40%)
宿題を 1 回も提出しなくても、期末試験で満点を取れば単位(= 60点)を認定する

  • つまり「宿題提出の有無や期末試験を受験したかどうかにかかわらず、全員を採点する」
    → 2 つのデータフレームに存在する全ての行が結合対象になる
    full_join() 関数を使う

  • この場合、結合されるデータフレームは次のような構造になる

  • full_join()xy 両データに存在する全ての行が結合対象

  • x には Student_3 があるが、y には Student_3 がない時
    → Student_3score の値は残る

  • y には Student_4 があるが、x には Student_4 がない時
    → Student_4submission の値は残る

  • df_examdf_hw は問題なく結合できる

  • Student_1 から Student_100 まで全員が結合できる
    → ここでは name がキー変数

df_full <- full_join(df_exam, df_hw, by = "name")
DT::datatable(df_full)

成績確定に必要な変数を作る

  • 成績を確定するために必要な 2 つの変数 (scoresubmission) は作れた
  • しかし、score は 100点満点換算の値だが、submission は 5 点満点の値
    → submission を 100点満点換算の値 (sub) に変換する必要がある
df_full <- df_full |> 
  mutate(sub = submission * 100/5) |> 
  select(name, score, sub) # submission  を非表示にする 
DT::datatable(df_full)
summary(df_full)
     name               score             sub        
 Length:100         Min.   : 45.00   Min.   : 20.00  
 Class :character   1st Qu.: 68.00   1st Qu.: 60.00  
 Mode  :character   Median : 74.00   Median : 80.00  
                    Mean   : 75.23   Mean   : 73.04  
                    3rd Qu.: 84.00   3rd Qu.: 80.00  
                    Max.   :100.00   Max.   :100.00  
                    NA's   :9        NA's   :8       

欠損値:

  • 期末試験を受験しなかった人が 9 人 (NA's = 9)
  • 宿題を1回も提出しなかった人が 8 人 (NA's = 8)

成績を計算する

  • 成績を確定するためのアルゴリズムは次のとおり

成績評価基準 ・期末試験(60%)
・宿題 (40%)

  • この基準に沿って、総合点を計算する
df_full <- df_full |> 
  mutate(grade = score * 0.6 + sub * 0.4)
DT::datatable(df_full)
  • grade が小数点表示になっているので、round() 関数を使って、小数点以下を四捨五入して非表示にする
  • 表示する変数を namegrade に絞る
df_full <- df_full |> 
  mutate(grade = score * 0.6 + sub * 0.4) |> 
  mutate(grade = round(grade, digits = 0)) |> 
  select(name, score, sub,grade)
DT::datatable(df_full)
  • これで、最終的な成績が計算できた

  • 学生の最終成績の分布をヒストグラムで表示してみる

df_full |> 
  filter(!is.na(grade)) |> 
  ggplot() +
  geom_histogram(aes(x = grade), color = "white", 
                 binwidth = 10, boundary = 0) +
  labs(x = "最終成績", y = "学生数") +
  geom_vline(xintercept = mean(df_full$grade,
    na.rm = T), 
    col = "yellow",
    linetype = "dotted")# 平均値に黄色の縦線を引く   

  • 最終成績の記述統計は次のとおり
summary(df_full$grade)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  53.00   67.75   75.00   74.62   81.00  100.00      16 
  • 宿題は 1 度も提出しなかったが、期末で満点を取得した学生はいない
  • Student_8 は宿題は 1 度も提出しなかったが、期末で 97点を取得
  • しかし、最終成績は 97 x 0.6 = 58点 なので単位認定はできない

6.3 データフレームの結合: inner_join()

  • 例えば、成績評価基準が次のような場合

成績評価基準_3 ・期末試験(60%)
・宿題 (40%)
「宿題を 1 回も提出しない人」もしくは「期末試験を受験しない人」は採点されない

  • つまり「宿題を最低 1 回提出し、かつ期末試験を受験した人だけを採点する」
    → どちらのデータフレームにも存在する行のみが結合対象になる
    inner_join() 関数を使う

  • この場合、結合されるデータフレームは次のような構造になる

  • inner_join()xy 両データに共通して存在する行が結合対象
  • x には Student_3 があるが、y には Student_3 がない時
    → Student_3 は残らない
  • y には Student_4 があるが、x には Student_4 がない時
    → Student_4 は残らない
  • df_examdf_hw は問題なく結合できる
  • Student_1 から Student_100 まで全員が結合できるとは限らない

→ ここでは name がキー変数

df_final <- inner_join(df_exam, df_hw, by = "name")
DT::datatable(df_final)
  • inner_join()xy 両データに同時に共通して存在する 92 人だけが結合対象

成績確定に必要な変数を作る

  • 成績を確定するために必要な 2 つの変数 (scoresubmission) は作れた
  • しかし、score は 100点満点換算の値だが、submission は 5 点満点の値
    → submission を 100点満点換算の値 (sub) に変換する必要がある
df_final <- df_final |> 
  mutate(sub = submission * 100/5) |> 
  select(name, score, sub) # submission  を非表示にする  
DT::datatable(df_final)
  • 「宿題を最低 1 回提出し、かつ期末試験を受験した人」は 92 人

成績を計算する

  • 成績を確定するためのアルゴリズムは次のとおり

成績評価基準_3 ・期末試験(60%)
・宿題 (40%)
「宿題を 1 回も提出しない人」もしくは「期末試験を受験しない人」は採点されない

  • この基準に沿って、総合点を計算する
df_final <- df_final |> 
  mutate(grade = score * 0.6 + sub * 0.4)
DT::datatable(df_final)
  • grade が小数点表示になっているので、round() 関数を使って、小数点以下を四捨五入して非表示にする
  • 表示する変数を namegrade に絞る
df_final <- df_final |> 
  mutate(grade = score * 0.6 + sub * 0.4) |> 
  mutate(grade = round(grade, digits = 0)) |> 
  select(name, grade)
DT::datatable(df_final)
  • これで、最終的な成績が計算できた

  • 学生の最終成績の分布をヒストグラムで表示してみる

df_final |> 
  filter(!is.na(grade)) |> 
  ggplot() +
  geom_histogram(aes(x = grade), color = "white", 
                 binwidth = 10, boundary = 0) +
  labs(x = "最終成績", y = "学生数") +
  geom_vline(xintercept = mean(df_final$grade,
    na.rm = T), 
    col = "yellow",
    linetype = "dotted") # 平均値に黄色の縦線を引く 

  • 最終成績の記述統計は次のとおり
summary(df_final$grade)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  53.00   67.75   75.00   74.62   81.00  100.00       8 

7. Wide型 ⇔ Long型の変換

  • tidyr パッケージを使ったデータの変換は次の 3 種類ある
pivot_longer() Wide型(雑然) → Long型(整然)
pivot_wider() long型(整然) → wide型(雑然)
separate() セルの分割 例:「年月日」→「年」「月」「日」

7.1 pivot_longer()

データの準備 (mos_mc_paired.csv) mos_mc_paired.csv をクリックしてデータをパソコンにダウンロード   ・RProject フォルダ内に data という名称のフォルダを作成する
・ダウンロードした mos_mc.csv を手動でRProject フォルダ内にある data フォルダに入れる
・選挙データの読み取る

df_mosmc <- read_csv("data/mos_mc_paired.csv",
               na = ".")  
  • データフレームを表示
DT::datatable(df_mosmc)
  • 「ワイド形式 (wide format)」なので、R 上では分析しにくい

pivot_longer() 関数を使って「ロング形式 (long format)」に変換する

pivot_longer() 関数 データ |>
  pivot_longer(cols = 変数が格納されている列,   names_to = "元の列名が入る変数名",   values_to = "変数の値が入る変数名")

df_long <- df_mosmc |>  
    pivot_longer(cols      = mos:mc,
                 names_to  = "burger", # バーガー店名を入力  
                 values_to = "score") # バーガーの評価点を入力  
  • ロング形式に変更したデータフレームを確認
DT::datatable(df_long)

7.2 pivot_wider()

  • long 型データを wide 型データへ整形する際は pivot_wider() を使う
  • 雑然データは wide 型データが多い
  • 雑然データの方が人間にとっては読みやすい場合もある
  • 図表を作成する際は常に読む側の立場から考える必要がある
  • pivot_wider() 関数を使って「ロング形式 (wide format)」に変換する

pivot_wider() 関数 データ |>
  pivot_wider(cols = 変数が格納されている列,   names_from = "元の列名が入る変数名",   values_from = "変数の値が入る変数名")

df_wide <- df_long |> 
  pivot_wider(names_from  = "burger", # バーガー店名を入力 
              values_from = "score") # バーガーの評価点を入力  
  • ワイド形式に変更したデータフレームを確認
DT::datatable(df_wide)

7.3 separate()

データの準備: COVID19_Worldwide.csv

COVID-19 の新規感染者数データの分析

変数名 詳細
ID ID
Country 国名
Date 年月日
Confirmed_Day COVID-19 新規感染者数(人)/ 一日あたり
Confirmed_Total COVID-19 累積感染者数(人)総合
Death_Day COVID-19 新規死亡者数(人) 一日あたり
Death_Total COVID-19 累積死亡者数(人)総合
Test_Day COVID-19 新規検査数(人) 一日あたり
Test_Total COVID-19 累積検査数(人)総合
covid_df <- read_csv("data/COVID19_Worldwide.csv", 
                       guess_max = 10000) 
                      # 最初の10000行を読んでからデータ型を判断するよう設定
  • 変数を確かめる
names(covid_df)
[1] "ID"              "Country"         "Date"            "Confirmed_Day"  
[5] "Confirmed_Total" "Death_Day"       "Death_Total"     "Test_Day"       
[9] "Test_Total"     
  • 分析に使う変数だけに絞る
df1 <- covid_df |> 
  select(Country, Date, Confirmed_Total, Death_Total)
変数名 詳細
Country 国名
Date 年月日
Confirmed_Total COVID-19 累積感染者数(人)総合
Death_Total COVID-19 累積死亡者数(人)総合
  • データの様子がわかるように DT パッケージを使って表示する
DT::datatable(df1)

df1tidy data の 4 つの原則を満たす

1. 1 つの「列」 = 1 つの「変数」
2. 1 つの「行」 = 1 つの「観測」
3. 1 つの「セル」 = 1 つの「値」
4. 1 つの「表」 = 1 つの「分析単位」

→ このデータは tidy data (=long型)
→ データを変換する必要はない

  • covid_df の変数の記述統計を表示させる
library(stargazer)
  • チャンクオプションで {r, results = "asis"} と指定する
stargazer(as.data.frame(df1), 
          type ="html",
          digits = 2)
Statistic N Mean St. Dev. Min Max
Confirmed_Total 31,806 18,250.14 115,471.60 0 3,184,582
Death_Total 31,806 1,039.01 6,565.51 0 134,094
  • ここでは separate() 関数の使い方を解説する
  • たとえば、年月日データ (Data) は 2020/1/22 と表示されている
  • 例えば年ごとの新規感染者数をまとめようとした場合、時点の列が「YYYY年MM月DD日」では不都合
    → 年、月、日に分割する必要がある
  • covid_dfseparate() 列を YearMonthDay に分けてみる

separate()関数の使い方 データ |>
  separate(col = "分割する変数名",
  into = c("分割後の変数名 1", "分割後の変数名 2", "分割後の変数名 3", ...),
  sep = "分割する基準")   

  • "2020/1/22" の場合、"2020""1""22""/" という「基準」によって分割されている
df1 <- df1 |> 
  separate(col  = "Date",
           into = c("Year", "Month", "Day"),
           sep  = "/")

2020年 (1月22日〜7月10日)の国別「累積感染者数」と「累積死者数」を計算してみる

death_country <- df1 |> 
  group_by(Country, Year) |> 
  summarise(Death = sum(Death_Total),
            Infected = sum(Confirmed_Total))
DT::datatable(death_country)
  • death_country の変数の記述統計を表示させる

  • チャンクオプションで {r, results = "asis"} と指定する

stargazer(as.data.frame(death_country), 
          type ="html",
          digits = 2)
Statistic N Mean St. Dev. Min Max
Death 186 177,670.60 782,130.30 0 8,616,010
Infected 186 3,120,774.00 13,105,502.00 731 160,231,690
  • Covid19 の累積感染者数と累積死者数の散布図を描いてみる
plot_1 <- death_country |>  
  ggplot(aes(Infected, Death)) +
  geom_point() +
  stat_smooth(method = lm) +
  ggrepel::geom_text_repel(aes(label = Country),
            size = 3, 
            family = "HiraKakuPro-W3") +
  labs(x = "Covid19累積感染者数", y = "累積死者数")+
  theme_bw(base_family = "HiraKakuProN-W3")

plot_1

  • 外れ値であるアメリカを除外してみる
plot_2 <- death_country |>  
  filter(Country != "United States") |> 
  ggplot(aes(Infected, Death)) +
  geom_point() +
  stat_smooth(method = lm) +
  ggrepel::geom_text_repel(aes(label = Country),
            size = 3, 
            family = "HiraKakuPro-W3") +
  labs(x = "Covid19累積感染者数", y = "累積死者数")+
  theme_bw(base_family = "HiraKakuProN-W3")
plot_2

7.4 str_c()

データの準備: hr96-21.csv

総選挙データ (1996年~2021年)

hr <- read_csv("data/hr96-21.csv",
  na = ".")
  • hr には 22 個の変数が入っている
変数名 詳細
year 選挙年 (1996-2021)
pref 都道府県名
ku 小選挙区名
kun 小選挙区
rank 当選順位
wl 選挙の当落: 1 = 小選挙区当選、2 = 復活当選、0 = 落選
nocand 立候補者数
seito 候補者の所属政党
j_name 候補者の氏名(日本語)
name 候補者の氏名(ローマ字)
previous これまでの当選回数(当該総選挙結果は含まない)
gender 立候補者の性別: “male”, “female”
age 立候補者の年齢
exp 立候補者が使った選挙費用(総務省届け出)
status 候補者のステータス: 0 = 非現職、1 現職、2 = 元職
vote 得票数
voteshare 得票率 (%)
eligible 小選挙区の有権者数
turnout 小選挙区の投票率 (%)
seshu_dummy 世襲候補者ダミー: 1 = 世襲、0 = 非世襲(地盤世襲 or 非世襲)
jiban_seshu 地盤の受け継ぎ元の政治家の氏名と関係
nojiban_seshu 世襲元の政治家の氏名と関係
  • データフレーム hr に入っている変数を確かめる
names(hr)
 [1] "year"          "pref"          "ku"            "kun"          
 [5] "wl"            "rank"          "nocand"        "seito"        
 [9] "j_name"        "gender"        "name"          "previous"     
[13] "age"           "exp"           "status"        "vote"         
[17] "voteshare"     "eligible"      "turnout"       "seshu_dummy"  
[21] "jiban_seshu"   "nojiban_seshu"
  • 分析に使う変数だけに絞る
hr1 <- hr |> 
  select(year, ku, kun, j_name)
変数名 詳細
year 選挙年 (1996-2017)
ku 小選挙区名
kun 小選挙区
j_name 候補者の氏名(日本語)
  • データの様子がわかるように DT パッケージを使って表示する
DT::datatable(hr1)
  • ここでは str_c() 関数の使い方を解説する
  • たとえば、小選挙区名 (ku) は tokyo、小選挙区 (kun) は 1 のように表示されている
  • 衆議院の選挙区を tokyo_1 のように表示したいとする

str_c()関数の使い方 データ |>
str_c(新たに作る変数名 = str_c(1番目の変数名, 2番目の変数名, sep = "間に入れる記号")   

hr1 <- hr1 |> 
  mutate(district = str_c(ku, kun, sep = "_"))
DT::datatable(hr1)

8. Exercise

Q8.1: 「2. 行のソート: arrange()」を参考にして、次の問題にこたえなさい

  • 分析には衆議院選挙データセット hr96-21.csv を使うこと

  • 表示する変数は次の 6 つに限ること

  1. year
  2. pref
  3. kun
  4. seito
  5. j_name
  6. vote
  7. voteshare
  • Q1: 2021年総選挙の立候補者の中で、獲得した票数の多い順に並べ、トップ10人の候補者名を挙げなさい

  • Q2: 2021年総選挙の立候補者の中で、獲得した得票率の大きい順に並べ、トップ10人の候補者名を挙げなさい

Q8.2: 「7.3 separate()」を参考にして、次の問題にこたえなさい

  • データ COVID19_Worldwide.csv を使う

  • Q1 2020年 (1月22日〜7月10日)の国別「累積検査数」を x 軸、「累積感染者数」を y 軸に設定した散布図を描きなさい

  • Q2 2020年 (1月22日〜7月10日)の国別「累積検査数」を x 軸、「累積感染者数」を y 軸に設定した散布図を描きなさい
    ・外れ値があれば、外れ値を除外した散布図を示しなさい

参考文献
  • Tidy Animated Verbs
  • 宋財泫 (Jaehyun Song)・矢内勇生 (Yuki Yanai)「私たちのR: ベストプラクティスの探究」
  • 宋財泫「ミクロ政治データ分析実習(2022年度)」
  • 土井翔平(北海道大学公共政策大学院)「Rで計量政治学入門」
  • 矢内勇生(高知工科大学)授業一覧
  • 浅野正彦, 矢内勇生.『Rによる計量政治学』オーム社、2018年
  • 浅野正彦, 中村公亮.『初めてのRStudio』オーム社、2018年
  • Winston Chang, R Graphics Cookbook, O’Reilly Media, 2012.
  • Kieran Healy, DATA VISUALIZATION, Princeton, 2019
  • Kosuke Imai, Quantitative Social Science: An Introduction, Princeton University Press, 2017