備忘ログ

チラシの裏的備忘録&メモ

Rで最頻値(mode)とその度数を求めたい(データフレーム中の各列の最頻値と度数を求めたい&データフレームの複数の列を組み合わせでの最頻値とその度数も求めたい)

Rで最頻値(mode)とその度数を求めたいと思った。 最頻値を求めるRの組み込みの関数はないので、いろいろ工夫が必要となる。

データフレーム中の各列の最頻値と度数を求めたいと思った。

また、後述するがデータフレームの複数の列を組み合わせでの最頻値とその度数も求めたいと思った。

データフレーム中の各列の最頻値と度数を求める

結論から

次の関数を読み込んで任意のデータフレームを投入するとそのデータフレーム中の列ごとの最頻値と度数が求められるようにしている。

最頻値は平均値や中央値の用に一意に求められない場合があるので、列に対して2つ以上の値がでることもある。

{tidyverse}のパッケージ・関数群を使って書いたmode_data.frame()baseだけで書いたmode2_data.frame()の2種類載せておくがどちらも求められる結果は同じ。

# {tidyverse}の関数群を使って求める場合
# {purrr}、{dplyr}、{rlang}、{tidyr}のパッケージのインストールが必要
mode_data.frame <- function(x){
  stopifnot(is.data.frame(x))
  df_colnames <- colnames(x)
  numeric_cheack <- all(x |> purrr::map_lgl(is.numeric))
  res <- NULL
  for(i in df_colnames){
    i <- rlang::sym(i)
    res_for <- x |> 
      dplyr::count(!!i) |> 
      dplyr::filter(n == max(n)) |> 
      tidyr::pivot_longer(cols = 1, names_to = "colnames") 
    if(!numeric_cheack) res_for <- dplyr::mutate(.data = res_for, value = as.character(value))
    res <- dplyr::bind_rows(res, res_for)
  }
  dplyr::select(.data = res, colnames, value, n)
}

これにデータフレームを投入すると各列の最頻値と度数がわかる。

mode_data.frame(iris)
## # A tibble: 8 × 3
##   colnames     value          n
##   <chr>        <chr>      <int>
## 1 Sepal.Length 5             10
## 2 Sepal.Width  3             26
## 3 Petal.Length 1.4           13
## 4 Petal.Length 1.5           13
## 5 Petal.Width  0.2           29
## 6 Species      setosa        50
## 7 Species      versicolor    50
## 8 Species      virginica     50

{base}のみで挙動するものは次の通り。

mode2_data.frame <- function(x){
  stopifnot(is.data.frame(x))
  df_colnames <- colnames(x)
  res <- NULL
  
  mode_ <- function(...){
    d <- table(...)
    d <- data.frame(d)
    freqcol <- colnames(d)[length(colnames(d))]
    d[d[freqcol] == max(d[freqcol]),]
  }
  
  for(i in df_colnames){
    res_for <- mode_(x[i])
    res_for[[3]] <- colnames(res_for[1])
    colnames(res_for) <- c("value", "Freq", "colnames")
    rownames(res_for) <- NULL
    res <- rbind(res, res_for)
  }
  
  res[c("colnames", "value", "Freq")]
}

同様にこれにデータフレームを投入すると各列の最頻値とその度数がわかる。

mode2_data.frame(iris)
##       colnames      value Freq
## 1 Sepal.Length          5   10
## 2  Sepal.Width          3   26
## 3 Petal.Length        1.4   13
## 4 Petal.Length        1.5   13
## 5  Petal.Width        0.2   29
## 6      Species     setosa   50
## 7      Species versicolor   50
## 8      Species  virginica   50

最頻値を求める

Rには最頻値を求める組み込みの関数はない。

例えば、{statip}パッケージのmfv()を使うとベクトル中の最頻値を簡単に求めることができる。

# install.packages("statip")でインストールする。
statip::mfv(iris$Sepal.Length)
## [1] 5
statip::mfv(iris$Species)
## [1] setosa     versicolor virginica 
## Levels: setosa versicolor virginica

最頻値が複数あってもそれらをすべて求めてくれるので便利だが、ベクトルしか受け付けないので次のような場合はエラーとなる。

