3  Rによるデータハンドリング

心理学を始め,データを扱うサイエンスでは,データ収集の計画,実行と,データに基づいた解析結果,それを踏まえてのコミュニケーションとの間に,「データをわかりやすい形に加工し,可視化し,分析する」という手順がある。このデータの加工をデータハンドリングという。統計といえば「分析」に注目されがちだが,実際にはデータハンドリングと可視化のステップが最も時間を必要とし,重要なプロセスである。

3.1 tidyverseの導入

本講義ではtidyverseをつかったデータハンドリングを扱う。tidyverseは,データに対する統一的な設計方針を表す概念でもあり,具体的にはそれを実装したパッケージ名でもある。まずはtidyverseパッケージをインストール(ダウンロード)し,次のコードでRに読み込んでおく。

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

Attaching core tidyverse packages,と表示され,複数のパッケージ名にチェックマークが入っていたものが表示されただろう。tidyverseパッケージはこれらの下位パッケージを含むパッケージ群である。これに含まれるdplyr,tidyrパッケージはデータの整形に,readrはファイルの読み込みに,forecatsはFactor型変数の操作に,stringrは文字型変数の操作に,lubridateは日付型変数の操作に,tibbleはデータフレーム型オブジェクトの操作に,purrrはデータに適用する関数に,ggplot2は可視化に特化したパッケージである。

続いてConflictsについての言及がある。tidyverseパッケージに限らず,パッケージを読み込むと表示されることのあるこの警告は,「関数名の衝突」を意味している。ここまで,Rを起動するだけで,sqrt,meanなどの関数が利用できた。これはRの基本関数であるが,具体的にはbaseパッケージに含まれた関数である。Rは起動時にbaseなどいくつかのパッケージを自動的に読み込んでいるのである。これに別途パッケージを読み込むとき,あとで読み込まれたパッケージが同名の関数を使っていることがある。このとき,関数名は後から読み込んだもので上書きされる。そのことについての警告が表示されているのである。具体的にみると,dplyr::filter() masks stats::filter()とあるのは,最初に読み込んでいたstatsパッケージのfilter関数は,(tidyverseパッケージに含まれる)dplyrパッケージのもつ同名の関数で上書きされ,今後はこちらが優先的に利用されるよ,ということを示している。

このような同音異字関数は,関数を特定するときに混乱を招くかもしれない。あるパッケージの関数であることを明示したい場合は,この警告文にあるように,パッケージ名::関数名,という書き方にすると良い。

3.2 パイプ演算子

続いてパイプ演算子について解説する。パイプ演算子はtidyverseパッケージに含まれていたmagrittrパッケージで導入されたもので,これによってデータハンドリングの利便性が一気に向上した。そこでRもver 4.2からこの演算子を導入し,特段パッケージのインストールを必要としなくとも使えるようになった。このR本体のパイプ演算子のことを,tidyverseのそれと区別して,ナイーブパイプと呼ぶこともある。

ともあれこのパイプ演算子がいかに優れたものであるかを解説しよう。次のスクリプトは,あるデータセットの標準偏差を計算するものである1。数式で表現すると次の通り。ここで\(\bar{x}\)はデータベクトル\(x\)の算術平均。 \[v = \sqrt{\frac{1}{n}\sum_{i=1}^n (x_i - \bar{x})^2}\]

dat <- c(10, 13, 15, 12, 14) # データ
M <- mean(dat) # 平均
dev <- dat - M # 平均偏差
pow <- dev^2 # 平均偏差の2乗
variance <- mean(pow) # 平均偏差の2乗の平均が分散
standardDev <- sqrt(variance) # 分散の正の平方根が標準偏差

ここでは,標準偏差オブジェクトstandardDevを作るまでに平均オブジェクトM,平均偏差ベクトルdev,その2乗したものpow,分散varianceと4つものオブジェクトを作って答えに到達している。また,作られるオブジェクトが左側にあり,その右側にどのような演算をしているかが記述されているため,頭の中では「オブジェクトを作る,次の計算で」と読んでいったことだろう。

