dplyr速習
dplyrパッケージは主にデータフレームを対象としたデータ加工のための関数を提供します。データ加工のための操作にはいくつかの種類がありますが、dplyrパッケージではこれらの操作を個別の関数として提供しています。dplyrパッケージの関数を組み合わせて利用することで、データ加工の処理を効率的に行うことができます。
dplyrが対象とするデータ操作の種類としては、次のようなものがあります。
データの選択・絞り込み… select()
, filter()
データの並べ替え… arrange()
データの加工… mutate()
データの集約… summarise()
, count()
データの結合… *_join()
, bind_*()
dplyrはこのほか、データフレーム全体ではなく、データの値、すなわちベクトルに対する操作を行うcase_when()
関数、if_else()
関数、na_if()
関数など、いくつも備わっています。
データ操作を行う上でdplyrパッケージの関数は直感的にわかりやすいため、dplyrの関数をデータベース上のデータに適用したり、data.tableクラスのオブジェクトに対して適用可能にするパッケージも用意されています。
データの用意
dplyrパッケージを使って行う処理を説明するために、南極に生育するペンギン個体の各部位の大きさについてのデータを利用します。このデータ(penguins
)は次のパッケージの読み込みと同時に利用できるようになります。
penguins |>
slice_head (n = 6 )
Adelie
Torgersen
39.1
18.7
181
3750
male
2007
Adelie
Torgersen
39.5
17.4
186
3800
female
2007
Adelie
Torgersen
40.3
18.0
195
3250
female
2007
Adelie
Torgersen
NA
NA
NA
NA
NA
2007
Adelie
Torgersen
36.7
19.3
193
3450
female
2007
Adelie
Torgersen
39.3
20.6
190
3650
male
2007
ペンギンデータに対して、以降の処理の説明を簡略化するために、データの件数と変数を減らします。
set.seed (123 )
penguins_small <-
penguins |>
slice_sample (n = 6 ) |>
select (species, island, starts_with ("bill" ), sex)
penguins_small
Gentoo
Biscoe
44.5
14.3
NA
Adelie
Torgersen
38.6
21.2
male
Gentoo
Biscoe
45.3
13.7
female
Chinstrap
Dream
52.8
20.0
male
Adelie
Torgersen
37.3
20.5
male
Chinstrap
Dream
43.2
16.6
female
それではdplyrの関数を使って、このデータを加工していきましょう。
複数の列への処理
across
共通の処理を複数の列へ適用する場合、次のように変数 = 関数(変数)
の関係を個別に指定する書き方は効率が悪いです。across()
関数を使うことで、このような複数列への一括処理が容易に行えるようになります。
penguins_small |>
# stringr::str_to_upper()はアルファベット文字列を大文字に変換します
mutate (species = str_to_upper (species),
sex = str_to_upper (sex),
.keep = "none" )
GENTOO
NA
ADELIE
MALE
GENTOO
FEMALE
CHINSTRAP
MALE
ADELIE
MALE
CHINSTRAP
FEMALE
across()
は関数を適用する列を.cols引数で指定し、適用する関数名を.fnsに渡して実行します。 次のコードはstr_to_upper()
関数を2つの変数に個別に実行していたものをacross()
関数を使った書き方に直したものです。
penguins_small |>
mutate (across (.cols = c ("species" , "sex" ),
.fns = str_to_upper),
.keep = "none" )
GENTOO
NA
ADELIE
MALE
GENTOO
FEMALE
CHINSTRAP
MALE
ADELIE
MALE
CHINSTRAP
FEMALE
結果は出力しませんが、以下のように書くこともできます。
penguins_small |>
mutate (across (c (species, sex),
str_to_upper),
.keep = "none" )
penguins_small |>
mutate (across (c (species, sex),
~ str_to_upper (.x)),
.keep = "none" )
penguins_small |>
mutate (across (c (species, sex),
function (x) str_to_upper (x)),
.keep = "none" )
penguins_small |>
mutate (across (c (species, sex),
\(x) str_to_upper (x)),
.keep = "none" )
*_all()
, *_at()
, *_if()
で終わる関数を使って複数列への処理が可能ですが、これらの関数は2023年8月現在 superseded
扱いとなっています。
penguins_small |>
transmute_at (c ("species" , "sex" ),
.funs = str_to_upper)
GENTOO
NA
ADELIE
MALE
GENTOO
FEMALE
CHINSTRAP
MALE
ADELIE
MALE
CHINSTRAP
FEMALE
この書き方に慣れている人は、関数を指定する引数名がacross()
関数と異なる点に注意が必要です。 具体的にはacross(.fns = )
で関数を指定することになります。
また、*_all()
, *_at()
, *_if()
で利用可能だった、処理関数の第一引数以外の引数の指定方法はacross()
関数では利用できません。 このあと説明する、across()
関数での引数の指定方法を覚えましょう。
penguins_small |>
transmute_at (c ("species" , "sex" ),
.funs = str_sub, start = 1 , end = 3 )
Gen
NA
Ade
mal
Gen
fem
Chi
mal
Ade
mal
Chi
fem
across()
関数では、処理を実行する関数(上記の例ではstr_to_upper()
関数)の第一引数に.cols引数で指定した変数の値が渡されます。値以外や、第一引数以外に値を渡す必要がある場合などは、次のような書き方で渡される値を明示的に指定します。 )
# species及びsexの値は .x としてstr_sub()の第一引数に渡される
penguins_small |>
mutate (across (c (species, sex), ~ str_sub (.x, 1 , 2 )), .keep = "none" )
Ge
NA
Ad
ma
Ge
fe
Ch
ma
Ad
ma
Ch
fe
# species及びsexの値は 無名関数の引数xとしてstr_sub()の第一引数に渡される
penguins_small |>
mutate (across (c (species, sex), function (x) str_sub (x, 1 , 2 )), .keep = "none" )
Ge
NA
Ad
ma
Ge
fe
Ch
ma
Ad
ma
Ch
fe
# R 4.1.0から利用可能な無名関数の表記も使える
penguins_small |>
mutate (across (c (species, sex), \(x) str_sub (x, 1 , 2 )), .keep = "none" )
Ge
NA
Ad
ma
Ge
fe
Ch
ma
Ad
ma
Ch
fe
penguins_small |>
mutate (across (c (species, sex), list (~ str_sub (.x, 1 , 2 ))), .keep = "none" )
Ge
NA
Ad
ma
Ge
fe
Ch
ma
Ad
ma
Ch
fe
across()
関数を使った複数列への処理では、通常、加工後の列名は元の列名と一致します。 例えば、ペンギンデータのspeciesごとに数値変数の平均値を求める以下の処理とその結果を見てみましょう。
penguins_small |>
group_by (species) |>
summarise (across (where (is.numeric), \(x) mean (x, na.rm = TRUE )))
Adelie
37.95
20.85
Chinstrap
48.00
18.30
Gentoo
44.90
14.00
この挙動が問題となることがあります。それは操作対象の変数に複数の関数を適用する場合です。 次の例はペンギンデータのspeciesごとに、数値変数の平均値と標準偏差を求める処理ですが、計算された平均値が元の変数名に格納されるため、標準偏差の計算結果を正しく求めることができなくなっています。
penguins_small |>
group_by (species) |>
summarise (across (where (is.numeric), \(x) mean (x, na.rm = TRUE )),
across (where (is.numeric), \(x) sd (x, na.rm = TRUE )))
Adelie
NA
NA
Chinstrap
NA
NA
Gentoo
NA
NA
こうした場合、across()
関数の.names引数が役に立ちます。 以下の例では、平均値を求めるacross()
関数と標準偏差を求めるacross()
関数それぞれに.names引数を指定しています。注目してほしいのは"{.col}_"
の部分です。ここにはテンプレートとして元の変数名が使われます。その他、任意の文字列を新たに用意される変数名として利用することができます。
penguins_small |>
group_by (species) |>
summarise (across (where (is.numeric), \(x) mean (x, na.rm = TRUE ), .names = "{.col}_mean" ),
across (where (is.numeric), \(x) sd (x, na.rm = TRUE ), .names = "{.col}_sd" ))
Adelie
37.95
20.85
0.9192388
0.4949747
NA
NA
Chinstrap
48.00
18.30
6.7882251
2.4041631
NA
NA
Gentoo
44.90
14.00
0.5656854
0.4242641
NA
NA
penguins_small |>
group_by (species) |>
summarise (across (where (is.numeric),
list (mean = ~ mean (.x, na.rm = TRUE ),
sd = ~ sd (.x, na.rm = TRUE ))))
Adelie
37.95
0.9192388
20.85
0.4949747
Chinstrap
48.00
6.7882251
18.30
2.4041631
Gentoo
44.90
0.5656854
14.00
0.4242641
penguins_small |>
group_by (species) |>
summarise (across (where (is.numeric), \(x) mean (x, na.rm = TRUE ),
.names = "{.col}_mean" ))
Adelie
37.95
20.85
Chinstrap
48.00
18.30
Gentoo
44.90
14.00
tidyselect::where
列の選択を行うselect()
関数にも、複数列への処理として、mutate_if()
関数やsummarise_at()
関数と同様にselect_if()
関数等が利用できました。対象の列が一致する条件式を選択する、というもので、次のような記法を実行するのでした。
# 因子型の列を選択
penguins_small |>
select_if (is.factor)
Gentoo
Biscoe
NA
Adelie
Torgersen
male
Gentoo
Biscoe
female
Chinstrap
Dream
male
Adelie
Torgersen
male
Chinstrap
Dream
female
ところがselect_if()
やselect_at()
関数はやはりsuperseded扱いとなっています。代わりに、where()
関数を使った条件式での列の指定が可能となっています。
penguins_small |>
select (where (is.factor))
Gentoo
Biscoe
NA
Adelie
Torgersen
male
Gentoo
Biscoe
female
Chinstrap
Dream
male
Adelie
Torgersen
male
Chinstrap
Dream
female
rename_with
# すべての列を対象に、列名に対してstr_to_upper()関数が適用される
penguins_small |>
rename_with (str_to_upper) |>
colnames ()
[1] "SPECIES" "ISLAND" "BILL_LENGTH_MM" "BILL_DEPTH_MM"
[5] "SEX"
# 列名に対してstr_to_upper()関数が適用されるが、適用範囲を因子型の列だけに制限する
penguins_small |>
rename_with (str_to_upper, where (is.factor)) |>
colnames ()
[1] "SPECIES" "ISLAND" "bill_length_mm" "bill_depth_mm"
[5] "SEX"
グループ
先ほど、「種ごとに処理を適用する」例を示しましたが、ここで使われた種、すなわちグループの指定方法に変更があります。
penguins_small |>
group_by (species) |>
summarise (n = n ())
Adelie
2
Chinstrap
2
Gentoo
2
従来は、処理を行う前にgroup_by()
関数で明示的にグループ対象の変数を指定する必要がありました。しかし現在は処理を行うmutate()
やsummarise()
関数の中で.by引数によってグループ対象の変数を指定可能です。
penguins_small |>
summarise (n = n (), .by = species)
Gentoo
2
Adelie
2
Chinstrap
2
penguins_small |>
# speciesごとにbill_length_mmの最大値を計算した値が格納される
mutate (bill_length_max = max (bill_length_mm, na.rm = TRUE ),
.by = species) |>
# speciesごとにデータを1件ずつ取り出す
# bill_lenght_maxの値がspeciesごとに異なることがわかる
slice (1 , .by = species)
Gentoo
Biscoe
44.5
14.3
NA
45.3
Adelie
Torgersen
38.6
21.2
male
38.6
Chinstrap
Dream
52.8
20.0
male
52.8
penguins_small |>
reframe (n = n (), .by = species)
Gentoo
2
Adelie
2
Chinstrap
2
また、複数のグループを指定した後の処理でグループを維持し続けるかを.groups引数で選択することが可能です。
# group_by()で与えた最初のグループは解除されるが、以降のグループは維持される
# グループ化の処理に対して、どのような振る舞いをするかを.groups引数で指定しないと
# 警告文が出力される
penguins_small |>
group_by (species, island, sex) |>
summarise (mean_bl = mean (bill_length_mm, na.rm = TRUE ))
`summarise()` has grouped output by 'species', 'island'. You can override using
the `.groups` argument.
Adelie
Torgersen
male
37.95
Chinstrap
Dream
female
43.20
Chinstrap
Dream
male
52.80
Gentoo
Biscoe
female
45.30
Gentoo
Biscoe
NA
44.50
.groups引数に”drop”を与えた場合、すべてのグループが解除されます。一方、グループを継続するには”keep”を与えます。
penguins_small |>
group_by (species, island, sex) |>
summarise (mean_bl = mean (bill_length_mm, na.rm = TRUE ),
.groups = "drop" )
Adelie
Torgersen
male
37.95
Chinstrap
Dream
female
43.20
Chinstrap
Dream
male
52.80
Gentoo
Biscoe
female
45.30
Gentoo
Biscoe
NA
44.50
penguins_small |>
group_by (species, island, sex) |>
summarise (mean_bl = mean (bill_length_mm, na.rm = TRUE ),
.groups = "keep" )
Adelie
Torgersen
male
37.95
Chinstrap
Dream
female
43.20
Chinstrap
Dream
male
52.80
Gentoo
Biscoe
female
45.30
Gentoo
Biscoe
NA
44.50
データ結合
dplyrパッケージには次の表に示す通り、柔軟なデータ結合関数が提供されています。
inner_join()
キーとして指定した変数から、二つのデータフレームに共通して存在するレコードを結合して返す
left_join()
キーとして指定した変数から、左(第一引数)のデータフレームに存在するレコードを結合して返す
right_join()
キーとして指定した変数から、右(第二引数)のデータフレームに存在するレコードを結合して返す
full_join()
キーとして指定した変数から、二つのデータフレームのいずれかに存在するレコードを結合して返す
semi_join()
(絞り込み)
anti_join()
キーとして指定した変数から、左(第一引数)のデータフレームに存在しないレコードを結合して返す
cross_join()
二つのデータフレームのすべての組み合わせを結合して返す
実行は以下のように、対象のデータのほか、キーとなる変数をby引数で指定します。
inner_join(データ1, データ2, by = "キーとなる変数")
このby引数の指定方法として、join_by()
関数を利用することもできます。この関数により、より柔軟な結合条件の指定が実現します。
penguins_ja <-
tibble (
species = c ("Adelie" , "Gentoo" , "Chinstrap" ),
name = c ("アデリーペンギン" , "ジェンツーペンギン" , "ヒゲペンギン" ),
redlist = c ("NT" , "LC" , NA_character_ ))
penguins_small |>
distinct (species, island) |>
left_join (penguins_ja, by = join_by (species))
Gentoo
Biscoe
ジェンツーペンギン
LC
Adelie
Torgersen
アデリーペンギン
NT
Chinstrap
Dream
ヒゲペンギン
NA
penguins_small |>
distinct (species, island) |>
left_join (penguins_ja, by = c ("species" ))
Gentoo
Biscoe
ジェンツーペンギン
LC
Adelie
Torgersen
アデリーペンギン
NT
Chinstrap
Dream
ヒゲペンギン
NA
penguins_ja2 <-
tibble (
name = c ("Adelie" , "Gentoo" , "Chinstrap" ),
jp_name = c ("アデリーペンギン" , "ジェンツーペンギン" , "ヒゲペンギン" ),
redlist = c ("NT" , "LC" , NA_character_ ))
penguins_small |>
distinct (species, island) |>
left_join (penguins_ja2, by = join_by (species == name))
Gentoo
Biscoe
ジェンツーペンギン
LC
Adelie
Torgersen
アデリーペンギン
NT
Chinstrap
Dream
ヒゲペンギン
NA
penguins_small |>
distinct (species, island) |>
left_join (penguins_ja2, by = c ("species" = "name" ))
Gentoo
Biscoe
ジェンツーペンギン
LC
Adelie
Torgersen
アデリーペンギン
NT
Chinstrap
Dream
ヒゲペンギン
NA
penguins_name <-
tibble (
name = c ("adeliae" , "Gentoo" , "antarctica" ),
redlist = c ("NT" , "LC" , NA_character_ ))
キー変数の項目が一致しないものがあるとエラーを発生させます。
penguins_small |>
distinct (species, island) |>
left_join (penguins_name, by = join_by (species == name), unmatched = "error" )
Error in `left_join()`:
! Each row of `y` must be matched by `x`.
ℹ Row 1 of `y` was not matched.
キー変数の項目が一致しないものがあると、そのレコードを除外します。これはデフォルトの挙動です。
penguins_small |>
distinct (species, island) |>
left_join (penguins_name, by = join_by (species == name), unmatched = "drop" )
Gentoo
Biscoe
LC
Adelie
Torgersen
NA
Chinstrap
Dream
NA
もう一つのオプション
df_pgid <-
tibble (id = seq.int (4 ),
species = c ("Adelie" , "Chinstrap" , "Gentoo" , "Chinstrap" ))
df_pgid
1
Adelie
2
Chinstrap
3
Gentoo
4
Chinstrap
df_pgname <-
tibble (
species = c ("Adelie" , "Chinstrap" , "Chinstrap" ),
name = c ("アデリーペンギン" , "ヒゲペンギン" , "ナンキョクペンギン" ))
df_pgname
Adelie
アデリーペンギン
Chinstrap
ヒゲペンギン
Chinstrap
ナンキョクペンギン
# multiple = "all"の状態
df_pgid |>
left_join (
df_pgname,
by = join_by (species))
Warning in left_join(df_pgid, df_pgname, by = join_by(species)): Detected an unexpected many-to-many relationship between `x` and `y`.
ℹ Row 2 of `x` matches multiple rows in `y`.
ℹ Row 2 of `y` matches multiple rows in `x`.
ℹ If a many-to-many relationship is expected, set `relationship =
"many-to-many"` to silence this warning.
1
Adelie
アデリーペンギン
2
Chinstrap
ヒゲペンギン
2
Chinstrap
ナンキョクペンギン
3
Gentoo
NA
4
Chinstrap
ヒゲペンギン
4
Chinstrap
ナンキョクペンギン
結合するデータの件数が多い場合、問題がどこかに混ざる危険があります。 新しい*_join()
関数では、このような問題を回避するために、結合先のレコードが複数ある場合、デフォルトではエラーを返却します。データや目的に応じて、ユーザー自身が複数の値への対処法を決定することができます。
# 結合先が複数ある状態で、最初のレコードのみを結合する
# multiple = "last" にすると最後のレコードのみを結合する
df_pgid |>
left_join (
df_pgname,
by = join_by (species),
multiple = "first" )
1
Adelie
アデリーペンギン
2
Chinstrap
ヒゲペンギン
3
Gentoo
NA
4
Chinstrap
ヒゲペンギン
df_pgid |>
left_join (
df_pgname,
by = join_by (species),
multiple = "all" ,
relationship = "many-to-many" )
1
Adelie
アデリーペンギン
2
Chinstrap
ヒゲペンギン
2
Chinstrap
ナンキョクペンギン
3
Gentoo
NA
4
Chinstrap
ヒゲペンギン
4
Chinstrap
ナンキョクペンギン
非等価結合、ローリング結合等はスキップ…
penguins_name <-
tibble (
name = c ("Adelie" , "Gentoo" , "Chinstrap" ),
preview_n = c (160 , 121 , 60 ),
redlist = c ("NT" , "LC" , NA_character_ ))
# Adelieはn >= preview_nの条件に合わないので結合から除外される
penguins_small |>
count (species) |>
left_join (penguins_name, by = join_by (species == name, n >= preview_n))
Adelie
2
NA
NA
Chinstrap
2
NA
NA
Gentoo
2
NA
NA