statip::mfv(iris)
## Error in tableNA(x):  is.atomic(x) は TRUE ではありません
statip::mfv(iris[1])
## Error in tableNA(x):  is.atomic(x) は TRUE ではありません

また度数も求められないので、併せて度数も知りたいときにはこれでは不十分となる。

Rで最頻値を求める方法としてよく紹介されているのが、table()関数をつかって値と度数を求めて、その度数が大きい値を求める方法である。

table(iris[1])
## Sepal.Length
## 4.3 4.4 4.5 4.6 4.7 4.8 4.9   5 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9   6 6.1 6.2 
##   1   3   1   4   2   5   6  10   9   4   1   6   7   6   8   7   3   6   6   4 
## 6.3 6.4 6.5 6.6 6.7 6.8 6.9   7 7.1 7.2 7.3 7.4 7.6 7.7 7.9 
##   9   7   5   2   8   3   4   1   1   3   1   1   1   4   1

と値とその度数が求まるので、これの度数の最も多いものを求めるというもの。

tb <- table(iris[1])
tb[tb == max(tb)]
##  5 
## 10

こうすると、5という値が最頻値で度数が10ということがわかる。

たまに上記でいうところのtb == max(tb)のところをwhich.max(tb)としている例を見かけるが、最頻値が一意でないことを考慮すると、tb == max(tb)の方がベターだと思う。

tb <- table(iris[3])
# 最頻値が一つしか表示されない
tb[which.max(tb)]
## 1.4 
##  13
# 最頻値が複数あっても表示される
tb[tb == max(tb)]
## Petal.Length
## 1.4 1.5 
##  13  13

ところで、{dplyr}を使えるなら次のようにすると指定した列の値の度数が昇順で求められる。

library(dplyr)
## 
##  次のパッケージを付け加えます: 'dplyr'

##  以下のオブジェクトは 'package:stats' からマスクされています:
## 
##     filter, lag

##  以下のオブジェクトは 'package:base' からマスクされています:
## 
##     intersect, setdiff, setequal, union
iris |> 
  count(Petal.Length, sort = TRUE)
##    Petal.Length  n
## 1           1.4 13
## 2           1.5 13
## 3           4.5  8
## 4           5.1  8
## 5           1.3  7
## 6           1.6  7
## 7           5.6  6
## 8           4.0  5
## 9           4.7  5
## 10          4.9  5
## 11          1.7  4
## 12          4.2  4
## 13          4.4  4
## 14          4.8  4
## 15          5.0  4
## 16          3.9  3
## 17          4.1  3
## 18          4.6  3
## 19          5.5  3
## 20          5.7  3
## 21          5.8  3
## 22          6.1  3
## 23          1.2  2
## 24          1.9  2
## 25          3.3  2
## 26          3.5  2
## 27          4.3  2
## 28          5.2  2
## 29          5.3  2
## 30          5.4  2
## 31          5.9  2
## 32          6.0  2
## 33          6.7  2
## 34          1.0  1
## 35          1.1  1
## 36          3.0  1
## 37          3.6  1
## 38          3.7  1
## 39          3.8  1
## 40          6.3  1
## 41          6.4  1
## 42          6.6  1
## 43          6.9  1

これで度数を確認して最頻値を調べても良いが、最頻値とその度数だけがほしければ、filter()でnの最頻値だけを抽出すればいい。

iris |> 
  count(Petal.Length) |> 
  filter(n == max(n))
##   Petal.Length  n
## 1          1.4 13
## 2          1.5 13

これはパイプで繋げなくても少しの入れ子形式で簡単に書ける。

filter(.data = count(iris, Petal.Length), n == max(n))
##   Petal.Length  n
## 1          1.4 13
## 2          1.5 13

これで{base}{dplyr}を使って最頻値と度数を求めることができたが、データフレーム中のすべての列の最頻値をその度数を求めるのは、例えばlapply()とかをつかうなら次のようになる。

lapply(iris, function(x){
  tb <- table(x)
  tb[tb == max(tb)]
})
## $Sepal.Length
##  5 
## 10 
## 
## $Sepal.Width
##  3 
## 26 
## 
## $Petal.Length
## x
## 1.4 1.5 
##  13  13 
## 
## $Petal.Width
## 0.2 
##  29 
## 
## $Species
## x
##     setosa versicolor  virginica 
##         50         50         50