パイプ演算子はこの思考の流れをそのまま具現化する。パイプ演算子は%>%と書き,左側の演算結果をパイプ演算子の右側に来る関数の第一引数として右側に渡す役目をする。これを踏まえて上のスクリプトを書き直してみよう。ちなみにパイプ演算子はショートカットCtrl(Cmd)+Shift+Mで入力できる。

dat <- c(10, 13, 15, 12, 14)
standardDev <- dat %>%
  {
    . - mean(.)
  } %>%
  {
    .^2
  } %>%
  mean() %>%
  sqrt()

ここでピリオド(.)は,前の関数から引き継いだもの(プレイスホルダー)であり,二行目は{dat - mean(dat)},すなわち平均偏差の計算を意味している。それを次のパイプで二乗し,平均し,平方根を取っている。平均や平方根を取るときにプレイスホルダーが明示されていないのは,引き受けた引数がどこに入るかが明らかなので省略しているからである。

この例に見るように,パイプ演算子を使うと,データ\(\to\)平均偏差$\(2乗\)\(平均\)$平方根,という計算の流れと,スクリプトの流れが一致しているため,理解しやすくなったのではないだろうか。

また,ここでの計算は,次のように書くこともできる。

standardDev <- sqrt(mean((dat - mean(dat))^2))

この書き方は,関数の中に関数がある入れ子状態になっており,\(y = h(g(f(x)))\)のような形式である。これも対応するカッコの内側から読み解いていく必要があり,思考の流れと逆転しているため理解が難しい。パイプ演算子を使うと,x %>% f() %>% g() %>% h() -> yのように記述できるため,苦労せずに読むことができる。

以下はこのパイプ演算子を使った記述で進めていくので,この表記法(およびショートカット)に慣れていこう。

3.3 課題1.パイプ演算子

  • sqrt,mean関数がbaseパッケージに含まれることをヘルプで確認してみましょう。どこを見れば良いでしょうか。filter,lag関数はどうでしょうか。
  • tidyverseパッケージを読み込んだことで,filter関数はdplyrパッケージのものが優先されることになりました。dplyrパッケージのfilter関数をヘルプで見てみましょう。
  • 上書きされる前のstatsパッケージのfilter関数に関するヘルプを見てみましょう。
  • 先ほどのデータを使って,平均値絶対偏差(MeanAD)および中央絶対偏差(MAD)をパイプ演算子を使って算出してみましょう。なお平均値絶対偏差,中央値絶対偏差は次のように定義されるものです。また絶対値を計算するR関数はabsです。

\[MeanAD = \frac{1}{n}\sum_{i=1}^n|x_i - \bar{x}|\] \[MAD = median(|x_1-median(x)|,\cdots,|x_n-median(x)|)\]

3.4 列選択と行選択

ここからはtidyverseを使ったより具体的なデータハンドリングについて言及する。 まずは特定の列および行だけを抜き出すことを考える。データの一部にのみ処理を加えたい場合に重宝する。

3.4.1 列選択

列選択はselect関数である。これはtidyverseパッケージ内のdplyrパッケージに含まれている。 select関数はMASSパッケージなど,他のパッケージに同名の関数が含まれることが多いので注意が必要である。

例示のために,Rがデフォルトで持つサンプルデータ,irisを用いる。なお,irisデータは150行あるので,以下ではデータセットの冒頭を表示するhead関数を用いているが,演習の際にはheadを用いなくても良い。

# irisデータの確認
iris %>% head()
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa
# 一部の変数を抜き出す
iris %>%
  select(Sepal.Length, Species) %>%
  head()
  Sepal.Length Species
1          5.1  setosa
2          4.9  setosa
3          4.7  setosa
4          4.6  setosa
5          5.0  setosa
6          5.4  setosa

逆に,一部の変数を除外したい場合はマイナスをつける。

