Rでデータの前処理をしていて知ったので備忘も兼ねて。
やりたかったこと
元のデータの横に数値カラムのグループ平均値を新しい列として付与したかったです。
愚直に書くと以下のようになります。
df = as_tibble(iris)
df %>%
group_by(Species) %>%
mutate(Sepal.Length_mean = mean(Sepal.Length),
Sepal.Width_mean = mean(Sepal.Width),
Petal.Length_mean = mean(Petal.Length),
Petal.Width_mean = mean(Petal.Width)
) %>%
ungroup()
作りたいデータ
# A tibble: 150 × 9
Sepal.Length Sepal.Width Petal.Length Petal.Width Species Sepal.Length_mean Sepal.Width_mean Petal.Length_mean Petal.Width_mean
<dbl> <dbl> <dbl> <dbl> <fct> <dbl> <dbl> <dbl> <dbl>
1 5.1 3.5 1.4 0.2 setosa 5.01 3.43 1.46 0.246
2 4.9 3 1.4 0.2 setosa 5.01 3.43 1.46 0.246
3 4.7 3.2 1.3 0.2 setosa 5.01 3.43 1.46 0.246
4 4.6 3.1 1.5 0.2 setosa 5.01 3.43 1.46 0.246
5 5 3.6 1.4 0.2 setosa 5.01 3.43 1.46 0.246
元々のやり方
さすがに愚直な書き方では列数増えたら爆死するなど厳しいので、tidyverseを駆使して一応は解決していました。
mutate_at
複数のカラムに同時に関数(今回であればmean)を適用したい場合はmutate_atで行えます。
df = as_tibble(iris)
df %>%
group_by(Species) %>%
mutate_at(.vars = vars(matches("^(Sepal|Petal).*")), .funs = list(~ mean(.))) %>%
ungroup()
ですが、これだとカラム名を変えることが出来ず同じカラム名で値が更新されてしまいます。
これだとカラム名が変わらない
# A tibble: 150 × 5
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
<dbl> <dbl> <dbl> <dbl> <fct>
1 5.01 3.43 1.46 0.246 setosa
2 5.01 3.43 1.46 0.246 setosa
3 5.01 3.43 1.46 0.246 setosa
4 5.01 3.43 1.46 0.246 setosa
5 5.01 3.43 1.46 0.246 setosa
rename_at
仕方ないので、複数のカラム名を同時に変更できるrename_atを組み合わせました。
df = as_tibble(iris)
df %>%
group_by(Species) %>%
mutate_at(.vars = vars(matches("^(Sepal|Petal).*")), .funs = list(~ mean(.))) %>%
ungroup() %>%
rename_at(.vars = vars(matches("^(Sepal|Petal).*")), .funs = list(~ paste0(., "_mean")))
カラム名も変更できている
# A tibble: 150 × 5
Sepal.Length_mean Sepal.Width_mean Petal.Length_mean Petal.Width_mean Species
<dbl> <dbl> <dbl> <dbl> <fct>
1 5.01 3.43 1.46 0.246 setosa
2 5.01 3.43 1.46 0.246 setosa
3 5.01 3.43 1.46 0.246 setosa
4 5.01 3.43 1.46 0.246 setosa
5 5.01 3.43 1.46 0.246 setosa ```
これを元のデータと結合することで、作りたいデータは一応できます。
今回知ったやり方
across
tidyverseのver1.0.0から実装された関数だそうです。
使い方はmutate_at/rename_atが分かっていれば非常に簡単で、
mutate(across(.cols = <処理したいカラム>, .fns = (~ <適用したい関数>), .names = <出力列に付けたい名前>))
.names引数では{.col}と指定することで選択した列名を使うことが可能なので、この関数を使うことで一発です。
df %>%
group_by(Species) %>%
mutate(across(.cols = matches("^(Sepal|Petal).*"), .fns = list(~ mean(., na.rm = T)), .names = "{.col}_mean")) %>>%
ungroup()
# A tibble: 150 × 9
Sepal.Length Sepal.Width Petal.Length Petal.Width Species Sepal.Length_mean Sepal.Width_mean Petal.Length_mean Petal.Width_mean
<dbl> <dbl> <dbl> <dbl> <fct> <dbl> <dbl> <dbl> <dbl>
1 5.1 3.5 1.4 0.2 setosa 5.01 3.43 1.46 0.246
2 4.9 3 1.4 0.2 setosa 5.01 3.43 1.46 0.246
3 4.7 3.2 1.3 0.2 setosa 5.01 3.43 1.46 0.246
4 4.6 3.1 1.5 0.2 setosa 5.01 3.43 1.46 0.246
5 5 3.6 1.4 0.2 setosa 5.01 3.43 1.46 0.246
おわりに
データの前処理を行ううえでtidyverseは痒い所に手が届く神パッケージです。
神もどんどん進化しています。ときどき、公式ページ(↓)を覗くなどして自分の知識をアップデートしないとなと思いました。