JavaScript での Levene検定 の実装

# Reactive stat 内部の Levene検定の実装 です

Levene検定は、3つ以上のグループの分散が等しいかどうかを検定する方法の一つです。
分散分析のようないくつかの統計的検定では、群やサンプル間で分散が等しいことを前提とします。
Levene検定は、その仮定を検証するために使用されます。

この記事では、Levene検定の概要と、JavaScriptでの実装方法を説明します。

## はじめに

弊社では **[ブラウザだけで使える無料統計ソフト Reactive stat](https://www.emuyn.net/stats)** を提供しています。
信頼性の高い R で統計解析し、その結果を AI が解説します！
その背景には、統計に苦労している医療者の助けになりたい、という気持ちがあります。
最終的な統計解析は R で行うのですが、レスポンスとサーバー負荷の改善のため、一部の統計計算はブラウザ内で行っています。
そのうち、汎用性の高い部分を共有させていただきたいと思います。
できるだけ R の出力と整合性を持つように javascript をインプリメントしてあるので、参考になれば幸いです。

## 使用したライブラリ

このコードでは、jStatライブラリを使用しています。jStatは、JavaScriptで統計計算を行うための便利なライブラリです。
jStatの詳細については、[公式ドキュメント](https://github.com/jstat/jstat) を参照してください。
全関数の一覧は [jStat v1.9.3 Documentation](https://jstat.github.io/all.html) を参照してください。

## Levene検定の概要

**仮説**
- $H_0$: すべてのグループの分散は等しい
- $H_a$: 少なくとも2つの分散は異なる

**検定統計量 $W$ の計算**:
$W = \frac{(N-k)}{(k-1)} \frac{\sum_{i=1}^{k} N_i (\bar{Z}{i\cdot} - \bar{Z})^2}{\sum{i=1}^{k} \sum_{j=1}^{N_i} (Z_{ij} - \bar{Z}_{i\cdot})^2}$

ここで：
- $k$ はグループの数
- $N$ は合計サンプルサイズ
- $N_i$ は $i$ 番目のグループのサンプルサイズ
- $Z_{ij} = |Y_{ij} - \tilde{Y}i|$, $Y{ij}$ は $i$ 番目のグループの $j$ 番目の観測値,
- $\tilde{Y}_i$ は $i$ 番目のグループの中央値
- $\bar{Z}{i\cdot}$ は $i$ 番目のグループの $Z{ij}$ の平均値
- $\bar{Z}$ は全サンプルの $Z_{ij}$ の平均値

帰無仮説のもとで検定統計量 $W$ は、自由度が $(k-1)$ のF分布に従います。したがって、p値はF分布を用いて求めることができ、このp値が予め定められた有意水準（しばしば $\alpha = 0.05$ ）よりも小さい場合、等分散の帰無仮説は棄却されます。

**仮定と注意**:
Levene検定は各データ群が独立であると仮定しています。
Levene検定は、Bartlett検定と比べて正規性からの逸脱に対して頑健です。各グループ内のデータが対称であることを仮定する必要はありません。しかし、極端に歪んだ分布やグループサイズが非常に異なる場合には、検出力が低下する可能性があります。

## コード

ライブラリ込みの html にしてありますので、
[jsfiddle](https://jsfiddle.net/) などにコピペして簡単に試せます。

```html:html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Levene's Test</title>
  <script src="https://cdn.jsdelivr.net/npm/jstat@latest/dist/jstat.min.js"></script>
</head>
<body>
  <h1>Levene's Test Example</h1>
  <p>Check the console for the result.</p>
  
  <script>
    // Levene検定
    function leveneTest(...groups) {
        const glen = groups.length; // グループ数
        const groupSizes = groups.map(group => group.length); // 各グループのサンプルサイズ
        const totalSize = groupSizes.reduce((acc, val) => acc + val, 0); // 全サンプル数

        // 各グループの中央値を計算
        const medians = groups.map(group => jStat.median(group));

        // 各グループの絶対偏差を計算
        const deviations = groups.map((group, idx) => {
            return group.map(val => Math.abs(val - medians[idx]));
        });

        // 各グループの絶対偏差の平均を計算
        const meanDeviations = deviations.map(group => {
            return group.reduce((acc, val) => acc + val, 0) / group.length;
        });

        // 全サンプルの絶対偏差の平均を計算
        const grandMean = deviations.flat().reduce((acc, val) => acc + val, 0) / totalSize;

        // 群間平方和（SSB）の計算
        const ssb = groupSizes.reduce((acc, groupSize, idx) => {
            return acc + groupSize * Math.pow(meanDeviations[idx] - grandMean, 2);
        }, 0);

        // 群内平方和（SSW）の計算
        const ssw = deviations.reduce((acc, group, idx) => {
            return acc + group.reduce((acc, val) => acc + Math.pow(val - meanDeviations[idx], 2), 0);
        }, 0);

        // 検定統計量Wの計算
        const W = ((totalSize - glen) / (glen - 1)) * (ssb / ssw);

        // p値の計算
        const pValue = 1 - jStat.centralF.cdf(W, glen - 1, totalSize - glen);

        return pValue;
    }

    // サンプルデータ
    const group1 = [85, 90, 92, 87, 99];
    const group2 = [80, 82, 78, 83, 79];
    const group3 = [84, 88, 91, 93, 86];

    const pValue = leveneTest(group1, group2, group3);
    console.log("Levene's Test p-value: ", pValue);
  </script>
</body>
</html>
```

## 検証用 R コード

[rdrr](https://rdrr.io/snippets/) にコピペして簡単に試せます。

```R:R
# Levene's Test

# サンプルデータ
group1 <- c(85, 90, 92, 87, 99)
group2 <- c(80, 82, 78, 83, 79)
group3 <- c(84, 88, 91, 93, 86)

# Levene検定を実行
library(car)
result <- leveneTest(y = c(group1, group2, group3), group = factor(rep(1:3, c(length(group1), length(group2), length(group3)))))

# 結果の表示
print(result)
```

## 注意点

- このコードは、jStatライブラリに依存しています。jStatを読み込まないと動作しません
- 浮動小数点数の計算では、わずかな誤差が生じる可能性があります
- Levene検定は、データが正規分布に従っていなくても適用可能ですが、極端に歪んだ分布やグループサイズが非常に異なる場合には、検出力が低下する可能性があります
- 群の数が2つの場合、F検定を使用することをお勧めします。Levene検定は3つ以上の群の比較に適しています

## 最後に

コードの内容はよく吟味し、R との食い違いが極力ないように気を付けていますが、もし間違いに気づかれた場合には指摘していただけますとありがたいです。

ただ、Chat GPT に読ませてその出力を検証もせず「問題があるので修正します」として貼り付けられると、他の方を混乱させてしまうのと、弊社の製品に対する信頼を損ねることにもつながりかねませんので、どうかそのようなことのないようにお願いいたします。

検証のための R のコードもつけてありますので、ご活用いただけますと幸いです。