iris %>%
  select(-Species) %>%
  head()
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1          5.1         3.5          1.4         0.2
2          4.9         3.0          1.4         0.2
3          4.7         3.2          1.3         0.2
4          4.6         3.1          1.5         0.2
5          5.0         3.6          1.4         0.2
6          5.4         3.9          1.7         0.4
# 複数変数の除外
iris %>%
  select(-c(Petal.Length, Petal.Width)) %>%
  head()
  Sepal.Length Sepal.Width Species
1          5.1         3.5  setosa
2          4.9         3.0  setosa
3          4.7         3.2  setosa
4          4.6         3.1  setosa
5          5.0         3.6  setosa
6          5.4         3.9  setosa

これだけでも便利だが,select関数は適用時に抜き出す条件を指定してやればよく,そのために便利な以下のような関数がある。

  • starts_with()
  • ends_with()
  • contains()
  • matches()

使用例を以下に挙げる。

# starts_withで特定の文字から始まる変数を抜き出す
iris %>%
  select(starts_with("Petal")) %>%
  head()
  Petal.Length Petal.Width
1          1.4         0.2
2          1.4         0.2
3          1.3         0.2
4          1.5         0.2
5          1.4         0.2
6          1.7         0.4
# ends_withで特定の文字で終わる変数を抜き出す
iris %>%
  select(ends_with("Length")) %>%
  head()
  Sepal.Length Petal.Length
1          5.1          1.4
2          4.9          1.4
3          4.7          1.3
4          4.6          1.5
5          5.0          1.4
6          5.4          1.7
# containsで部分一致する変数を取り出す
iris %>%
  select(contains("etal")) %>%
  head()
  Petal.Length Petal.Width
1          1.4         0.2
2          1.4         0.2
3          1.3         0.2
4          1.5         0.2
5          1.4         0.2
6          1.7         0.4
# matchesで正規表現による選択をする
iris %>%
  select(matches(".t.")) %>%
  head()
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1          5.1         3.5          1.4         0.2
2          4.9         3.0          1.4         0.2
3          4.7         3.2          1.3         0.2
4          4.6         3.1          1.5         0.2
5          5.0         3.6          1.4         0.2
6          5.4         3.9          1.7         0.4

ここで触れた正規表現とは,文字列を特定するためのパターンを指定する表記ルールであり,R言語に限らずプログラミング言語一般で用いられるものである。書誌検索などでも用いられることがあり,任意の文字列や先頭・末尾の語などを記号(メタ文字)を使って表現するものである。詳しくは正規表現で検索すると良い(たとえばこちらのサイトなどがわかりやすい。)

3.4.2 行選択

一般にデータフレームは列に変数が並んでいるので,select関数による列選択とは変数選択とも言える。 これに対し,行方向にはオブザベーションが並んでいるので,行選択とはオブザベーション(ケース,個体)の選択である。行選択にはdplyrfilter関数を使う。

# Sepal.Length変数が6以上のケースを抜き出す
iris %>%
  filter(Sepal.Length > 6) %>%
  head()
  Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1          7.0         3.2          4.7         1.4 versicolor
2          6.4         3.2          4.5         1.5 versicolor
3          6.9         3.1          4.9         1.5 versicolor
4          6.5         2.8          4.6         1.5 versicolor
5          6.3         3.3          4.7         1.6 versicolor
6          6.6         2.9          4.6         1.3 versicolor
# 特定の種別だけ抜き出す
iris %>%
  filter(Species == "versicolor") %>%
  head()
  Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1          7.0         3.2          4.7         1.4 versicolor
2          6.4         3.2          4.5         1.5 versicolor
3          6.9         3.1          4.9         1.5 versicolor
4          5.5         2.3          4.0         1.3 versicolor
5          6.5         2.8          4.6         1.5 versicolor
6          5.7         2.8          4.5         1.3 versicolor
# 複数指定の例
iris %>%
  filter(Species != "versicolor", Sepal.Length > 6) %>%
  head()
  Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
1          6.3         3.3          6.0         2.5 virginica
2          7.1         3.0          5.9         2.1 virginica
3          6.3         2.9          5.6         1.8 virginica
4          6.5         3.0          5.8         2.2 virginica
5          7.6         3.0          6.6         2.1 virginica
6          7.3         2.9          6.3         1.8 virginica