リスト形式で求められる。

最頻値が一意でない場合を考慮するとリストで取り出すのが妥当かもしれないが、リスト形式は少し値の取り出しが少しむずかしいし、列が多い場合は表示が長くなりがちでいまいち。

ちなみに{dplyr}count()関数の場合はデータフレームじゃないとうまく受けつけないのでpurrr::map()等での処理はしにくい。

そこで、データフレームの形で、取り出したいなと思う。

そこで冒頭のような関数を使って最頻値を取り出そうと考えた。

組み合わせの最も多いものとその度数を求める

例えば、{palmerpenguins}penguinsデータをみてみる。

library(palmerpenguins)
data("penguins")
penguins
## # A tibble: 344 × 8
##    species island    bill_length_mm bill_depth_mm flipper_…¹ body_…² sex    year
##    <fct>   <fct>              <dbl>         <dbl>      <int>   <int> <fct> <int>
##  1 Adelie  Torgersen           39.1          18.7        181    3750 male   2007
##  2 Adelie  Torgersen           39.5          17.4        186    3800 fema…  2007
##  3 Adelie  Torgersen           40.3          18          195    3250 fema…  2007
##  4 Adelie  Torgersen           NA            NA           NA      NA <NA>   2007
##  5 Adelie  Torgersen           36.7          19.3        193    3450 fema…  2007
##  6 Adelie  Torgersen           39.3          20.6        190    3650 male   2007
##  7 Adelie  Torgersen           38.9          17.8        181    3625 fema…  2007
##  8 Adelie  Torgersen           39.2          19.6        195    4675 male   2007
##  9 Adelie  Torgersen           34.1          18.1        193    3475 <NA>   2007
## 10 Adelie  Torgersen           42            20.2        190    4250 <NA>   2007
## # … with 334 more rows, and abbreviated variable names ¹​flipper_length_mm,
## #   ²​body_mass_g
## # ℹ Use `print(n = ...)` to see more rows

ここで、種類と島の組み合わせで最も多いものを求めたいと思うことがある。

{dplyr}count()はDescriptionに書いてあるとおり、group_by()してsummrise(n = n())していることになっているので、次のように書くと組み合わせでの度数を昇順で求めることができる。

penguins |> 
  count(species, island, sort = TRUE)
## # A tibble: 5 × 3
##   species   island        n
##   <fct>     <fct>     <int>
## 1 Gentoo    Biscoe      124
## 2 Chinstrap Dream        68
## 3 Adelie    Dream        56
## 4 Adelie    Torgersen    52
## 5 Adelie    Biscoe       44

これで、BiscoeのGentooが最も多く収録されていることがわかる。

これも最頻値同様に、最頻値だけがほしければ次のようにすると行ける。

penguins |> 
  count(species, island) |> 
  filter(n == max(n))
## # A tibble: 1 × 3
##   species island     n
##   <fct>   <fct>  <int>
## 1 Gentoo  Biscoe   124

これはcount()で指定する列は2つ以上でももちろん行ける。

{base}の場合はtable()関数を使う。

table(penguins[c("species", "island")])
##            island
## species     Biscoe Dream Torgersen
##   Adelie        44    56        52
##   Chinstrap      0    68         0
##   Gentoo       124     0         0

これでもっとも多い組み合わせがBiscoeのGentooであることがわかるが、ここから最も多い組み合わせだけの値を取り出そうとするとdata.frame()で頻度を取り出すのが良いと思う(これは組み合わせとして最も多いものだけでなく、0のものも一目瞭然なので今回の最も多い組合せを求める場面以外でも使える)。

df <- data.frame(table(penguins[c("species", "island")]))
df
##     species    island Freq
## 1    Adelie    Biscoe   44
## 2 Chinstrap    Biscoe    0
## 3    Gentoo    Biscoe  124
## 4    Adelie     Dream   56
## 5 Chinstrap     Dream   68
## 6    Gentoo     Dream    0
## 7    Adelie Torgersen   52
## 8 Chinstrap Torgersen    0
## 9    Gentoo Torgersen    0

こうすると組み合わせの度数がFreqとなるので、あとはこれを昇順で並び替えると一番上が最も多い組わせになる。

