5 新しい変数(列)の作成:mutate
5.1 使用データ
-
psychTools
パッケージに入っている国際パーソナリティ項目プールからの2800名分のデータ- 名前が似ている
psychotools
パッケージもあるので注意 - 質問項目が25問あり,5つの構成概念(ここでは因子という)に対応する項目への回答を足し合わせたスコアを計算する
- 性,教育歴,年齢の変数もあり
- 名前が似ている
- 項目に対し想定される因子(因子名の頭文字が変数名と対応)
- Agree A1からA5
- Conscientious C1からC5
- Extraversion E1からE5
- Neuroticism N1からN5
- Openness O1からO5
- 回答選択肢
- 1 Very Inaccurate まったくあてはまらない
- 2 Moderately Inaccurate あてはまらない
- 3 Slightly Inaccurate ややあてはまらない
- 4 Slightly Accurate ややあてはまる
- 5 Moderately Accurate あてはまる
- 6 Very Accurate 非常にあてはまる
5.2 基本
- データフレームに新しい列を計算して追加するまたは置き換える関数
-
mutate()
の中に新しく作成する変数名を入れ,=
でつないで計算式を入れる - ここでは,まず変数A1の平均値(全ケース同じ値が入る)を計算し,個々のケースの値の差分を新しく列として追加する例を示す
df_bfi |>
select(A1) |> # A1のみを残す
mutate(
mean_a1 = mean(A1, na.rm = TRUE), # A1の平均値を作成(NAは除外)
dif_a1_mean = A1 - mean_a1) # 各個体のA1と平均値の差分を計算
## # A tibble: 2,800 × 3
## A1 mean_a1 dif_a1_mean
## <int> <dbl> <dbl>
## 1 2 2.41 -0.413
## 2 2 2.41 -0.413
## 3 5 2.41 2.59
## # ℹ 2,797 more rows
- mean_a1列にはA1の平均値がすべて同じ値で入る(平均値だけの計算がしたければ6章を参照)
- dif_a1_mean列は,A1列からmean_a1列を引いた値が入る
5.2.1 新しく作成した列の位置を指定する
-
.before = 1
と引数を指定することで,先頭に持ってこれる-
.after =
とあわせて,列名を指定することで出現場所を指定できる
-
df_bfi |>
mutate(
mean_a1 = mean(A1, na.rm = TRUE), # A1の平均値を作成(NAは除外)
dif_a1_mean = A1 - mean_a1,
.before = 1)
## # A tibble: 2,800 × 30
## mean_a1 dif_a1_mean A1 A2 A3 A4 A5 C1 C2
## <dbl> <dbl> <int> <int> <int> <int> <int> <int> <int>
## 1 2.41 -0.413 2 4 3 4 4 2 3
## 2 2.41 -0.413 2 4 5 2 5 5 4
## 3 2.41 2.59 5 4 5 4 4 4 5
## # ℹ 2,797 more rows
## # ℹ 21 more variables: C3 <int>, C4 <int>, C5 <int>, E1 <int>,
## # E2 <int>, E3 <int>, E4 <int>, E5 <int>, N1 <int>, N2 <int>,
## # N3 <int>, N4 <int>, N5 <int>, O1 <int>, O2 <int>, O3 <int>,
## # O4 <int>, O5 <int>, gender <int>, education <int>, age <int>
- 引数
.keep = "used"
で作成に関係した列だけにする
df_bfi |>
mutate(
mean_a1 = mean(A1, na.rm = TRUE), # A1の平均値を作成(NAは除外)
dif_a1_mean = A1 - mean_a1,
.keep = "used")
## # A tibble: 2,800 × 3
## A1 mean_a1 dif_a1_mean
## <int> <dbl> <dbl>
## 1 2 2.41 -0.413
## 2 2 2.41 -0.413
## 3 5 2.41 2.59
## # ℹ 2,797 more rows
5.3 変数の型の変換
-
1.6.1.1で触れたように,変数には型の情報が伴い,統計解析やデータ加工の際に適切な型を求められることがあるため理解が必要
- 小数も扱う数値(double-precision)
<dbl>
- 整数
<int>
- 文字
<chr>
- 因子
<fct>
- 小数も扱う数値(double-precision)
- 変数の型の確認は色々方法があるが,tibble形式のデータフレームなら
select()
でOK- 以下の出力の変数名の下にその変数の型が表示される
df_bfi |>
select(gender, education)
## # A tibble: 2,800 × 2
## gender education
## <int> <int>
## 1 1 NA
## 2 2 NA
## 3 2 NA
## # ℹ 2,797 more rows
- tibble形式でない普通のデータフレームでも,最後に
glimpse()
で出力することで型を確認可能
## Rows: 2,800
## Columns: 2
## $ gender <int> 1, 2, 2, 2, 1, 2, 1, 1, 1, 2, 1, 1, 2, 1, 1, …
## $ education <int> NA, NA, NA, NA, NA, 3, NA, 2, 1, NA, 1, NA, N…
-
glimpse()
の結果はデータフレームの表示が行列入れ替わっており,変数の情報が行ごとに出力される - gender, education列が
<int>
になっているので整数型になっている
5.3.1 型の変換
- ここでは,2つの数値型変数gender, educationを因子型に変換する例を示す
- それぞれ
factor()
で因子型に変換
df_bfi |>
select(gender, education) |>
mutate(gender = factor(gender),
education = factor(education))
## # A tibble: 2,800 × 2
## gender education
## <fct> <fct>
## 1 1 <NA>
## 2 2 <NA>
## 3 2 <NA>
## # ℹ 2,797 more rows
- gender, education列が
<fct>
になっているので整数型になっている
5.3.2 【効率化】複数の変数に対し一度の指定で実行
- 変換したい変数が大量にあるときは上記の方法では大変
-
across()
を使うと,指定した変数に対して同じ内容の処理なら 1回 ですむようになる7 -
across()
の第一引数に変数ベクトルを入れ,次に適用する関数(ここではfactor())を入れる
df_bfi |>
mutate(across(c(gender, education),
factor)) |>
select(gender, education) # 結果表示のため冗長だが変わった変数だけselect
## # A tibble: 2,800 × 2
## gender education
## <fct> <fct>
## 1 1 <NA>
## 2 2 <NA>
## 3 2 <NA>
## # ℹ 2,797 more rows
- この場合,元の変数が上書きされるので,新しい列はできていない
5.4 across()の特徴
- 変数の指定に2.3で解説したヘルパー関数が使える
df_bfi |>
mutate(across(starts_with("n"),
factor)) |>
select(starts_with("n")) # 結果表示のため
## # A tibble: 2,800 × 5
## N1 N2 N3 N4 N5
## <fct> <fct> <fct> <fct> <fct>
## 1 3 4 2 2 3
## 2 3 3 3 5 5
## 3 4 5 4 2 3
## # ℹ 2,797 more rows
- 同じく2.2.3で解説した文字型の変数名も指定に使える
vars <- c("N1", "N2", "N3", "N4", "N5")
df_bfi |>
mutate(across(all_of(vars),
factor)) |>
select(starts_with("n")) # 結果表示のため
## # A tibble: 2,800 × 5
## N1 N2 N3 N4 N5
## <fct> <fct> <fct> <fct> <fct>
## 1 3 4 2 2 3
## 2 3 3 3 5 5
## 3 4 5 4 2 3
## # ℹ 2,797 more rows
5.4.1 【重要知識】新しい変数名にして追加
ここはこの後色々なところで出てくる方法のため理解しておきたい
適用する関数をリストにする(
list()
に入れる)ことで,変数名を変更して追加できるlist()
に入れるときはこれまでと異なる書き方が必要になる-
関数名の前に
\(x)
が必要。x
部分は他の文字でもよいし,引数がいくつも必要なら,複数の文字を入れられる- list内の「関数
()
」内にx
(上記同様,\()
内と一致していれば他の文字でもよい)が必要。このx
にacross()
の第一引数に指定した変数が入っていくという意味 - purrr 1.0.0では,従来の
~
と.x
を使った記法から,無名関数の\(x)
とx
を使う書き方に移行した(参考:purrr 1.0.0 Docmentation)(詳しくは1.4.2.2参照)
- list内の「関数
例:genderとeducationの型を因子型に変換し,変換前後で変わっているかどうか確認
df_bfi |>
mutate(across(c(gender, education),
list(f = \(x) factor(x)))) |>
select(matches("gender|education"))
## # A tibble: 2,800 × 4
## gender education gender_f education_f
## <int> <int> <fct> <fct>
## 1 1 NA 1 <NA>
## 2 2 NA 2 <NA>
## 3 2 NA 2 <NA>
## # ℹ 2,797 more rows
- (参考)purrr 1.0.0より前のバージョンでの説明
# purrr 1.0.0より前のバージョンでの書き方
# df_bfi |>
# mutate(across(c(gender, education),
# list(f = ~factor(.x)))) |>
# select(matches("gender|education"))
- 因子型に変換した変数の末尾に_fがつく
- selectで
matches()
を使っているのは,新しく接尾辞が増えた変数を追加しているので入力を少なく効率的に表示させるため
5.5 合計点の作成
- 変数の四則演算の式を入れれば合計得点として計算された列をデータフレームに追加できる
## # A tibble: 2,800 × 6
## N1 N2 N3 N4 N5 neuroticism
## <int> <int> <int> <int> <int> <int>
## 1 3 4 2 2 3 14
## 2 3 3 3 5 5 19
## 3 4 5 4 2 3 18
## # ℹ 2,797 more rows
- 別解として,変数の逆転項目を反映させた後に, 5.9 で異なるやり方で合計した例を解説する
- 項目数が多い場合などはこちらの方が効率化できる場合も
5.5.1 足し上げる変数に欠損値があるとどうなるか
- 欠損値については4.2.1参照
- 合計得点の計算の場合,対象となる変数の内1つでもNAがあれば合計点もNAとなる
## # A tibble: 106 × 6
## N1 N2 N3 N4 N5 neuroticism
## <int> <int> <int> <int> <int> <int>
## 1 4 5 3 2 NA NA
## 2 NA 2 1 2 2 NA
## 3 1 2 1 2 NA NA
## # ℹ 103 more rows
5.6 変数の値を数値から文字列に変える
- 一度因子型に変換してから
forcats
パッケージのfct_recode()
関数を使うと簡単-
fct_recode()
の第一引数に変換元の変数を入れそのあとに置き換えルールをnew = old
で入れていく
-
- 例:genderの値1,2をそれぞれmale, femaleという文字に置き換える
- ちゃんと変換の対応がついているかどうかを
dplyr
パッケージのcount()
関数で確認- 適切に変換されていなければ,1 = male, 2 = female以外の組み合わせも発生するため
-
count()
の強みは,出力がデータフレームで出てくる点なので,結果が扱いやすい
df_bfi |>
mutate(gender = factor(gender),
gender_c = fct_recode(gender,
male = "1",
female = "2")) |>
count(gender, gender_c)
## # A tibble: 2 × 3
## gender gender_c n
## <fct> <fct> <int>
## 1 1 male 919
## 2 2 female 1881
5.7 連番からIDの作成
-
dplyr::row_number()
で行番号からIDを作成
df_bfi_n |>
mutate(id = row_number())
## # A tibble: 2,800 × 7
## N1 N2 N3 N4 N5 neuroticism id
## <int> <int> <int> <int> <int> <int> <int>
## 1 3 4 2 2 3 14 1
## 2 3 3 3 5 5 19 2
## 3 4 5 4 2 3 18 3
## # ℹ 2,797 more rows
5.7.1 【別解】行の名前を直接変数化
- 実は
mutate()
を使わなくてもできて,データの最初に持ってこれる便利関数がある -
tibble::rowid_to_column()
-
var =
で連番として追加する変数名を指定
-
df_bfi_n |>
rowid_to_column(var = "id")
## # A tibble: 2,800 × 7
## id N1 N2 N3 N4 N5 neuroticism
## <int> <int> <int> <int> <int> <int> <int>
## 1 1 3 4 2 2 3 14
## 2 2 3 3 3 5 5 19
## 3 3 4 5 4 2 3 18
## # ℹ 2,797 more rows
# この先使わないのでデータフレーム削除
rm(df_bfi_n)
5.8 逆転項目を作る
- 心理尺度などの場合,質問内容に対する回答選択肢の意味が,項目間で逆になるように設定されることがあり,合計点などを作る際に尺度の意味を適切に表すように,取りうる数値の範囲内で値を入れ替える作業が発生することがある
- たとえば,感情の状態を項目を合計してたずねる尺度で,「いつも楽しい」という項目と,「いつも悲しい」という聞き方をしていたら,それぞれの回答を得点化したときに意味が反対になるため,同じ方向になるようにする必要がある
- 例:「1. まったくあてはまらない ←→ 6. 非常にあてはまる」のルールを「いつも悲しい」項目に適用し,1の回答を6に置き換えれば,「いつも楽しい」とポジティブな方向で点数の意味がそろう
5.8.1 逆転:recode
5.8.1.1 逆転項目の確認
- bfiデータの場合,どの項目を逆転する必要があるかを示す情報(
「
-変数名
」で表現)がパッケージに含まれている-
psychTools::bfi.keys
で確認可能
-
- したがって,“-A1”, “-C4”, “-C5”, “-E1”, “-E2”, “-O2”, “-O5”が対象
5.8.1.2 変数1つを逆転
-
dplyr::recode()
を使用 - 対象の変数を
recode()
の第一引数に,入れ替えたい値をold = new
で並べていく - 値の指定で考慮すべき点
- oldの数値は` `で囲む必要がある
- newの数値に
L
がつくのは,型を整数のままにするため。Lなしでも実行できるが,型がdbl
になる
df_bfi |>
mutate(A1_r = recode(A1, `1` = 6L, `2` = 5L, `3` = 4L,
`4` = 3L, `5` = 2L, `6` = 1L)) |>
select(A1, A1_r)
## # A tibble: 2,800 × 2
## A1 A1_r
## <int> <int>
## 1 2 5
## 2 2 5
## 3 5 2
## # ℹ 2,797 more rows
5.8.1.3 変数2つ以上を逆転
- A1と同様に同じ形をくり返し変数名だけ変えていけばできるが,コードが長くなりミスも生じやすくなる
df_bfi |>
mutate(A1_r = recode(A1, `1` = 6L, `2` = 5L, `3` = 4L,
`4` = 3L, `5` = 2L, `6` = 1L),
C4_r = recode(C4, `1` = 6L, `2` = 5L, `3` = 4L,
`4` = 3L, `5` = 2L, `6` = 1L)) |>
select(A1, A1_r, C4, C4_r)
## # A tibble: 2,800 × 4
## A1 A1_r C4 C4_r
## <int> <int> <int> <int>
## 1 2 5 4 3
## 2 2 5 3 4
## 3 5 2 2 5
## # ℹ 2,797 more rows
5.8.1.4 【効率化】 変数2つ以上を逆転
df_bfi |>
mutate(across(c(A1, C4),
list(r = \(x) recode(x, `1` = 6L, `2` = 5L, `3` = 4L,
`4` = 3L, `5` = 2L, `6` = 1L)))) |>
select(A1, A1_r, C4, C4_r)
## # A tibble: 2,800 × 4
## A1 A1_r C4 C4_r
## <int> <int> <int> <int>
## 1 2 5 4 3
## 2 2 5 3 4
## 3 5 2 2 5
## # ℹ 2,797 more rows
5.8.2 【別解】逆転(公式)
- 項目を反転する公式が「(max + min) - 回答値」であることを利用
-
psych::reverse.code()
のhelp参照 - 例:最小値1,最大値4の場合,max + min = 5となり,回答値が2の場合,5 - 2 = 3となり反転された結果となる
-
min <- 1
max <- 6
df_bfi |>
mutate(A1_r = max + min - A1,
C4_r = max + min - C4) |>
select(A1, A1_r, C4, C4_r)
## # A tibble: 2,800 × 4
## A1 A1_r C4 C4_r
## <int> <dbl> <int> <dbl>
## 1 2 5 4 3
## 2 2 5 3 4
## 3 5 2 2 5
## # ℹ 2,797 more rows
5.8.2.1 【効率化】 変数2つ以上を逆転
- 「
\(x)
」の後に計算式がきても動く - ここでは,
max + min - x
のx
にacross()
内に置かれた変数が入っていく
## # A tibble: 2,800 × 4
## A1 A1_r C4 C4_r
## <int> <dbl> <int> <dbl>
## 1 2 5 4 3
## 2 2 5 3 4
## 3 5 2 2 5
## # ℹ 2,797 more rows
5.8.3 【別解】逆転(case_when)
- 条件式の
==
を使って値を変換することもできる。こちらだと条件式の内容や複数の変数の組み合わせなど幅広い応用が可能に
df_bfi |>
mutate(A1_r = case_when(A1 == 1 ~ 6L,
A1 == 2 ~ 5L,
A1 == 3 ~ 4L,
A1 == 4 ~ 3L,
A1 == 5 ~ 2L,
A1 == 6 ~ 1L)) |>
select(A1, A1_r)
## # A tibble: 2,800 × 2
## A1 A1_r
## <int> <int>
## 1 2 5
## 2 2 5
## 3 5 2
## # ℹ 2,797 more rows
5.8.3.1 【効率化】 変数2つ以上を逆転
df_bfi |>
mutate(across(c(A1,C4),
list(r = \(x) case_when(x == 1 ~ 6L,
x == 2 ~ 5L,
x == 3 ~ 4L,
x == 4 ~ 3L,
x == 5 ~ 2L,
x == 6 ~ 1L)))
) |>
select(A1, A1_r, C4, C4_r)
## # A tibble: 2,800 × 4
## A1 A1_r C4 C4_r
## <int> <int> <int> <int>
## 1 2 5 4 3
## 2 2 5 3 4
## 3 5 2 2 5
## # ℹ 2,797 more rows
5.9 【別解】合計点の作成
- 5.5の別解として,ここですべての合計点を作成する
-
base::rowSums()
- データフレームで行の単位で総計するので,行(ケース)ごとに合計点を作成できる
- まず総計の対象となる変数名を2.2.3で解説したように文字ベクトルのオブジェクトに格納して定義していく
-
rowSums()
の中でacross()が使えるので,あとは定義した項目のオブジェクトを指定していくだけ-
dplyr 1.1.0
よりpick()
に変更(tidyverse blogの記事; 適用例)
-
# 合計する項目の定義
Ag <-
df_bfi |>
select(A1_r, A2:A5) |>
names()
Co <-
df_bfi |>
select(C1:C3, C4_r, C5_r) |>
names()
Ex <-
df_bfi |>
select(E1_r, E2_r, E3:E5) |>
names()
Ne <-
df_bfi |>
select(N1:N5) |>
names()
Op <-
df_bfi |>
select(O1, O2_r, O3, O4, O5_r) |>
names()
df_bfi <-
df_bfi |>
mutate(
Agree = rowSums(pick(all_of(Ag))),
Conscientious = rowSums(pick(all_of(Co))),
Extraversion = rowSums(pick(all_of(Ex))),
Neuroticism = rowSums(pick(all_of(Ne))),
Openness = rowSums(pick(all_of(Op)))
)
# dplyr < 1.1.0の例
# df_bfi <-
# df_bfi |>
# mutate(
# Agree = rowSums(across(all_of(Ag))),
# Conscientious = rowSums(across(all_of(Co))),
# Extraversion = rowSums(across(all_of(Ex))),
# Neuroticism = rowSums(across(all_of(Ne))),
# Openness = rowSums(across(all_of(Op)))
# )
5.9.1 【確認】
- 変数の中にNAが入る場合は合計もNAになる。
- rowSums(across(all_of(Op)), na.rm = TRUE)と引数を追加すれば,NAを無視して合計できる
## # A tibble: 2,800 × 6
## A1_r A2 A3 A4 A5 Agree
## <dbl> <int> <int> <int> <int> <dbl>
## 1 5 4 3 4 4 20
## 2 5 4 5 2 5 21
## 3 2 4 5 4 4 19
## # ℹ 2,797 more rows
## # A tibble: 87 × 6
## E1_r E2_r E3 E4 E5 Extraversion
## <dbl> <dbl> <int> <int> <int> <dbl>
## 1 2 4 NA 4 3 NA
## 2 6 6 4 4 NA NA
## 3 2 NA 3 2 3 NA
## # ℹ 84 more rows
-
rowSums()
をbase::rowMeans()
に変えれば平均値も計算できる
5.10 連続変数をカテゴリに区分する
5.10.1 分布の把握
- 変数ageのヒストグラムを描き,分布を確認する
- グラフ作成パッケージ
ggplot2
でどんなグラフが作れるかは著者作成の辞書参照
ggplot(df_bfi) + # ここにデータフレーム
geom_histogram(aes(age)) # aes()の中に対象変数
- さっと中央値だけ見たいのであれば,従来のRの書き方が早い
- 役に立つ場面が多いので,慣れたら従来の書き方を学んでおくとよい
- 「
データフレーム$変数名
」と指定することで変数(実際はベクトルになる)として扱える
# 中央値
median(df_bfi$age)
## [1] 26
# モダンな方法だと少し長くなる
# df_bfi |>
# summarise(median(age))
5.10.2 数値変数の値で2区分のカテゴリ変数を作る
-
dplyr::if_else()
で条件式(TRUEまたはFALSEを返すもの)によって値を2区分する - 構造:
if_else(条件式, TRUEの場合の値, FALSEの場合の値)
- TRUEの場合の値, FALSEの場合の値はそれぞれ文字型を入れることもできる(例:“27歳以上”, “27歳未満”)
- 例:ageの値で27歳以上を1, それ以外を0とした2区分変数を作成する
res_age2 <-
df_bfi |>
mutate(age2 = if_else(age >= 27, 1, 0)) |>
select(age, age2)
res_age2 |> count(age2)
## # A tibble: 2 × 2
## age2 n
## <dbl> <int>
## 1 0 1495
## 2 1 1305
5.10.2.1 確認
-
age >= 27
が1,27未満が0にコーディングされているかfilter()
で限定して確認
## # A tibble: 11 × 3
## age age2 n
## <int> <dbl> <int>
## 1 20 0 212
## 2 21 0 144
## 3 22 0 122
## 4 23 0 138
## 5 24 0 105
## 6 25 0 113
## 7 26 0 99
## 8 27 1 97
## 9 28 1 86
## 10 29 1 78
## 11 30 1 65
- 念のため最初3行(1-3行目)と最後3行(n-2行目からn行目)も確認
-
dplyr::slice()
で1:3行目と最後の3行を表示させる
-
## # A tibble: 6 × 3
## age age2 n
## <int> <dbl> <int>
## 1 3 0 1
## 2 9 0 1
## 3 11 0 3
## 4 72 1 1
## 5 74 1 1
## 6 86 1 1
5.10.2.2 【別解】確認
- 長くなるがこちらの方が覚えやすいかも
res_age2 |>
count(age, age2) |>
slice_head(n = 3)
## # A tibble: 3 × 3
## age age2 n
## <int> <dbl> <int>
## 1 3 0 1
## 2 9 0 1
## 3 11 0 3
res_age2 |>
count(age, age2) |>
slice_tail(n = 3)
## # A tibble: 3 × 3
## age age2 n
## <int> <dbl> <int>
## 1 72 1 1
## 2 74 1 1
## 3 86 1 1
rm(res_age2)
5.10.3 数値変数の値で3区分以上のカテゴリ変数を作る
- 年齢層を10歳区切りでカテゴリ化
res_age6 <-
df_bfi |>
mutate(age6 = case_when(
age < 20 ~ "20歳未満",
age >= 20 & age < 30 ~ "20-29歳",
age >= 30 & age < 40 ~ "30-39歳",
age >= 40 & age < 50 ~ "40-49歳",
age >= 50 & age < 60 ~ "50-59歳",
age >= 60 ~ "60歳以上"
))
# 確認するには以下のコードの最初の2行だけでよいが,
# 出力が長いためランダムに10件抽出しageの昇順にしてある
res_age6 |>
count(age, age6) |>
slice_sample(n = 10) |> # ランダムに10件抽出
arrange(age) # ageをキーに行を昇順にソート
## # A tibble: 10 × 3
## age age6 n
## <int> <chr> <int>
## 1 9 20歳未満 1
## 2 12 20歳未満 28
## 3 17 20歳未満 100
## 4 33 30-39歳 50
## 5 39 30-39歳 50
## 6 40 40-49歳 56
## 7 42 40-49歳 30
## 8 45 40-49歳 28
## 9 61 60歳以上 4
## 10 72 60歳以上 1
rm(res_age6)