ここで==とあるのは一致しているかどうかの判別をするための演算子である。=ひとつだと「オブジェクトへの代入」と同じになるので,判別条件の時には重ねて表記する。同様に,!=とあるのはnot equal,つまり不一致のとき真になる演算子である。

3.5 変数を作る・再割り当てする

既存の変数から別の変数を作る,あるいは値の再割り当ては,データハンドリング時に最もよく行う操作のひとつである。たとえば連続変数をある値を境に「高群・低群」というカテゴリカルな変数に作り変えたり,単位を変換するために線形変換したりすることがあるだろう。このように,変数を操作するときに「既存の変数を加工して特徴量を作りだす」というときの操作は,基本的にdplyrmutate関数を用いる。次の例をみてみよう。

mutate(iris, Twice = Sepal.Length * 2) %>% head()
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Twice
1          5.1         3.5          1.4         0.2  setosa  10.2
2          4.9         3.0          1.4         0.2  setosa   9.8
3          4.7         3.2          1.3         0.2  setosa   9.4
4          4.6         3.1          1.5         0.2  setosa   9.2
5          5.0         3.6          1.4         0.2  setosa  10.0
6          5.4         3.9          1.7         0.4  setosa  10.8

新しくTwice変数ができたのが確認できるだろう。この関数はパイプ演算子の中で使うことができる(というかその方が主な使い方である)。次の例は,Sepal.Length変数を高群と低群の2群に分けるものである。

iris %>%
  select(Sepal.Length) %>%
  mutate(Sepal.HL = ifelse(Sepal.Length > mean(Sepal.Length), 1, 2)) %>%
  mutate(Sepal.HL = factor(Sepal.HL, label = c("High", "Low"))) %>%
  head()
  Sepal.Length Sepal.HL
1          5.1      Low
2          4.9      Low
3          4.7      Low
4          4.6      Low
5          5.0      Low
6          5.4      Low

ここでもちいたifelse関数は,if(条件判断,真のときの処理,偽のときの処理)という形でもちいる条件分岐関数であり,ここでは平均より大きければ1,そうでなければ2を返すようになっている。mutate関数でこの結果をSepal.HL変数に代入(生成)し,次のmutate関数では今作ったSepal.HL変数をFactor型に変換して,その結果をまたSepal.HL変数に代入(上書き)している。このように,変数の生成先を生成元と同じにしておくと上書きされるため,たとえば変数の型変換(文字型から数値型へ,数値型からFactor型へ,など)にも用いることができる。

3.6 課題2.select, filter, mutate

  • Baseball.csvを読み込んで、データフレームdfに代入しましょう。
  • dfには複数の変数が含まれています。変数名の一覧はnames関数で確認できます。dfオブジェクトに含まれる変数名を確認しましょう。
  • dfには多くの変数がありますが、必要なのは年度(Year)、選手名(Name)、所属球団(team)、身長(height)、体重(weight)、年俸(salary)、守備位置(position)だけです。これらの変数だけを選択して、df2オブジェクトを作成しましょう。
  • df2には数年分のデータが含まれています。2020年度のデータだけを分析したいので、選別してみましょう。
  • 同じく、2020年度阪神タイガースに関するデータだけを選別してみましょう。
  • 同じく、2020年度阪神タイガース以外のデータセットはどのようにして選別できるでしょうか。
  • 選手の身体的特徴を表すBMI変数を作成しましょう。なお、BMIは体重(kg)を身長(m)の二乗で除したものです。変数heightの単位がcmであることに注意しましょう。
  • 投手と野手を区別する新しい変数position2を作成しましょう。これはFactor型にします。なお、野手は投手でないもの、すなわち内野手、外野手、捕手のいずれかです。
  • 日本プロ野球界は大きく分けてセリーグ(Central League)とパリーグ(Pacific League)に分かれています。セリーグに所属する球団はGiants, Carp, Tigers, Swallows, Dragons, DeNAであり、パ・リーグはそれ以外です。df2を加工して、所属するリーグの変数Leagueを作成しましょう。この変数もFactor型にしておきましょう。
  • 変数Yearは語尾に「年度」という文字が入っているため文字列型になっています。実際に使うときは不便なので、「年度」という文字を除外し、数値型変数に変換しましょう。