# dfを頻度順に並び替える。
df[order(df["Freq"], decreasing = TRUE),]
## Warning in xtfrm.data.frame(x): cannot xtfrm data frames

##     species    island Freq
## 3    Gentoo    Biscoe  124
## 5 Chinstrap     Dream   68
## 4    Adelie     Dream   56
## 7    Adelie Torgersen   52
## 1    Adelie    Biscoe   44
## 2 Chinstrap    Biscoe    0
## 6    Gentoo     Dream    0
## 8 Chinstrap Torgersen    0
## 9    Gentoo Torgersen    0

これも、最も多い組み合わせだけを取り出すことが簡単に出来る。

df[df["Freq"] == max(df["Freq"]),]
##   species island Freq
## 3  Gentoo Biscoe  124

これはある程度一般化できて、次のような関数とすることが出来る。

mode_ <- function(...){
    d <- table(...)
    d <- data.frame(d)
    freqcol <- colnames(d)[length(colnames(d))]
    d[d[freqcol] == max(d[freqcol]),]
  }

ここに任意のデータフレームやベクトルを投入すると組み合わせでの最も多い値と度数がわかる。

mode_(iris[c("Sepal.Length", "Sepal.Width")])
##     Sepal.Length Sepal.Width Freq
## 226          5.8         2.7    4

これはもちろん1列でも行けるし、複数列でも行ける。

mode_(iris["Sepal.Length"])
##   Sepal.Length Freq
## 8            5   10
mode_(iris[c(1,5)])
##   Sepal.Length Species Freq
## 8            5  setosa    8
## 9          5.1  setosa    8

データフレームをそのまま投入するとすべての列の組み合わせを見てくれる。

mode_(iris)
##         Sepal.Length Sepal.Width Petal.Length Petal.Width   Species Freq
## 2064246          5.8         2.7          5.1         1.9 virginica    2

irisデータはこんなに小さなデータなのに同じ値のデータが存在するんだと思った。小数点第一位で丸められているからかも知れない。

しかし、大きいデータを触ると少し重たいのと、table()の処理の上限で大きすぎるデータは取り扱えない。

mode_(penguins)
## Error in table(...):  2^31 個の要素を超えるテーブルを作成しようとしています

{dplyr}count()であれば大きいデータでも取り扱えれる。

penginsデータにはすべてが同じ値のデータが存在しないので、さらに大きい{ggplot2}diamondsデータを使ってみてみる。

data("diamonds", package = "ggplot2")
diamonds |> 
  count(across()) |> 
  filter(n == max(n))
## # A tibble: 1 × 11
##   carat cut   color clarity depth table price     x     y     z     n
##   <dbl> <ord> <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl> <int>
## 1  0.79 Ideal G     SI1      62.3    57  2898   5.9  5.85  3.66     5

データ内に5つ同じ値のデータが含まれていてこれが最も多い組わせであることがわかる。

もちろん、filter()n > 1と指定するとすべて同じ値のデータが2つ以上ある場合のデータを抽出することが出来る。

diamonds |> 
  count(across(), sort = TRUE) |> 
  filter(n > 1)
## # A tibble: 143 × 11
##    carat cut       color clarity depth table price     x     y     z     n
##    <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl> <int>
##  1  0.79 Ideal     G     SI1      62.3    57  2898  5.9   5.85  3.66     5
##  2  0.3  Good      J     VS1      63.4    57   394  4.23  4.26  2.69     2
##  3  0.3  Very Good G     VS2      63      55   526  4.29  4.31  2.71     2
##  4  0.3  Very Good J     VS1      63.4    57   506  4.26  4.23  2.69     2
##  5  0.3  Premium   D     SI1      62.2    58   709  4.31  4.28  2.67     2
##  6  0.3  Ideal     G     VS2      63      55   675  4.31  4.29  2.71     2
##  7  0.3  Ideal     G     IF       62.1    55   863  4.32  4.35  2.69     2
##  8  0.3  Ideal     H     SI1      62.2    57   450  4.26  4.29  2.66     2
##  9  0.3  Ideal     H     SI1      62.2    57   450  4.27  4.28  2.66     2
## 10  0.31 Good      D     SI1      63.5    56   571  4.29  4.31  2.73     2
## # … with 133 more rows
## # ℹ Use `print(n = ...)` to see more rows

143組もあるらし。