行方向にグループ化して、行方向で計算・集計を行う方法

rowwisec_acrossを利用して行方向でNAを除去する

TOC

導入

やってみると困る処理として、行単位で「全部NAの場合にfilterをかけて省く」というものがあります。今回、ちょっと詰まって調べたので、そこで学んだ関数の使いかたを記事として共有してみます。

尚、ヒントとなったのは

このスタックオーバーフローの質問です。

欠損値をフィルターしてみる

それでははじめましょう。まずは、データを作っておきます。

library(tidyverse)

dat <- tibble(
  a1 = sample(c(1,NA), 300, replace=TRUE),
  a2 = sample(c(1,NA), 300, replace=TRUE),
  b1 = sample(c(1,NA), 300, replace=TRUE),
  b2 = sample(c(1,NA), 300, replace=TRUE)
)
knitr::kable(head(dat))
a1a2b1b2
NANANANA
1NANANA
11NA1
NA11NA
1NANA1
1NA1NA

このデータ、各列に1かNA(欠損)のいずれかをランダムに挿入してあります。

ここで、このデータの組み合わせを確認すると、

dat %>% 
  count(a1,a2,b1,b2) %>% 
  knitr::kable()
a1a2b1b2n
111122
111NA11
11NA115
11NANA23
1NA1117
1NA1NA18
1NANA123
1NANANA21
NA11116
NA11NA22
NA1NA119
NA1NANA20
NANA1121
NANA1NA18
NANANA113
NANANANA21

こんな感じです。

で、すべての列がNAの場合の列を除去してみようとすると、普通に書くと

dat %>% 
  filter( !(is.na(a1) & is.na(a2) & is.na(b1) & is.na(b2))) %>% 
  count(a1,a2,b1,b2)
## # A tibble: 15 × 5
##       a1    a2    b1    b2     n
##    <dbl> <dbl> <dbl> <dbl> <int>
##  1     1     1     1     1    22
##  2     1     1     1    NA    11
##  3     1     1    NA     1    15
##  4     1     1    NA    NA    23
##  5     1    NA     1     1    17
##  6     1    NA     1    NA    18
##  7     1    NA    NA     1    23
##  8     1    NA    NA    NA    21
##  9    NA     1     1     1    16
## 10    NA     1     1    NA    22
## 11    NA     1    NA     1    19
## 12    NA     1    NA    NA    20
## 13    NA    NA     1     1    21
## 14    NA    NA     1    NA    18
## 15    NA    NA    NA     1    13

これでできます。ただ、ここでもし変数が100個とかあると、この方法だとかなり面倒。。。
なので、こうします。

dat %>% 
  rowwise() %>% 
  filter(!all(is.na(c_across())))
## Warning: There was 1 warning in `filter()`.
## ℹ In argument: `!all(is.na(c_across()))`.
## ℹ In row 1.
## Caused by warning:
## ! Using `c_across()` without supplying `cols` was deprecated in
##   dplyr 1.1.0.
## ℹ Please supply `cols` instead.
## # A tibble: 279 × 4
## # Rowwise: 
##       a1    a2    b1    b2
##    <dbl> <dbl> <dbl> <dbl>
##  1     1    NA    NA    NA
##  2     1     1    NA     1
##  3    NA     1     1    NA
##  4     1    NA    NA     1
##  5     1    NA     1    NA
##  6    NA    NA     1    NA
##  7    NA     1     1     1
##  8     1    NA     1     1
##  9    NA    NA     1     1
## 10    NA     1    NA     1
## # ℹ 269 more rows

これで、行のすべての値がNA(欠損している)場合のデータを除外できました。

ただ、
除外でやると、本当にできているのか怪しいので、抽出してみましょう

dat %>% 
  rowwise() %>% 
  filter(all(is.na(c_across())))
## # A tibble: 21 × 4
## # Rowwise: 
##       a1    a2    b1    b2
##    <dbl> <dbl> <dbl> <dbl>
##  1    NA    NA    NA    NA
##  2    NA    NA    NA    NA
##  3    NA    NA    NA    NA
##  4    NA    NA    NA    NA
##  5    NA    NA    NA    NA
##  6    NA    NA    NA    NA
##  7    NA    NA    NA    NA
##  8    NA    NA    NA    NA
##  9    NA    NA    NA    NA
## 10    NA    NA    NA    NA
## # ℹ 11 more rows

できてますね?

他にも、aで始まる列が欠損している行だけを抜き出す

dat %>% 
  rowwise() %>% 
  filter(all(is.na(c_across(starts_with("a")))))