3.7 ロング型とワイド型

ここまでみてきたデータは行列の2次元に,ケース\(\times\)変数の形で格納されていた。この形式は,人間が見て管理するときにわかりやすい形式をしているが,計算機にとっては必ずしもそうではない。たとえば「神エクセル」と揶揄されることがあるように,稀に表計算ソフトを方眼紙ソフトあるいは原稿用紙ソフトと勘違いしたかのような使い方がなされる場合がある。人間にとってはわかりやすい(見て把握しやすい)かもしれないが,計算機にとって構造が把握できないため,データ解析に不向きである。巷には,こうした分析しにくい電子データがまだまだたくさん存在する。

これをうけて2020年12月,総務省により機械判読可能なデータの表記方法の統一ルールが策定された(総務省 2020)。それには次のようなチェック項目が含まれている。

  • ファイル形式はExcelかCSVとなっているか
  • 1セル1データとなっているか
  • 数値データは数値属性とし,文字列を含まないこと
  • セルの結合をしていないか
  • スペースや改行等で体裁を整えていないか
  • 項目名を省略していないか
  • 数式を使用している場合は,数値データに修正しているか
  • オブジェクトを使用していないか
  • データの単位を記載しているか
  • 機種依存文字を使用していないか
  • データが分断されていないか
  • 1シートに複数の表が掲載されていないか

データの入力の基本は,1行に1ケースの情報が入っている,過不足のない1つのデータセットを作ることといえるだろう。

同様に,計算機にとって分析しやすいデータの形について,Hadley (2014) が提唱したのが整然データ(Tidy Data)という考え方である。整然データとは,次の4つの特徴を持ったデータ形式のことを指す。

  • 個々の変数(variable)が1つの列(column)をなす。
  • 個々の観測(observation)が1つの行(row)をなす。
  • 個々の観測の構成単位の類型(type of observational unit)が1つの表(table)をなす。
  • 個々の値(value)が1つのセル(cell)をなす。

この形式のデータであれば,計算機が変数と値の対応構造を把握しやすく,分析しやすいデータになる。データハンドリングの目的は,混乱している雑多なデータを,利用しやすい整然データの形に整えることであると言っても過言ではない。 さて,ここでよく考えてみると,変数名も一つの変数だと考えることに気づく。一般に,行列型のデータは次のような書式になっている。

ワイド型データ
午前 午後 夕方 深夜
東京
大阪
福岡

ここで,たとえば大阪の夕方の天気を見ようとすると「晴れ」であることは明らかだが,この時の視線の動きは大阪行の,夕方列,という参照の仕方である。言い方を変えると,大阪・夕方の「晴れ」を参照するときに,行と列の両方のラベルを参照する必要がある。

ここで同じデータを次のように並べ替えてみよう。

ロング型データ
地域 時間帯 天候
東京 午前
東京 午後
東京 夕方
東京 深夜
大阪 午前
大阪 午後
大阪 夕方
大阪 深夜
福岡 午前
福岡 午後
福岡 夕方
福岡 深夜

このデータが表す情報は同じだが,大阪・夕方の条件を絞り込むことは行選択だけでよく,計算機にとって使いやすい。この形式をロング型データ,あるいは「縦持ち」データという。これに対して前者の形式をワイド型データ,あるいは「横持ち」データという。

ロング型データにする利点のひとつは,欠損値の扱いである。ワイド型データで欠損値が含まれる場合,その行あるいは列全体を削除するのは無駄が多く,かと言って行・列両方を特定するのは技術的にも面倒である。これに対しロング型データの場合は,当該行を絞り込んで削除するだけで良い。

tidyverseには(正確にはtidyrには),このようなロング型データ,ワイド型データの変換関数が用意されている。 実例とともに見てみよう。まずはワイド型データをロング型に変換するpivot_longerである。

