★データ解析備忘録★

ゆる〜い技術メモ

データフレームから全て NAの列や行を取り除くには

TL;DR

もっと簡単に書ける/良い方法があれば @y__mattu までお願いします。

library(tidyverse)

# サンプルデータ
my_iris <- iris %>%
  # 全部NAの列を追加
  add_column(na_col = NA, .before = 0) %>%
  # 全部NAの行を追加
  add_row(.before = 0)

head(my_iris)
#>   na_col Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1     NA           NA          NA           NA          NA    <NA>
#> 2     NA          5.1         3.5          1.4         0.2  setosa
#> 3     NA          4.9         3.0          1.4         0.2  setosa
#> 4     NA          4.7         3.2          1.3         0.2  setosa
#> 5     NA          4.6         3.1          1.5         0.2  setosa
#> 6     NA          5.0         3.6          1.4         0.2  setosa

# 全部NAの列を削除
my_iris %>%
  select_if(~sum(!is.na(.)) > 0) %>%
  head()
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1           NA          NA           NA          NA    <NA>
#> 2          5.1         3.5          1.4         0.2  setosa
#> 3          4.9         3.0          1.4         0.2  setosa
#> 4          4.7         3.2          1.3         0.2  setosa
#> 5          4.6         3.1          1.5         0.2  setosa
#> 6          5.0         3.6          1.4         0.2  setosa

# 全部NAの行を削除
my_iris %>%
  filter_all(any_vars(!is.na(.))) %>%
  head()
#>   na_col Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1     NA          5.1         3.5          1.4         0.2  setosa
#> 2     NA          4.9         3.0          1.4         0.2  setosa
#> 3     NA          4.7         3.2          1.3         0.2  setosa
#> 4     NA          4.6         3.1          1.5         0.2  setosa
#> 5     NA          5.0         3.6          1.4         0.2  setosa
#> 6     NA          5.4         3.9          1.7         0.4  setosa

簡単な解説

データフレームから「NAしかない」列や行がいくつかあった時、それらをごっそり削りたい。全部NAの列や行なんてよほどのことがない限り必要ないですからね。ネ申エクセルシートで特定の列や途中の行を見えなくしてるやつをデータでもらったりするとそんな場面があたりします。そもそもそんなデータ渡してくんなよって話ですが

Drop "all NA" columns

こんなときは深呼吸をして、 dplyr::select_if() を使います。dplyr で ***_if() という関数は、「特定の条件に当てはまるものにだけ***する」という働きをします。 mutate() であればこれは列の追加・上書きを担当するので、 df %>% mutate_if(vars(is.numeric), funs(as.character)) みたいに書けば「数値型の列だったら文字列型に変換する」という処理ができるわけです。詳しくは以下。

notchained.hatenablog.com

select_if() はそれのselect版で、 select() は列選択をする関数なので「条件に当てはまった列だけ選択する」ということになります。 ~sum(!is.na(.)) > 0 の部分ですが、まずここでの ~ラムダ式 とか 無名関数 と呼ばれるものをつくるものです。 上の mutate_if()funs(as.character) という書き方をしていますが、これの代わりにこういう書き方ができる、というだけです。

2018.12.10 追記 funs() の書き方は非推奨になりました。 ~ を使いましょう

sum(!is.na(.)) > 0 は、「全部NAのもの」を指定するためにこういう書き方をしました。ここは is.na(NA) とかを実行してみるとなんでこういう書き方になるかわかると思います。

また、tidyverseで多用する %>% (パイプ演算子) を使ったコードの中での . ですが、ざっくり「自分自身(パイプ演算子前の内容)」と思っておけばいいと思います。 1:3 %>% sum(.) であれば .1:3 ですし、今回であれば my_iris になります。

Delete "all NA" rows

基本的な発想は列の時と同じです。 dplyr::***_all() は「全部に ***する」ということなので filter_all() は「全部の変数をみて、なんかの時にfilterする(行を残す)」という意味。 dplyr::any_vars() は「どれか一つの変数(列)が」という意味なので filter_all(any_vars(!is.na(.))) で「どれか一列でもNAじゃなかったら行を残す = 全部NAの行を消す」という意味。

注意

tidyverse でNAの行を消すと言えば tidyr::drop_na() がありますが、これはそのまま使うと「一列でもNAがあるを消す」という関数なので注意。「ある特定の変数にNAがある行を消す」んであれば dorp_na(変数名)