data frameの列名を直接指定できる関数

non-standard evaluationを使っていこう
R
公開

2023年4月15日

tidyverseのパッケージを使ったことのある方は、引数の独特さをご存知だと思います。data frame(あるいはtibble)の中の変数(列名)をダブルクォーテーションで囲わなくても指定できてしまう不思議な仕様です。例えばdatasets::CO2の中のPlantTypeという列のみを持つdata frameを取り出したい場合は、通常は次のように書きます。

CO2[c("Plant", "Type")]

これをdplyrパッケージのselect()関数で書くと次のようになります。

library(dplyr)

CO2 |> select(c(Plant, Type))

c()の中に列名が直接書かれていて不思議な感じがしますね。このような書き方をtidyverseではdata maskingと呼んでいます。

これはtidyverse独自の記法かと思いきや、実はRのbaseパッケージにも同じようなことができる関数があります。(実際、data maskingはbasesubset()関数を参考にしたとされています)

どのようなものがあるか、ここでご紹介します。

subset

subset()はdata frameの一部を抜き出す関数ですが、引数の中でdata frameの列名を直接指定することができます。先ほどのCO2データの例は次のように書けます。

subset(CO2, select=c(Plant, Type))

パイプ演算子を使えばdplyr::select()と同じような書き方ができます。

CO2 |> subset(select=c(Plant, Type))

連続する整数を作る”:“のように、列の範囲を”:“で指定することもできます。自由。

subset(CO2, select=Plant:Treatment)

列だけでなく、行の抜き出し条件でも列名をダブルクォーテーションなしで書くことができます。

subset(CO2, conc < 180, select=Plant:Treatment)

このような列名の指定のしかたはsubsetヘルプで”non-standard evaluation”であるという説明があります。長いコードを一度に実行するような場合(プログラムを組む場合)には推奨されないようなことも書かれていますが、そのような場合でもコードの分かりやすさが向上するメリットがあると思います。

transform

transform()はdata frameに列を追加したり、既存の列を変更したりする関数です。例えばdatasets::irisデータの列Sepal.Lengthに1を加えたり、新しくSepalという列を追加したりするには通常は次のように書きます。

iris_local <- datasets::iris  # 編集用に別変数に保存

# Sepal.Lengthに1を加える(2種類の書き方)
iris_local$Sepal.Length <- iris_local$Sepal.Length + 1
iris_local[["Sepal.Length"]] <- iris_local[["Sepal.Length"]] + 1

# 新しくSepalという列を追加する(2種類の書き方)
iris_local$Sepal <- iris_local$Sepal.Length + iris_local$Sepal.Width
iris_local[["Sepal"]] <- iris_local[["Sepal.Length"]] + iris_local[["Sepal.Width"]]

これをtransform()で書くとこうなります。

iris_local <- transform(datasets::iris,
                        Sepal.Length = Sepal.Length + 1,
                        Sepal = Sepal.Length + Sepal.Width)

Sepal.Lengthなどを直接書けることが分かりますね。ちなみにdplyrパッケージではmutate()が近い機能を持つ関数です。

with

with()を使うと、任意のコードをnon-standard evaluationで書くことができます。

例えば、datasets::irisデータのSepal.Lengthのうち6.0以上の値を取り出すには、通常は次のように書きます。

iris$Sepal.Length[iris$Sepal.Length >= 6]
iris[iris$Sepal.Length >= 6, "Sepal.Length"]

これをwith()を使って書くとこうなります。

with(iris, Sepal.Length[Sepal.Length >= 6])

irisというdata frameの名前を最初に1度書くだけで、列名を直接指定できるようになるということですね。と言ってもこれだけ見るとそんなにありがたみがないように思われるかも知れませんが、実はlapply()関数やR4.1.0で追加されたパイプ演算子などと組み合わせることで、tidyverse的なコードを書けるとても便利な関数になります。それについてはまた別途書きたいと思います。

数式(formula)

数式は変数間の関係を表すもので、モデル式を与えるときによく使われます。数式を使う場面ではdata frameの列が変数とみなされるので、列名を変数として直接記述できます。

例えば、irisデータのSepal.LengthSepal.Widthで説明する線形回帰モデルを作るにはこう書きます。

lm(Sepal.Length ~ Sepal.Width, data = iris)

列名が直接出て来ているのが分かりますね。

しかし数式はデータハンドリングの話とはあまり関係ないのでは・・・?と思われた方もいるでしょう。本来の使い方ならばそのとおりなのですが、実は関数の中には「数式」の形を引数として受け取れるものがあり、うまく活用するとnon-standard evaluationのようなコードが書けます。特にsplit()関数は有用です。

attach

attach()の引数にdata frameを指定すると、それ以降は列名を直接指定できるようになります。

attach(iris)

Sepal.Length[1:6]

attach()にdata frameを渡すと、Search Pathにそのdata frameか追加されます。それによって列名をそれぞれ変数宣言したかのように扱えるというわけですね。

Search Pathはsearch()関数で確認することができます。Search Pathは通常はパッケージの環境(environment)などを整理するために使われます。先ほど追加したdata frameをSearch Pathから削除するにはdetach()を使います。

detach(iris)

data frameの列名の呼び出しのためにわざわざSearch Pathにattach()するのはあまりよい方法とは言えません。実際にRのパッケージを自分で新たに開発したりしてSearch Pathの使い方を理解すると、その辺りが分かってくると思います。

まとめ

tidyverseのような変数の指定の仕方が、実はbaseパッケージでもけっこう行えることがお分かりいただけたかと思います。私はこれらを利用して、tidyverseを使わずにtidyverseのようなスマートな記述ができる方法をいろいろ模索しています。