iris %>% pivot_longer(-Species)
# A tibble: 600 × 3
   Species name         value
   <fct>   <chr>        <dbl>
 1 setosa  Sepal.Length   5.1
 2 setosa  Sepal.Width    3.5
 3 setosa  Petal.Length   1.4
 4 setosa  Petal.Width    0.2
 5 setosa  Sepal.Length   4.9
 6 setosa  Sepal.Width    3  
 7 setosa  Petal.Length   1.4
 8 setosa  Petal.Width    0.2
 9 setosa  Sepal.Length   4.7
10 setosa  Sepal.Width    3.2
# ℹ 590 more rows

ここでは元のirisデータについて,Speciesセルを軸として,それ以外の変数名と値をname,valueに割り当てて縦持ちにしている。

逆に,ロング型のデータをワイド型に持ち替えるには,pivot_widerを使う。 実例は以下の通りである。

iris %>%
  select(-Species) %>%
  rowid_to_column("ID") %>%
  pivot_longer(-ID) %>%
  pivot_wider(id_cols = ID, names_from = name, values_from = value)
# A tibble: 150 × 5
      ID Sepal.Length Sepal.Width Petal.Length Petal.Width
   <int>        <dbl>       <dbl>        <dbl>       <dbl>
 1     1          5.1         3.5          1.4         0.2
 2     2          4.9         3            1.4         0.2
 3     3          4.7         3.2          1.3         0.2
 4     4          4.6         3.1          1.5         0.2
 5     5          5           3.6          1.4         0.2
 6     6          5.4         3.9          1.7         0.4
 7     7          4.6         3.4          1.4         0.3
 8     8          5           3.4          1.5         0.2
 9     9          4.4         2.9          1.4         0.2
10    10          4.9         3.1          1.5         0.1
# ℹ 140 more rows

今回はSpecies変数を除外し,別途ID変数として行番号を変数に付与した。この行番号をキーに,変数名はnames列から,その値はvalue列から持ってくることでロング型をワイド型に変えている2

3.8 グループ化と要約統計量

データをロング型にすることで,変数やケースの絞り込みが容易になる。その上で,ある群ごとに要約した統計量を算出したい場合は,group_by変数によるグループ化と,summariseあるいはreframeがある。実例を通して確認しよう。

iris %>% group_by(Species)
# A tibble: 150 × 5
# Groups:   Species [3]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl> <fct>  
 1          5.1         3.5          1.4         0.2 setosa 
 2          4.9         3            1.4         0.2 setosa 
 3          4.7         3.2          1.3         0.2 setosa 
 4          4.6         3.1          1.5         0.2 setosa 
 5          5           3.6          1.4         0.2 setosa 
 6          5.4         3.9          1.7         0.4 setosa 
 7          4.6         3.4          1.4         0.3 setosa 
 8          5           3.4          1.5         0.2 setosa 
 9          4.4         2.9          1.4         0.2 setosa 
10          4.9         3.1          1.5         0.1 setosa 
# ℹ 140 more rows

上のコードでは,一見したところ表示されたデータに違いがないように見えるが,出力時にSpecies[3]と表示されていることがわかる。ここで,Species変数の3水準で群分けされていることが示されている。これを踏まえて,summariseしてみよう。

iris %>%
  group_by(Species) %>%
  summarise(
    n = n(),
    Mean = mean(Sepal.Length),
    Max = max(Sepal.Length),
    IQR = IQR(Sepal.Length)
  )
# A tibble: 3 × 5
  Species        n  Mean   Max   IQR
  <fct>      <int> <dbl> <dbl> <dbl>
1 setosa        50  5.01   5.8 0.400
2 versicolor    50  5.94   7   0.7  
3 virginica     50  6.59   7.9 0.675

ここではケース数(n),平均(mean),最大値(max),四分位範囲(IQR)3を算出した。

また,ここではSepal.Lengthについてのみ算出したが,他の数値型変数に対しても同様の計算がしたい場合は,across関数を使うことができる。

