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組もあるらし。