以前、データフレームの列(=変数)に対して、追加・名前変更・要約などの操作方法についてまとめました。
最近は列の操作でacross関数を使うことが多くなってきたので、ここらへんで学び直しておこうと思います。
- 必要なパッケージとデータの準備
- across関数で操作の対象となる変数を指定する
- across( )をsummarise( )と一緒に使う
- across( )をmutate( )と一緒に使う
- rename_with( )で列名を変更する
- おわりに
- 参考資料
必要なパッケージとデータの準備
まずはtidyverseパッケージを読み込みます。
library(tidyverse)
今回はggplotでお馴染みのdiamondsデータを使います。diamondsデータに馴染みがなくても、データセットであることがすぐにわかるように、データ名はdata
に変更しておきます。
> data <- diamonds > data # A tibble: 53,940 × 10 carat cut color clarity depth table price x y z <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl> 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43 2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31 4 0.29 Premium I VS2 62.4 58 334 4.2 4.23 2.63 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75 6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48 7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47 8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49 10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39 # … with 53,930 more rows
across関数で操作の対象となる変数を指定する
across関数に条件を渡すことで、操作の対象となる変数を指定することができます。across( )は単体では使えません。列に対する操作を行うsummarise( )やmutate( )と合わせて使います。
across( )では次のような引数を指定します。
.cols
:操作の対象としたい変数。後述のように色々な方法で条件指定可能。デフォルトは.cols = everthing( )
、つまり全ての変数。.fns
:適用したい関数。= mean
:単に関数名を与えるだけでもいい= ~ mean(.x, na.rm = TRUE)
:ラムダ式で書いてもいい= list(mean = mean, n_miss = ~ sum(is.na(.x))
:関数のリストも受け付けてくれる
.names
:出力される列の名前の付け方。対象とした列名{.col}
や適用した関数{.fn}
を使って動的に命名できます。
実例は後ほど。
変数名で条件指定する
select( )の使い方でも説明しましたが、変数名の文字列パターンで指定することができます。
starts_with( )
:変数名が〜で始まるものends_with( )
:変数名が〜で終わるものcontains( )
:変数名に〜
例えば、across(starts_with("a"))
とすれば、変数名が"a"から始まる列を対象にすることができます。
検査値を示すものには"lab_"と接頭辞をつけるなど、上手く変数命名のルールを決めると便利ですね。
変数の型で条件指定する
数値型と文字列型では適用できる関数が異なるので、変数の型で条件指定できると便利です。
例えば、across(where(is.numeric))
とすれば、数値型の変数のみを対象にすることができます。
複数の条件を組み合わせて指定する
例えば、across(where(is.numeric) & !starts_with("a"))
のように、複数の条件を組み合わせて変数を指定することも可能です。この例だと「数値型で、かつ先頭が"a"で始まらない変数」を指定していることになります。
across( )をsummarise( )と一緒に使う
across( )を使わずに、x
,y
,z
を平均値で要約しようとすると、
data %>% summarise(mean_x = mean(x, rm.na=TRUE), mean_y = mean(y, rm.na=TRUE), mean_z = mean(z, rm.na=TRUE))
のように、要約したい変数の個数だけ書き下さないといけなかった。
across( )を使えば、
fun <- fuction(x){ data %>% summarize(across(x:z, ~mean(.x, na.rm=TRUE), .names="mean_{.col}"))
とするだけで、以下の同じ結果が得られる。((ここでは対象となる変数をx:z
のように、データフレームでの列の範囲として指定したが、列がどの順序で並んでいるかはちゃんとわからないのであまり使わないかも))
# A tibble: 1 × 3 mean_x mean_y mean_z <dbl> <dbl> <dbl> 1 5.73 5.73 3.54
across( )をmutate( )と一緒に使う
summarise( )では要約した値を別のデータフレームとして表示しましたが、元のデータフレームに新たな変数として追加したいときはmutate( )を使います。
例えば、変数x
,y
,z
の平均と標準偏差を、新たな変数mean_x
, sd_x
, mean_y
,...として追加する場合は、
mean_sd <- list( mean = ~mean(.x, na.rm = TRUE), sd = ~sd(.x, na.rm = TRUE) ) data %>% select(x:z) %>% mutate(across(x:z, mean_sd, .names="{.fn}_{.col}"))
と書きます。*1
ここでは平均と標準偏差を求める関数をラムダ式で定義し、mean_sd
という名前のリストとしてまとめて適用しています。
# A tibble: 53,940 × 9 x y z mean_x sd_x mean_y sd_y mean_z sd_z <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> 1 3.95 3.98 2.43 5.73 1.12 5.73 1.14 3.54 0.706 2 3.89 3.84 2.31 5.73 1.12 5.73 1.14 3.54 0.706 3 4.05 4.07 2.31 5.73 1.12 5.73 1.14 3.54 0.706 4 4.2 4.23 2.63 5.73 1.12 5.73 1.14 3.54 0.706 5 4.34 4.35 2.75 5.73 1.12 5.73 1.14 3.54 0.706 6 3.94 3.96 2.48 5.73 1.12 5.73 1.14 3.54 0.706 7 3.95 3.98 2.47 5.73 1.12 5.73 1.14 3.54 0.706 8 4.07 4.11 2.53 5.73 1.12 5.73 1.14 3.54 0.706 9 3.87 3.78 2.49 5.73 1.12 5.73 1.14 3.54 0.706 10 4 4.05 2.39 5.73 1.12 5.73 1.14 3.54 0.706 # … with 53,930 more rows
当たり前ですが、平均・標準偏差は全ての観測値に共通の値が与えられていますね。
もう少し実用的に、例えばcut
のグループ毎の平均・標準偏差を追加したい場合は次のようにgroup_by( )を挟んでやります(mean_sd
は先程の例と同じです)。
data %>% select(cut,x:z) %>% group_by(cut) %>% mutate(across(x:z, mean_sd, .names="{.fn}_{.col}"))
出力です。次はcut
の水準毎に異なった平均・標準偏差が追加されました。
# A tibble: 53,940 × 10 # Groups: cut [5] cut x y z mean_x sd_x mean_y sd_y mean_z sd_z <ord> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> 1 Ideal 3.95 3.98 2.43 5.51 1.06 5.52 1.07 3.40 0.658 2 Premium 3.89 3.84 2.31 5.97 1.19 5.94 1.26 3.65 0.731 3 Good 4.05 4.07 2.31 5.84 1.06 5.85 1.05 3.64 0.655 4 Premium 4.2 4.23 2.63 5.97 1.19 5.94 1.26 3.65 0.731 5 Good 4.34 4.35 2.75 5.84 1.06 5.85 1.05 3.64 0.655 6 Very Good 3.94 3.96 2.48 5.74 1.10 5.77 1.10 3.56 0.730 7 Very Good 3.95 3.98 2.47 5.74 1.10 5.77 1.10 3.56 0.730 8 Very Good 4.07 4.11 2.53 5.74 1.10 5.77 1.10 3.56 0.730 9 Fair 3.87 3.78 2.49 6.25 0.964 6.18 0.956 3.98 0.652 10 Very Good 4 4.05 2.39 5.74 1.10 5.77 1.10 3.56 0.730 # … with 53,930 more rows
rename_with( )で列名を変更する
across( )はrename( )と一緒には使えないようです。代わりにrename_with( )と使うようですが、rename_with( )には対象とする変数を指定する引数があるので、across( )を入れ子にする必要はありません(多分)。*2
rename_with( )でもacross( )と同様、.cols
と.fn
を指定しますが、引数の順序が異なるので注意が必要です。例えば、数値型変数に"num_"という接頭辞をつけたい場合は次のように書きます(後で使うので今回は別名で保存しました)。
d <- data %>% rename_with(~paste0("num_", .x), where(is.numeric))
> d # A tibble: 53,940 × 10 num_carat cut color clarity num_depth num_table num_price num_x num_y num_z <dbl> <ord> <ord> <ord> <dbl> <dbl> <int> <dbl> <dbl> <dbl> 1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43 2 0.21 Prem… E SI1 59.8 61 326 3.89 3.84 2.31 3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31 4 0.29 Prem… I VS2 62.4 58 334 4.2 4.23 2.63 5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75 6 0.24 Very… J VVS2 62.8 57 336 3.94 3.96 2.48 7 0.24 Very… I VVS1 62.3 57 336 3.95 3.98 2.47 8 0.26 Very… H SI1 61.9 55 337 4.07 4.11 2.53 9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49 10 0.23 Very… H VS1 59.4 61 338 4 4.05 2.39 # … with 53,930 more rows
特定の文字列を置換して変数名を変更したい状況の方が多いような気がします。gsub( )を使うと次のように書けます。
d %>% rename_with(~gsub("num", "cont", .x), where(is.numeric))
gsub( )の引数は以下のとおりです。
pattern
:検索したい文字列パターンreplacement
:マッチした場合にこの文字列に置き換えたいx
:検索・置換の対象となる文字列(ここではラムダ式で書いているので.x
となっている)
おわりに
- 自由度が高くなると自分の応用力の無さが際立ちます...
- ペーストとココナッツミルクでグリーンカレーを作ったら、ネコたちが酔っ払ってしまいました。マタタビに似たものでも入っているんでしょうか。
参考資料
- 本家の資料。
- 新旧の記載方を対応させながら説明されています。
- glueを使った表現の説明が簡潔で分かりやすかったです。