iris %>%
  group_by(Species) %>%
  summarise(across(
    c(Sepal.Length, Sepal.Width, Petal.Length),
    ~ mean(.x)
  ))
# A tibble: 3 × 4
  Species    Sepal.Length Sepal.Width Petal.Length
  <fct>             <dbl>       <dbl>        <dbl>
1 setosa             5.01        3.43         1.46
2 versicolor         5.94        2.77         4.26
3 virginica          6.59        2.97         5.55

ここで,~mean(.x)の書き方について言及しておく。チルダ(tilda,~)で始まるこの式を,Rでは特にラムダ関数とかラムダ式と呼ぶ。これはこの場で使う即席関数の作り方である。別の方法として,正式に関数を作る関数functionを使って次のように書くこともできる。

iris %>%
  group_by(Species) %>%
  summarise(across(
    c(Sepal.Length, Sepal.Width, Petal.Length),
    function(x) {
      mean(x)
    }
  ))
# A tibble: 3 × 4
  Species    Sepal.Length Sepal.Width Petal.Length
  <fct>             <dbl>       <dbl>        <dbl>
1 setosa             5.01        3.43         1.46
2 versicolor         5.94        2.77         4.26
3 virginica          6.59        2.97         5.55

ラムダ関数や自作関数の作り方については,後ほどあらためて触れるとして,ここでは複数の変数に関数をあてがう方法を確認して置いて欲しい。across関数で変数を選ぶ際は,select関数の時に紹介したstarts_withなども利用できる。次に示す例は,複数の変数を選択し,かつ,複数の関数を適用する例である。複数の関数を適用するために,ラムダ関数をリストで与えることができる。

iris %>%
  group_by(Species) %>%
  summarise(across(starts_with("Sepal"),
    .fns = list(
      M = ~ mean(.x),
      Q1 = ~ quantile(.x, 0.25),
      Q3 = ~ quantile(.x, 0.75)
    )
  ))
# A tibble: 3 × 7
  Species    Sepal.Length_M Sepal.Length_Q1 Sepal.Length_Q3 Sepal.Width_M
  <fct>               <dbl>           <dbl>           <dbl>         <dbl>
1 setosa               5.01            4.8              5.2          3.43
2 versicolor           5.94            5.6              6.3          2.77
3 virginica            6.59            6.22             6.9          2.97
# ℹ 2 more variables: Sepal.Width_Q1 <dbl>, Sepal.Width_Q3 <dbl>

3.9 課題3.データの整形

  • 上で作ったdf2オブジェクトを利用します。環境にdf2オブジェクトが残っていない場合は、もう一度上の課題に戻って作り直しておきましょう。
  • 年度(Year)でグルーピングし、年度ごとの登録選手数(データの数)、平均年俸を見てみましょう。
  • 年度(Year)とチーム(team)でグルーピングし、同じく年度ごとの登録選手数(データの数)、平均年俸を見てみましょう。
  • 続いて、一行に1年度分、列に各チームと変数の組み合わせが入った、ワイド型データを作りたいと思います。pivot_widerを使って上のオブジェクトをワイド型にしてみましょう。
  • ワイド型になったデータを、Year変数をキーにしてpivot_longerでロング型データに変えてみましょう。

  1. もちろんsd(dat) の一行で済む話だが,ここでは説明のために各ステップを書き下している。もっとも,sd関数で計算されるのは\(n-1\)で割った不偏分散の平方根であり,標本標準偏差とは異なるものである。↩︎

  2. Species変数を除外したのは,これをキーにしたロング型をワイド型に変えることができない(Speciesは3水準しかない)からで,個体を識別するIDが別途必要だったからである。Species情報が欠落することになったが,これはロング型データのvalue列がchar型とdouble型の両方を同時に持てないからである。この問題を回避するためには,Factor型のデータをas.numeric()関数で数値化することなどが考えられる。↩︎

  3. 四分位範囲(Inter Quantaile Range)とは,データを値の順に4分割した時の上位1/4の値から,上位3/4の値を引いた範囲である。↩︎