## # A tibble: 73 × 4
## # Rowwise: 
##       a1    a2    b1    b2
##    <dbl> <dbl> <dbl> <dbl>
##  1    NA    NA    NA    NA
##  2    NA    NA     1    NA
##  3    NA    NA     1     1
##  4    NA    NA     1     1
##  5    NA    NA     1    NA
##  6    NA    NA    NA     1
##  7    NA    NA     1    NA
##  8    NA    NA     1    NA
##  9    NA    NA     1    NA
## 10    NA    NA    NA     1
## # ℹ 63 more rows

1で終わる列が欠損している行だけを抜き出す。

dat %>% 
  rowwise() %>% 
  filter(all(is.na(c_across(ends_with("1")))))
## # A tibble: 73 × 4
## # Rowwise: 
##       a1    a2    b1    b2
##    <dbl> <dbl> <dbl> <dbl>
##  1    NA    NA    NA    NA
##  2    NA     1    NA     1
##  3    NA     1    NA     1
##  4    NA    NA    NA     1
##  5    NA     1    NA    NA
##  6    NA     1    NA    NA
##  7    NA     1    NA    NA
##  8    NA    NA    NA     1
##  9    NA    NA    NA     1
## 10    NA    NA    NA    NA
## # ℹ 63 more rows

できてますね?

この、関数の働き、filterではなくて、mutate関数を利用して
イメージとして見ると、次のような形になります

result <- dat %>% 
  rowwise() %>% 
  mutate(res = sum(is.na(c_across(everything()))))
knitr::kable(head(result,20))
a1a2b1b2res
NANANANA4
1NANANA3
11NA11
NA11NA2
1NANA12
1NA1NA2
NANA1NA3
NA1111
1NA111
NANA112
NA1NA12
NA1111
NA1111
NANA112
1NANA12
NANA1NA3
NA1111
NA11NA2
NA1NA12
11110

実際にa1からb2までの列の欠損値の数がresというコラムに表示されていますね?
rowwiseで行方向のグループを作成しないと、この動作、普通のmutateでやるとエラーが生じます。

行方向で足し算してみる

この書き方を応用すれば、行方向にデータを足し合わせるようなことも簡単にできました。

dat2 <- tibble(
  a = sample(1:6,100,replace=TRUE),
  b = sample(1:6,100,replace=TRUE),
  c = sample(1:6,100,replace=TRUE),
  d = sample(1:6,100,replace=TRUE),
  e = sample(1:6,100,replace=TRUE),
  f = sample(1:6,100,replace=TRUE),
  g = sample(1:6,100,replace=TRUE)
)
knitr::kable(head(dat2,10))
abcdefg
5532446
6426631
5134641
2161641
4365364
1241246
5562235
6252452
1153233
1425654

こんなデータの、a列からg列までの数字を足した結果が入るresという列を作成してみましょう

もちろん、

dat2 %>% 
  mutate(res = a+b+c+d+e+f+g)
## # A tibble: 100 × 8
##        a     b     c     d     e     f     g   res
##    <int> <int> <int> <int> <int> <int> <int> <int>
##  1     5     5     3     2     4     4     6    29
##  2     6     4     2     6     6     3     1    28
##  3     5     1     3     4     6     4     1    24
##  4     2     1     6     1     6     4     1    21
##  5     4     3     6     5     3     6     4    31
##  6     1     2     4     1     2     4     6    20
##  7     5     5     6     2     2     3     5    28
##  8     6     2     5     2     4     5     2    26
##  9     1     1     5     3     2     3     3    18
## 10     1     4     2     5     6     5     4    27
## # ℹ 90 more rows

という下記方でもOKですが、変数が100個あった場合などでは

dat2 %>% 
  rowwise() %>% 
  mutate(res = sum(c_across(), na.rm=TRUE)) %>% 
  ungroup()
## # A tibble: 100 × 8
##        a     b     c     d     e     f     g   res
##    <int> <int> <int> <int> <int> <int> <int> <int>
##  1     5     5     3     2     4     4     6    29
##  2     6     4     2     6     6     3     1    28
##  3     5     1     3     4     6     4     1    24
##  4     2     1     6     1     6     4     1    21
##  5     4     3     6     5     3     6     4    31
##  6     1     2     4     1     2     4     6    20
##  7     5     5     6     2     2     3     5    28
##  8     6     2     5     2     4     5     2    26
##  9     1     1     5     3     2     3     3    18
## 10     1     4     2     5     6     5     4    27
## # ℹ 90 more rows

という書き方をした方がわかりやすく、短く書けるような気がします。

あと、最後のungroupは忘れないでrowwiseの効果を打ち消しておきましょう。

以上、簡単にですが、rowwisec_acrossで調べたことを共有しました。
何か間違っていること、認識違いのことがあったりしたら教えてください!!

Have a happy R life!

Let's share this post !

Author of this article

Comments

To comment

TOC