niszetの日記

細かい情報を載せていくブログ

select_ifとstr_detectでデータフレームの列名を柔軟に指定して選択する (追記)

もともとは別のことをしようと思ったのですが、1トピックに絞ります。

データフレームの列名をある程度ざっくりと選びたい。

select_atとかでも良いのかもしれませんが、select_ifは条件を指定して選ぶことが出来るので便利ですね。
たとえばこんな感じに。

iris %>% select_if(str_detect(colnames(.), "Petal")) %>% head
#>   Petal.Length Petal.Width
#> 1          1.4         0.2
#> 2          1.4         0.2
#> 3          1.3         0.2
#> 4          1.5         0.2
#> 5          1.4         0.2
#> 6          1.7         0.4

これについては、この書き方でも出来る。

iris %>% select(contains("Sepal")) %>% head()
#>   Sepal.Length Sepal.Width
#> 1          5.1         3.5
#> 2          4.9         3.0
#> 3          4.7         3.2
#> 4          4.6         3.1
#> 5          5.0         3.6
#> 6          5.4         3.9

後者については安定のyutannihilationさんからのアドバイス

をいただきました。

このとき、列名に複数の条件を指定する場合は前者の書き方を使い、str_detectのpatternの方に書くと出来て、こんな感じになります。

iris %>% select_if(str_detect(colnames(.), "(Petal)|(Sepal)")) %>% head
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width
#> 1          5.1         3.5          1.4         0.2
#> 2          4.9         3.0          1.4         0.2
#> 3          4.7         3.2          1.3         0.2
#> 4          4.6         3.1          1.5         0.2
#> 5          5.0         3.6          1.4         0.2
#> 6          5.4         3.9          1.7         0.4

後者の方法では複数指定はたぶんできない...

列が大量にあるけど、特定の文字列を含むことがわかっていて、それが複数のパタンある場合に使える(使った)のですが、そんなに需要はないのかも。 他にも良い方法はありそうですが、「条件を満たしたら選択(select_if)する、条件は列名について次のパタンを見つけたとき」と、自分にはわかりやすいので良いかな…

select_ifの挙動

この前後でちょっとハマってしまって、またユタ兄さんに助けていただきました。

論理型のベクトルの場合はそのまま列が選択される。下記の例だとselect(c(1,4))としたときと同じ結果になる

iris %>% select_if(c(T,F,F,T,F)) %>% head
#>   Sepal.Length Petal.Width
#> 1          5.1         0.2
#> 2          4.9         0.2
#> 3          4.7         0.2
#> 4          4.6         0.2
#> 5          5.0         0.2
#> 6          5.4         0.4

一方、上の方に書いたようにfunctionで与えると、各列をベクトルとして与えます。 これは下記のようにして確かめることが出来ます(listではないことの確認。data.frameでも同様

iris %>% select_if(function(df){is.list(df)})
NA

iris %>% select_if(function(df){is.vector(df)}) %>% head
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width
#> 1          5.1         3.5          1.4         0.2
#> 2          4.9         3.0          1.4         0.2
#> 3          4.7         3.2          1.3         0.2
#> 4          4.6         3.1          1.5         0.2
#> 5          5.0         3.6          1.4         0.2
#> 6          5.4         3.9          1.7         0.4```

しかし、reprex使うと1つめはNAが返って?きますね…。コンソール上だと「0 列 150 行のデータフレーム」と返ってきますが。それぞれ環境が異なるので表示が違っても(゚ε゚)キニシナイ!!

しかし、functionは↓のように、typeofだとclosureって返ってきてやや混乱しますね(classもちゃんとfunctionって返すのか…知らなかった)

str(function(){})
#> function ()  
#>  - attr(*, "srcref")=Class 'srcref'  atomic [1:8] 17 5 17 16 5 16 17 17
#>   .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x00000000160567e0>
typeof(function(){})
#> [1] "closure"
mode(function(){})
#> [1] "function"
class(function(){})
#> [1] "function"
is.function(function(){})
#> [1] TRUE

ということで、ちゃんとマニュアルに目を通しましょう…というお話でした(そうだっけ?)

Enjoy!!

追記

その後、select+matchesでもいけますよ!ということでアドバイスいただきました。

やってみた。

iris %>% select(matches("(Petal)|(Sepal)")) %>% head
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width
#> 1          5.1         3.5          1.4         0.2
#> 2          4.9         3.0          1.4         0.2
#> 3          4.7         3.2          1.3         0.2
#> 4          4.6         3.1          1.5         0.2
#> 5          5.0         3.6          1.4         0.2
#> 6          5.4         3.9          1.7         0.4

こっちの方が簡単ですね…。selectだと、列の番号を受け取れればそれを選択できる。select_ifは対応する列のT/Fで選択する/しないをわける、と。

また、こちらの記事も参考に、とのことでした。

qiita.com

Enjoy!!