順列に関連する 12 個のキーワード。 各チップから関連ページへジャンプできる。
🍰 まずはやさしく
順列は、ものの並び方のことです。
選び方と順番の両方を数えるために使います。
スマホのパスワードを決める時に役立ちます。
ここでは、順列の基本的な考え方を読みます。
n個から r個を順序付けて選ぶ場合の数
🍰 まずはやさしく
順列は、並べ方のルールの基礎です。
データの分析や暗号の強さを調べる時に使います。
都道府県をランキング順に並べる例で考えます。
この章では、統計学での使われ方を読みます。
高校数学の確率・場合の数の基礎。 暗号強度の評価、 ノンパラメトリック検定(permutation test)など、 機械学習にも顔を出します。
あなたは今、 「順列」のページにいる。 これは 「組合せ論」系列の最も基本的な概念で、 $n$ 個の異なる対象から $k$ 個を選んで並べ方を考慮して並べる方法の数。 統計学では「並べ替え検定(permutation test)」の基礎、 機械学習では特徴量重要度(permutation importance)に登場する。 SSDSE-B-2026 の 47 都道府県から 5 個並べる方法数は約 1.84 億通り。
前提: 階乗 / 並列: 組合せ / 発展: 並べ替え検定
🍰 まずはやさしく
順列は、順番を大切にする選び方です。
並び方が変わると別物として数えるために使います。
部活のメンバーを1列に並べる時に役立ちます。
ここでは、具体的な数え方の流れを読みます。
4人 A, B, C, D から 2人選んで一列に並べる:
順序ありなので「AB」と「BA」を別カウント。 もし区別しないなら組合せ ${}_4C_2 = 6$。
| 問題 | 順序を区別? | 公式 | 47 県から 5 県の例 |
|---|---|---|---|
| 5 県を表彰式の壇上に並べる | する (1 位/2 位/...) | ${}_{47}P_5$ | 184,072,680 通り |
| 5 県を視察先として選ぶ | しない | ${}_{47}C_5$ | 1,533,939 通り (P÷5!) |
覚え方: 「順序を意識する → P (Permutation)」「順序を意識しない → C (Combination)」。 P は C の $r!$ 倍 (5 県の並び順は 5!=120 通りあるため)。
スライダーで n(並べる元の個数)と k(取り出して並べる個数)を動かすと、 順列 P(n,k)=n!/(n-k)! の値・実際の並べ方・組合せ C(n,k) との違いがリアルタイムに変わります。 図をスワイプ(左右にドラッグ)するか下のボタンで、 実際の並べ方を 1 つずつ切り替えられます。 「順序を区別する」と数がどれだけ増えるかを体感してください。
🍰 まずはやさしく
順列は、数式で表せる並べ方の数です。
計算を簡単にして答えを出すために使います。
買い物で選ぶ商品の順番を数える時に役立ちます。
ここでは、順列の公式と計算方法を読みます。
順列(Permutation):n個から r個を順序付けて選ぶ場合の数
| 公式名 | 数式 | 適用場面 |
|---|---|---|
| 基本順列 | $P(n,k) = \frac{n!}{(n-k)!}$ | $n$ 個から $k$ 個を選んで並べる |
| 全順列 | $P(n,n) = n!$ | 全部並べる |
| 重複順列 | $n^k$ | $n$ 種類から $k$ 個重複可 |
| 同じものを含む順列 | $\frac{n!}{n_1! n_2! \cdots n_r!}$ | 重複要素含む全並べ替え |
| 円順列 | $(n-1)!$ | 円形の配置 |
| 数珠順列 | $\frac{(n-1)!}{2}$ | 裏返しも同じとみなす円 |
| 組合せ | $C(n,k) = \frac{n!}{(n-k)!k!}$ | 順序を区別しない選び方 |
| 順列⇔組合せ | $P(n,k) = C(n,k) \cdot k!$ | 変換 |
| Stirling 近似 | $n! \approx \sqrt{2\pi n}(n/e)^n$ | 大きな $n!$ の近似 |
| ガンマ関数 | $\Gamma(n) = (n-1)!$ | 階乗の連続拡張 |
この 10 公式を覚えれば、 順列に関する問題の 95% は解ける。 残り 5% は群論や生成関数を要する高度なもの。
全問正解なら、 順列の知識は実務レベルに達している。 次は組合せ・確率・並べ替え検定の関連ページへ。
順列・組合せ・階乗の周辺には沢山の公式があり、 場面ごとに使い分ける。 ここで一覧化しておく。
| 名称 | 式 | 意味・使い所 |
|---|---|---|
| 階乗 | $n! = n \cdot (n-1) \cdots 1$ | $n$ 個全てを並べる |
| 順列 | $_nP_r = \frac{n!}{(n-r)!}$ | $n$ から $r$ を順序付き選択 |
| 組合せ | $_nC_r = \frac{n!}{r!(n-r)!}$ | $n$ から $r$ を順序なし選択 |
| 重複順列 | $n^r$ | パスワードのような同要素再使用 |
| 重複組合せ | $_{n+r-1}C_r$ | 果物の購入セット |
| 円順列 | $(n-1)!$ | 円卓の座席配置 |
| 数珠順列 | $\frac{(n-1)!}{2}$ | ネックレスの飾り順 |
| 多項係数 | $\frac{n!}{k_1! k_2! \cdots k_m!}$ | 同種要素の並び替え、 CV 分割 |
| パスカルの公式 | $_nC_r = _{n-1}C_{r-1} + _{n-1}C_r$ | 漸化式で組合せを構築 |
| 二項定理 | $(a+b)^n = \sum_{r=0}^{n} {_nC_r} a^r b^{n-r}$ | 確率分布 (二項分布) の基礎 |
| スターリング近似 | $n! \approx \sqrt{2\pi n}\,(n/e)^n$ | 大きな $n$ で階乗を近似計算 |
| 交代和 | $\sum_{r=0}^{n}(-1)^r {_nC_r}=0$ $(n \geq 1)$ | 包除原理の基礎、 完全順列 (montmort 数) |
→ 公式を丸暗記するのではなく、 「何を区別し、 何を等価視するか」のロジックで導出できるようにしておく。 SSDSE-B-2026 の現場では、 多項係数とスターリング近似は計算量の見積もりで頻出。
順列の概念は数式だけでは捉えにくい。 ここでは SSDSE-B-2026 の都道府県データを使って、 順列・順位・並び替えの効果を視覚化した 3 枚の図を示す。 すべて実データから生成されたものであり、 合成データは一切含まない。
→ 図 1 は順位相関の妥当性、 図 2 は順列検定の必要性、 図 3 は順列検定の動作原理、 をそれぞれ示している。 順列は「並べ方の総数」を数えるだけでなく、 「分布の形を仮定なしに扱う」「順位を比較する」道具でもあることが、 1 枚ずつの図で確認できる。
これらを横断する共通の主題は「順序が情報を持つか / 持たないか」を見極めること。 SSDSE-B-2026 のような実データで順列概念を実感することで、 高校数学の $_nP_r$ が「実務で何の道具になるか」が明確になる。
さらに踏み込めば、 順列は単独で意味を持つだけでなく、 確率論 (二項分布、 ポアソン分布、 超幾何分布のいずれもが組合せ・順列を母数として持つ)、 情報理論 (エントロピーは確率分布上の組合せ的指標)、 圏論 (対称群が群論・表現論の母体)、 計算量理論 (NP 困難な最適化問題の多くが順列空間上の探索) など、 数学の根幹に深く接続している。 順列の理解度は、 そのまま「離散数学を実問題と結ぶ翻訳力」の指標になる。 SSDSE-B-2026 を題材にした本稿の演習で、 47 都道府県の順列空間と現実の社会データを行き来する感覚を養い、 統計・データ解析コンペの実戦で「順序を扱う」あらゆる場面に対応できる土台を築いてほしい。 順列は地味な概念だが、 データサイエンスの根を支える基礎中の基礎であり、 ここを丁寧に押さえることが上達への最短経路となる。 公務員試験・統計検定・データ解析実務のいずれにおいても、 順列を「式」「実装」「意味」の 3 層で同時に語れる人材は強い。 SSDSE-B-2026 を題材に手を動かし続けることで、 その境地に確実に近づける。
| 記号 | 意味 |
|---|---|
| ${}_nP_r$ | n個からr個の順列数 |
| $n!$ | n の階乗(n × (n-1) × … × 1) |
| ${}_nC_r$ | 組合せ数(順序区別なし) |
| $\binom{n}{r}$ | 二項係数(${}_nC_r$ と同じ) |
順列(Permutation)は、 単に用語の定義を覚えるだけでは本当には理解できません。 なぜこの概念が生まれたのか、 どんな問題を解決するために導入されたのか、 類似の手法とどう違うのか — これらを意識することで、 初めて「使える知識」になります。
数式や Python コードはあくまで 道具。 道具の使い方を覚える前に、 その道具で何をしたいか(目的) を明確にすることが、 データサイエンス学習の鉄則です。
この用語は、 単独で存在するわけではなく、 多くの関連概念とネットワークを形成しています。 上の「関連用語」セクションに挙げたリンク先を1つずつ辿ると、 全体像が見えてきます。 特に:
理論を学ぶことと、 実務で使えることは別物です。 公的統計(SSDSE、 e-Stat 等)の実データで実装・実験することで、 教科書だけでは見えない罠 に気付けます。 たとえば:
これらは 順列 に限った話ではなく、 データサイエンス全般に共通する作法です。 「落とし穴」セクションの内容と合わせて、 自分なりのチェックリストを作るとよいでしょう。
順列 を使った分析の 正しさを担保する ためには、 以下の観点で検証するのが定番です。
| 確認する点 | 順列 で何を見るか |
|---|---|
| 順列と組合せの混同 | 「並べる」か「選ぶだけ」かを問題文で確認。 |
| 階乗の爆発 | 20! を整数で扱う → 大きいが Python なら OK。 浮動小数では誤差。 |
| 0! の扱い | $0! = 1$(空の積)。 ${}_nP_0=1$。 |
| 重複順列の見落とし | 同じものを何度も使える場合は別公式($n^r$)。 |
| 再現性 | 同じデータ・同じコードで同じ結果が出るか。このページの ▶ 実行ボタンで確かめられます |
順列 は分野横断で活躍する概念です。 業界別に見ると以下のような使われ方があります。
順列 を実際のデータで学ぶときは、 SSDSE(教育用標準データセット、 総務省統計局)が便利です。
これらは 統計センターの SSDSE ページ から CSV で直接ダウンロードできます。 上の Python コード例で data/raw/SSDSE-B-2026.csv としているのが、 まさにこれです。
実データで動かすことで、 教科書の例題では見えない 実務的な気づき(欠損のパターン、 単位の混在、 都道府県名の表記揺れ等)が得られます。
pip install pandas numpy scikit-learn matplotlib で揃います。utf-8 ではなく shift_jis や cp932 の場合がある(古い日本の公的統計に多い)。 encoding='cp932' を試してください。%matplotlib inline、 スクリプト実行なら plt.show() を忘れずに。 日本語フォントは matplotlib 用に別途設定(japanize-matplotlib 等)が必要。順列の数式は次の 2 つを必ず押さえる:
$$P(n, k) = {}_nP_k = \frac{n!}{(n-k)!} = n \cdot (n-1) \cdot (n-2) \cdots (n-k+1)$$
$$P(n, n) = n! = n \cdot (n-1) \cdot (n-2) \cdots 2 \cdot 1$$
直感的読み方:「最初の位置は $n$ 通り、 次は $n-1$ 通り、 ..., $k$ 番目は $n-k+1$ 通り、 これを掛け合わせる」。 並び順を区別するので、 同じメンバーの異なる並びは別カウント。
組合せ $C(n,k) = P(n,k) / k!$ は順列を「並び順を無視」して数えたもの。 順列 = 組合せ × 並び方の数。
順列の数学的な深層には「群論」がある。 $n$ 個の対象の全順列は対称群 $S_n$ をなし、 群論の中心的対象。 SSDSE-B-2026 の 47 都道府県の置換は $S_{47}$ の元として記述できる。
「並び順を数える」だけの順列が、 群論を経由して暗号・代数・物理・ML に展開する。 「基礎数学が応用に直結する」典型例。
| n | n! | 列挙時間(1 つ 1 ns) | 現実の解釈 |
|---|---|---|---|
| 5 | 120 | 0.12 マイクロ秒 | 瞬時 |
| 10 | 3,628,800 | 3.6 ミリ秒 | 体感ゼロ |
| 15 | 1.3 × 10^12 | 22 分 | 待てる |
| 20 | 2.4 × 10^18 | 77 年 | 一生終わらない |
| 25 | 1.6 × 10^25 | 5 億年 | 地質学的時間 |
| 30 | 2.7 × 10^32 | 8 × 10^15 年 | 宇宙年齢×10^6 |
| 47 (SSDSE) | 2.59 × 10^59 | ∞ | 物理的に不可能 |
$n=20$ で「人生では終わらない」、 $n=30$ で「宇宙年齢の 100 万倍」。 順列の世界では「賢く絞り込む」アルゴリズムが必須。 これが分枝限定法・動的計画法・近似アルゴリズムが発達した理由。
学習済みモデルに対し、 ある特徴量の値をシャッフルして予測精度がどれだけ落ちるかを測る。 落ち幅が大きい特徴量ほど重要。 順列の概念をモデル解釈に応用したのが Breiman (2001) の発明。
データを $k$ 個のフォールドに分割する際、 ランダム順列でシャッフルしてから分割するのが標準。 順序による偏りを除く目的。 sklearn の KFold(shuffle=True) がこの実装。
Shapley 値は特徴量の貢献度を「全順列における追加効果の平均」として定義する。 SHAP ライブラリはこれを高速近似計算。 順列が ML 解釈の理論的根拠を支える。
時系列データの「ランダム置換」や、 タブラーデータの「カラム順序の入れ替え」は順列ベースのデータ拡張。 過学習防止に有効。
BERT 系の Pre-training で、 文の順序を入れ替えて「順序が正しいか」を判定する Next Sentence Prediction や、 単語の順序をシャッフルする augmentation。 順列が NLP の表現学習に活用される。
| ML 手法 | 順列の役割 | 代表ライブラリ |
|---|---|---|
| Random Forest | 特徴量重要度の計算 | sklearn.inspection.permutation_importance |
| Gradient Boosting | 同上 | XGBoost / LightGBM |
| SHAP | Shapley 値計算 | shap |
| Cross-validation | フォールド分割のシャッフル | sklearn.model_selection |
| Mini-batch SGD | エポック毎のシャッフル | PyTorch DataLoader |
並べ替え検定(permutation test)は順列の最も実用的な統計応用。 帰無仮説「2 群に差はない」のもとで、 ラベルを並べ替えても統計量の分布が変わらないことを利用する。
| 観点 | 並べ替え検定 | t 検定(パラメトリック) |
|---|---|---|
| 分布仮定 | 不要 | 正規分布を仮定 |
| 小サンプル | ○ 適している | △ 仮定が崩れる |
| 計算コスト | 高い(順列爆発) | 低い(解析解) |
| 汎用性 | 任意の統計量に対応 | 平均差のみ |
| 解釈 | 直感的(並べ替えて比較) | 分布論を要する |
SSDSE-B-2026 のような 47 件規模なら、 並べ替え検定が直感的で説明しやすい。 大規模データでは t 検定の方が高速。
順列 $n!$ の応用先で最も実用的なのが「順列検定」。 これは「2 群の平均差が偶然か否か」を、 仮定なし (分布の正規性を仮定しない) に判定する手法。 SSDSE-B-2026 の都道府県データで、 「東日本 vs 西日本の人口総数に差はあるか?」を順列検定で評価する。
このコードでやること: SSDSE-B-2026 の都道府県人口 (A1101) を東日本 (北海道〜静岡) と西日本 (愛知〜沖縄) に分け、 平均差を観測値とする。 そして 47 都道府県のラベルを 5,000 回ランダムに並べ替え (=順列の一部を抽出)、 「観測値以上の差が偶然生じる確率」を p 値として求める。
📥 入力データ (SSDSE-B-2026 の人口列を東/西で分割):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import pandas as pd import numpy as np from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2022].copy() east = df.iloc[:22]['A1101'].values # 北海道〜静岡 west = df.iloc[22:]['A1101'].values # 愛知〜沖縄 observed_diff = np.abs(east.mean() - west.mean()) # 順列検定: 5,000 回ラベルをシャッフル np.random.seed(0) combined = np.concatenate([east, west]) n_east = len(east) n_perm = 5000 count = 0 for _ in range(n_perm): np.random.shuffle(combined) diff = np.abs(combined[:n_east].mean() - combined[n_east:].mean()) if diff >= observed_diff: count += 1 p_value = count / n_perm print(f'観測平均差 = {observed_diff / 1e4:.1f} 万人') print(f'p 値 (順列検定) = {p_value:.4f}') |
📤 実行すると次の出力が得られる:
💬 p 値 0.22 は「観測差は偶然のレベル」を意味する。 つまり東京・大阪のような巨大都市の偏りはあるものの、 「東/西」という分割は人口を区別する有意な軸ではない。 順列検定は分布の仮定を置かないので、 t 検定が使えない歪んだデータ (人口は極端な右裾を持つ) でも安全に使える。
SSDSE-B-2026 の 47 都道府県から、 訪問する 5 県を選んで順番に並べる場合の総数を計算する。
📥 入力:
📤 計算:
💬 「47 県から 5 つ並べる」と聞くと小さい数字に感じるが、 実際は 1.84 億通り。 6 つにすると 77 億通りで、 もう全列挙は計算機でも厳しい。 これが順列の「指数的に増える」性質。
参考:組合せ $C(47, 5) = P(47,5) / 5! = 184072680 / 120 = 1,533,939$。 順列の 120 分の 1 になる(5 個の並べ方が 5! = 120 通りあるため)。
順列 $_nP_r = n!/(n-r)!$ と組合せ $_nC_r = n!/(r!(n-r)!)$ の関係は $_nP_r = _nC_r \times r!$ で結ばれる。 47 都道府県から 3 県を選ぶ場合の比較:
| 概念 | 記号 | 47 県から 3 県 | 順序 | 例 |
|---|---|---|---|---|
| 順列 | $_{47}P_3$ | $47 \times 46 \times 45 = 97{,}290$ | 区別する | 「金・銀・銅」の表彰順 |
| 組合せ | $_{47}C_3$ | $97{,}290 / 6 = 16{,}215$ | 区別しない | 「上位 3 県」の選出 |
「順序が問題になるか?」が分岐点。 県名を「並べる」なら順列、 「集める」なら組合せ。 scipy では scipy.special.perm(47, 3) と scipy.special.comb(47, 3) で計算できる。
順列 (permutation) は「順序を区別して並べる方法の総数」を扱う離散数学の基礎概念である。 確率・統計・機械学習の至るところに姿を現すが、 中学・高校で学んだ $_nP_r = n!/(n-r)!$ の式だけでは、 現実のデータ解析でどう使うかが見えにくい。 ここでは SSDSE-B-2026 (47 都道府県 × 約 80 変数 × 12 年度) を題材に、 順列の概念が現れる 6 つの実例 — (1) 順列検定による中央値差の評価、 (2) 特徴量重要度の permutation importance 計算、 (3) クロスバリデーション分割の順列空間、 (4) ランキング系列の Spearman/Kendall 順位相関、 (5) 順列暗号としてのワンタイムパッド類似、 (6) 巡回セールスマン問題 (TSP) で 47 都道府県を回る最短経路 — を順に体験する。 各実例で「順列の総数」「実際に試行できる回数」「現実的な計算可能性」を必ず数字で示し、 「組合せ爆発」がどこから始まるかを腹に落とす。
このコードでやること: SSDSE-B-2026 の都道府県別「人口総数 A1101 (2022 年)」を、 関東 1 都 6 県 vs その他 40 県に分割し、 中央値の差を 10,000 回の順列で評価する。 t 検定や Welch 検定は平均と分散の仮定が必要だが、 順列検定は「ラベルを交換しても分布は変わらない」という弱い仮定のみで動く。 人口データは右に強く歪んでおり (東京・神奈川・大阪が突出)、 正規分布の仮定は破綻している。 こうした場合に順列検定は強力な代替手段になる。
📥 入力データ (SSDSE-B-2026 を関東/その他で分割した抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2022].copy() np.random.seed(0) kanto = ['東京都', '神奈川県', '埼玉県', '千葉県', '茨城県', '栃木県', '群馬県'] df['region'] = df['Prefecture'].apply( lambda x: 'kanto' if x in kanto else 'other') group_a = df[df['region'] == 'kanto']['A1101'].values group_b = df[df['region'] == 'other']['A1101'].values observed = np.abs(np.median(group_a) - np.median(group_b)) combined = np.concatenate([group_a, group_b]) n_a = len(group_a) n_perm = 10000 count = 0 for _ in range(n_perm): np.random.shuffle(combined) diff = np.abs(np.median(combined[:n_a]) - np.median(combined[n_a:])) if diff >= observed: count += 1 p_value = (count + 1) / (n_perm + 1) print(f'観測中央値差 = {observed / 1e4:.1f} 万人') print(f'p 値 (順列検定, 10,000 回) = {p_value:.5f}') |
📤 実行すると次の出力が得られる:
💬 p < 0.01 で、 「関東とその他で人口中央値に差はない」という帰無仮説は強く棄却される。 47 県のラベル並び替えの全パターンは $_{47}C_7 = 62{,}891{,}499$ 通りもあるが、 そのうち 10,000 件をランダムサンプリングするだけで p 値の近似は十分得られる。 これが順列検定の効率性。
このコードでやること: SSDSE-B-2026 の説明変数 (人口総数、 出生数、 死亡数、 転入者数、 転出者数) から「総人口」を予測する RandomForest を学習し、 各特徴量の列を順列でシャッフルして予測誤差がどれだけ悪化するかを測る (permutation importance)。 シャッフル後に大きく誤差が増える列ほど、 モデルにとって重要な特徴量である、 という解釈。 順列は「特徴量間の依存関係を壊す」ために使われている。
📥 入力データ (SSDSE-B-2026 の主要 5 列):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import pandas as pd from sklearn.ensemble import RandomForestRegressor from sklearn.inspection import permutation_importance from sklearn.model_selection import train_test_split df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) cols = ['A4101', 'A4200', 'A5101', 'A5102'] df = df.dropna(subset=cols + ['A1101']) X = df[cols] y = df['A1101'] X_tr, X_te, y_tr, y_te = train_test_split( X, y, test_size=0.3, random_state=0) model = RandomForestRegressor(n_estimators=200, random_state=0) model.fit(X_tr, y_tr) result = permutation_importance( model, X_te, y_te, n_repeats=30, random_state=0) for name, imp in zip(cols, result.importances_mean): print(f'{name}: 重要度 {imp:.4f}') |
📤 実行すると次の出力が得られる (値は random_state 依存):
💬 死亡数 A4200 を順列で壊すと R² が 0.93 ポイント悪化する一方、 転入者数 A5101 を壊しても 0.01 しか悪化しない。 → 「転入者数の情報は、 他の特徴量で代替できる」「死亡数は他で代替が効かない、 独立な情報源」とモデルが教えてくれる。 この計算は順列が無いと成立しない (列を抜くと相関の連鎖が壊れる)。
47 都道府県を 5-fold CV に分割する方法は何通りあるか? これは多項係数で表され $\binom{47}{10,10,10,10,7} = \frac{47!}{10!\,10!\,10!\,10!\,7!}$ となり、 およそ $3.0 \times 10^{29}$ 通り。 全パターンを試すのは宇宙の寿命でも無理なので、 通常は 1 つの分割しか試さない。 だが「分割の偶然」で精度が振れることを防ぐため、 sklearn の RepeatedKFold は n_repeats 回だけランダム順列を取って平均する。
| 分割方法 | 順列の数 | 実務での試行回数 | 用途 |
|---|---|---|---|
| 2-fold (47 を 23+24) | $_{47}C_{23} \approx 1.6 \times 10^{13}$ | 2-5 回 | 高速プロトタイプ |
| 5-fold | $\approx 3.0 \times 10^{29}$ | 5 回 × 5 repeats | 標準 |
| Leave-One-Out | 47 (= $_{47}C_1$) | 47 回 (全部) | 小データ向け |
| Random permutation CV | $47! \approx 2.6 \times 10^{59}$ | 100-1000 回サンプリング | ベンチマーク |
→ 順列の総数は天文学的だが、 実務では数十回のサンプリングで CV 精度の分散は安定する。 これは「中心極限定理 + 順列のランダム性」の合わせ技。
このコードでやること: SSDSE-B-2026 の総人口 A1101 による都道府県順位と、 出生数 A4101 による順位を比較する。 Spearman 相関係数は「2 つの順列がどれだけ近いか」を測る指標で、 Kendall の τ は「順列のペアごとの転倒数 (inversion)」をカウントする。 順列は単なる「並べ方」だが、 2 つの順列を比べることで「ランキングの一致度」が定量化できる。
📥 入力データ (SSDSE-B-2026 2022 年度、 人口順位 vs 出生数順位):
1 2 3 4 5 6 7 8 9 10 11 12 13 | import pandas as pd from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2022].copy() # 人口順位と出生数順位を比較 pop_rank = df['A1101'].rank(ascending=False) birth_rank = df['A4101'].rank(ascending=False) rho, p_s = stats.spearmanr(pop_rank, birth_rank) tau, p_k = stats.kendalltau(pop_rank, birth_rank) print(f'Spearman rho = {rho:.4f} p = {p_s:.2e}') print(f'Kendall tau = {tau:.4f} p = {p_k:.2e}') |
📤 実行すると次の出力が得られる:
💬 ρ ≈ 0.98 は「人口順位と出生数順位はほぼ同じ順列」を意味する。 47! ≈ $2.6 \times 10^{59}$ 通りの順列空間で、 これだけ似ているのは偶然ではない (p < $10^{-30}$)。 出生数の多寡は人口規模でほぼ決まる、 という自然な結論が、 順列の比較から自動的に出てくる。 順列の差分を「転倒対 (inversion pair) の個数」として数えるのが Kendall の τ で、 ρ より頑健 (外れ値に強い)。
古典暗号「シーザー暗号」は文字シフトだが、 トランスポジション暗号 (転置式暗号) は順列そのもの。 たとえば 47 文字の平文を「47! 通りのどれかの順列で並び替えた」と思えば、 鍵が無ければ 47! ≈ $2.6 \times 10^{59}$ の中から 1 つを当てる総当たり計算が必要。 これは現代のスーパーコンピュータでも宇宙の寿命を超える時間がかかる。 順列の組合せ爆発が暗号の安全性を支えている。 一方で、 順列の構造には「偶置換 / 奇置換」「巡回構造」「対称群 $S_n$」といった群論的性質があり、 量子計算アルゴリズム (例: Shor のアルゴリズム) の研究対象になる。
このコードでやること: 架空の 8 都市の座標をコード内にハードコードし、 全都市を 1 回ずつ訪問する経路の総距離を計算する。 都市数 n の巡回路は始点・向きの対称性を除いて $(n-1)!/2$ 通り。 8 都市なら 2,520 通りなので順列全探索で厳密最適が求まるが、 47 都道府県では $47!/(2 \times 47) \approx 2.7 \times 10^{57}$ 通りとなり全探索は不可能。 ここでは (A) 最近傍法 (nearest neighbor) と (B) 順列全探索 ($(n-1)! = 5{,}040$ 通りを列挙) を比較する。 順列 = 訪問順序、 という最も直接的な対応関係。
📥 入力データ (架空の都市配置・実在の県庁所在地ではなくコード内にハードコードした仮想座標):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | import numpy as np from itertools import permutations # 架空の都市配置(8 都市)- 実在の県庁所在地ではなくハードコードした仮想座標 names = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] xs = np.array([2.0, 5.0, 8.0, 9.0, 7.0, 4.0, 1.0, 6.0]) # 架空の x 座標 ys = np.array([1.0, 2.0, 1.5, 5.0, 8.0, 7.0, 4.0, 5.5]) # 架空の y 座標 def total_dist(order): d = 0.0 for i in range(len(order) - 1): a, b = order[i], order[i + 1] d += np.hypot(xs[a] - xs[b], ys[a] - ys[b]) return d # (A) 最近傍法(ヒューリスティクス) n = len(xs) visited = [0] remaining = set(range(1, n)) while remaining: last = visited[-1] nxt = min(remaining, key=lambda j: np.hypot(xs[last] - xs[j], ys[last] - ys[j])) visited.append(nxt) remaining.remove(nxt) print(f'最近傍法の総距離 = {total_dist(visited):.2f}') print('最近傍法の訪問順 =', ' → '.join(names[i] for i in visited)) # (B) 順列全探索(始点固定、(n-1)! = 5040 通りを列挙して厳密最適) best_order, best = None, float('inf') for perm in permutations(range(1, n)): order = [0] + list(perm) c = total_dist(order) if c < best: best, best_order = c, order print(f'全探索の最短総距離 = {best:.2f}') print('全探索の最短訪問順 =', ' → '.join(names[i] for i in best_order)) |
📤 実行すると次の出力が得られる:
💬 最近傍法は素早く近似解を出すが、 この架空データでは全探索の厳密最適より約 11% 長い。 8 都市では全探索 (5,040 通り) が一瞬で終わるが、 都市数が増えると $(n-1)!$ が爆発し、 20 都市で $19! \approx 1.2 \times 10^{17}$ 通りとなって全探索は不可能になる。 そこで 2-opt・焼きなまし法 (simulated annealing)・遺伝的アルゴリズム等のヒューリスティクスが必須になる。 順列空間の広大さが、 組合せ最適化の難しさを物語る。
順列の概念は「並べる」「順序を与える」「組合せ爆発を制御する」という 3 つの形で、 実務のあらゆる領域に登場する。 ここでは公的データ・ビジネス・科学計算・教育・スポーツ・防災の 12 領域で、 順列が「何を数え、 何を最適化し、 何を検定するか」を一覧する。
| 領域 | 課題 | 順列の現れ方 | 使う公式・手法 |
|---|---|---|---|
| 統計検定 | 2 群差の有意性 | ラベル順列で帰無分布を構築 | permutation test, ${n+m \choose n}$ |
| 機械学習 | 特徴量重要度 | 列をシャッフルして誤差増加を測る | permutation importance |
| 最適化 | TSP, ジョブショップ | 訪問順 = 順列 | 2-opt, SA, GA, $n!$ 探索 |
| 暗号 | トランスポジション暗号 | 文字列の並び替えで秘匿 | $n!$ の安全性 |
| スポーツ | トーナメント抽選 | 対戦順 = 順列 | $_nP_r$, 制約付き |
| 生物 | DNA 配列比較 | アライメント問題 | 編集距離, 順列の局所探索 |
| 物流 | 配送順最適化 | 配達順序 = 順列 | VRP, ILP |
| スケジューリング | 会議室割当 | 時間スロット並び替え | 制約充足, OR-Tools |
| 情報検索 | 検索結果順位 | Spearman/Kendall | $\rho, \tau$ |
| 教育 | 座席配置 | $n!$ 通りの座席順列 | 階乗 |
| 防災 | 避難経路シミュレーション | 経路順序の最適化 | A*, 順列の枝刈り |
| 公共データ | 都道府県ランキング | 順位 = 順列 | 順位相関, 安定度 |
→ 共通の構造: 「並べ方の数 $n!$」「並べ方の比較 (順位相関)」「並べ方の探索 (ヒューリスティクス)」の 3 系統。 順列を知らないと、 これらの問題を「複雑そう」で終わらせてしまう。
順列計算の限界を肌で感じるため、 階乗 $n!$ の桁数と必要計算時間を一覧化する。 「現在のスーパーコンピュータ (約 1 exaFLOPS = $10^{18}$ flops) で全順列を列挙したら何秒かかるか」を計算する。 1 順列の処理に 1 flops と仮定 (実際はもっと重い)。
| $n$ | $n!$ | 桁数 | スパコン 1 exaFLOPS での時間 | 解釈 |
|---|---|---|---|---|
| 5 | 120 | 3 | $1.2 \times 10^{-16}$ 秒 | 瞬時 |
| 10 | 3,628,800 | 7 | $3.6 \times 10^{-12}$ 秒 | 瞬時 |
| 15 | $1.31 \times 10^{12}$ | 13 | $1.3 \times 10^{-6}$ 秒 | マイクロ秒 |
| 20 | $2.43 \times 10^{18}$ | 19 | 2.4 秒 | 人が待てる |
| 25 | $1.55 \times 10^{25}$ | 26 | $1.55 \times 10^{7}$ 秒 (約 180 日) | 半年 |
| 30 | $2.65 \times 10^{32}$ | 33 | $2.65 \times 10^{14}$ 秒 (約 840 万年) | 不可 |
| 47 (47都道府県) | $2.59 \times 10^{59}$ | 60 | $2.59 \times 10^{41}$ 秒 (約 $8 \times 10^{33}$ 年) | 宇宙の寿命の $5.9 \times 10^{23}$ 倍 |
| 100 | $9.33 \times 10^{157}$ | 158 | $9.33 \times 10^{139}$ 秒 | 想像不可 |
→ $n = 25$ あたりが「全列挙の限界」。 47 都道府県の全順列はもちろん、 30 都市の TSP すらも全列挙不可能。 これが「ヒューリスティクス」「サンプリング」「動的計画法 (bitmask DP)」が必須になる理由。
このコードでやること: scipy で $n!$ を計算し、 上の表の数値を再現する。 Python の math.factorial は任意精度整数なので 100! も瞬時に計算できる。
1 2 3 4 5 6 7 8 9 10 | import math import scipy.special for n in [5, 10, 15, 20, 25, 30, 47, 100]: fact = math.factorial(n) digits = len(str(fact)) print(f'n = {n:3d}: n! = {fact} ({digits} 桁)') # scipy の場合は浮動小数で高速 print(scipy.special.factorial(47)) |
📤 実行すると次の出力が得られる (先頭抜粋):
💬 47! の桁数 60 は、 「観測可能な宇宙の原子の総数 ($\approx 10^{80}$)」より小さい点が興味深い。 つまり都道府県の順列ですら、 宇宙の原子よりは少ない。 だが計算時間で言えば、 宇宙の寿命の $10^{23}$ 倍が必要。 「数えられても、 列挙できない」のが順列の本質。
順列の概念は紀元前から人類の知的活動の中に現れている。 古代から現代までの主要な発展を時系列でまとめる。
| 年代 | 人物・文献 | 業績 |
|---|---|---|
| 紀元前 2000 年 | 古代インド (Sushruta Samhita) | 6 種の味の組合せ数 = $2^6 - 1 = 63$ を計算 |
| 紀元前 200 年 | Pingala (古代インド) | サンスクリット韻律学でパスカル三角形 (メル三角形) を記述 |
| 11 世紀 | al-Karaji, Bhāskara II | 組合せ係数の漸化式を確立 |
| 1654 年 | Pascal & Fermat | 賭けの問題 (point の分配) で順列・組合せを確率論に応用 |
| 1666 年 | Leibniz「De Arte Combinatoria」 | 「組合せ術」を体系化、 普遍言語の基礎に |
| 1770 年 | Euler | 置換 (permutation) を群論の対象として研究、 ラテン方格 |
| 1832 年 | Galois | 対称群 $S_n$ と置換群論で 5 次方程式不可解性を証明 |
| 1935 年 | R. A. Fisher | 「実験計画法」で順列検定の現代的形式を提唱 |
| 1971 年 | Cook, Karp | TSP 等の順列問題が NP-hard であることを証明 |
| 1990s〜 | 機械学習 | Permutation importance, ブートストラップ、 並べ替え法が標準化 |
| 2010s〜 | 深層学習 | 順列不変性 (Set Transformer, DeepSets) が研究テーマに |
→ 順列は「数える」段階から「並べる」「比較する」「最適化する」「群論的に構造を解明する」へと進化してきた。 21 世紀には機械学習の説明可能性 (XAI) の中心概念にもなった。
合成 n=5, k=3 で順列数を計算する。
1 2 3 | from math import perm, factorial print(f"P(5,3) = {perm(5, 3)}") print(f"5!/2! = {factorial(5)//factorial(2)}") |
💬 手計算 (Step 2) 60 と Python 出力が完全一致。
SSDSE-B-2026 などの実データを使った最小コード(9行):
1 2 3 4 5 6 7 8 9 | from math import factorial from itertools import permutations n, r = 5, 3 nPr = factorial(n) // factorial(n - r) print(f'{n}P{r} =', nPr) # 実際に列挙 perms = list(permutations(['A','B','C','D','E'], r)) print('実際の列挙数:', len(perms)) print('最初5個:', perms[:5]) |
※ data/raw/SSDSE-B-2026.csv は e-Stat SSDSE から取得した実データを想定。
🎯 このコードでやること: SSDSE-B-2026 の 47 都道府県から $k$ 個並べる順列数を $k=1,2,...,8$ で計算する。
📥 入力: SSDSE-B-2026 の都道府県数(47)。
1 2 3 4 5 6 7 8 9 10 11 | import math import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932') n = df['都道府県'].nunique() print(f'都道府県数 n = {n}') print() print(f'{"k":>3} {"P(n,k)":>20}') for k in range(1, 9): p = math.perm(n, k) print(f'{k:>3} {p:>20,}') |
📤 実行結果:
💬 $k=5$ で 1.84 億、 $k=8$ で 12.7 兆。 指数関数的爆発が起きていることが視覚的に分かる。
🎯 このコードでやること: 都道府県名から 3 つ選んで並べた全パターンの先頭 5 件を出力する。
📥 入力: SSDSE-B-2026 の都道府県カラム。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from itertools import permutations import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932') df = df[df['年度'] == 2023] # 47 県 × 1 年度に絞る prefs = df['都道府県'].head(5).tolist() print('対象:', prefs) print() count = 0 for perm in permutations(prefs, 3): if count < 8: print(' → '.join(perm)) count += 1 print(f'\n合計 {count} 通り (P(5,3) = 60 と一致)') |
📤 実行結果:
💬 順列の総数 $P(5,3) = 5×4×3 = 60$ と完全一致。 「北海道 → 青森県 → 岩手県」と「青森県 → 北海道 → 岩手県」は別カウント(並び順が違うので)。
🎯 このコードでやること: SSDSE-B-2026 の都道府県人口を「関東」「東北」に分け、 両群の平均差が偶然得られる確率を並べ替え検定で評価する。
📥 入力: SSDSE-B-2026 の総人口 + 地方マップ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import pandas as pd import numpy as np from itertools import combinations df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932') df = df[df['年度'] == 2023] # 最新年度のみ (年混在を防ぐ) kanto = ['茨城県','栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県'] tohoku = ['青森県','岩手県','宮城県','秋田県','山形県','福島県'] a = df[df['都道府県'].isin(kanto)]['総人口'].values b = df[df['都道府県'].isin(tohoku)]['総人口'].values obs_diff = a.mean() - b.mean() print(f'観測された平均差: {obs_diff:,.0f} 人') all_vals = np.concatenate([a, b]) n_a = len(a) count_extreme = 0 total = 0 for picks in combinations(range(len(all_vals)), n_a): perm_a = all_vals[list(picks)] perm_b = np.delete(all_vals, list(picks)) diff = perm_a.mean() - perm_b.mean() if abs(diff) >= abs(obs_diff): count_extreme += 1 total += 1 print(f'p 値 = {count_extreme}/{total} = {count_extreme/total:.4f}') |
📤 実行結果:
💬 関東と東北の人口差は偶然では 2.0% の確率でしか得られない → 統計的に有意。 並べ替え検定は順列の考え方を統計に直接応用した手法。
🎯 このコードでやること: $n!$ の桁数を $n=1$ から $n=50$ まで観察し、 階乗の爆発的な増加を体感する。
📥 入力: 数値範囲のみ。
1 2 3 4 5 6 | import math for n in [1, 5, 10, 15, 20, 25, 30, 40, 47, 50]: f = math.factorial(n) digits = len(str(f)) print(f'{n:>3}! = {digits:>3} 桁 (先頭: {str(f)[:8]}...)') |
📤 実行結果:
💬 47! は 60 桁。 これは観測可能な宇宙の原子数(約 80 桁)に近い。 順列を全列挙する計算は、 物理的に不可能なスケールに容易に達する。
🎯 このコードでやること: 全列挙が困難な場合に、 ランダムサンプリングで並べ替え検定を近似する。 SSDSE-B-2026 の関東 vs 全国平均の比較を例にする。
📥 入力: SSDSE-B-2026 の総人口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932') df = df[df['年度'] == 2023] # 最新年度のみ (年混在を防ぐ) kanto = ['茨城県','栃木県','群馬県','埼玉県','千葉県','東京都','神奈川県'] df['is_kanto'] = df['都道府県'].isin(kanto) obs_diff = df[df['is_kanto']]['総人口'].mean() - df[~df['is_kanto']]['総人口'].mean() print(f'観測された平均差: {obs_diff:,.0f}') rng = np.random.default_rng(2026) pops = df['総人口'].values n_kanto = df['is_kanto'].sum() n_iter = 10000 extreme = 0 for _ in range(n_iter): shuffled = rng.permutation(pops) diff = shuffled[:n_kanto].mean() - shuffled[n_kanto:].mean() if abs(diff) >= abs(obs_diff): extreme += 1 print(f'モンテカルロ p 値 ≒ {extreme/n_iter:.4f}') |
📤 実行結果:
💬 関東と非関東の人口差は p=0.07% で偶然では得難い → 関東は明らかに人口集中。 完全列挙 $C(47,7) = 6.2 \times 10^7$ は無理なので、 10000 回サンプリングで近似する。
🎯 このコードでやること: SSDSE-B-2026 の都道府県を乱数で並べ替え、 「ランダムな順列」を 5 通り表示する。
📥 入力: 47 都道府県リスト。
1 2 3 4 5 6 7 8 9 10 11 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=1, encoding='cp932') df = df[df['年度'] == 2023] # 47 県 × 1 年度に絞る prefs = df['都道府県'].tolist() rng = np.random.default_rng(2026) for i in range(5): shuffled = rng.permutation(prefs) print(f'順列 {i+1}: {", ".join(shuffled[:5])} ...') |
📤 実行結果:
💬 47! ≒ 2.59 × 10^59 通りの順列の中から、 5 つだけランダムに選んだサンプル。 シャッフルは「順列空間からの一様サンプリング」と数学的に等価。
🎯 このコードでやること: SSDSE-B-2026 の地方 8 ブロックの代表都市を巡る最短ルートを総当たりで求める(5 ブロックで $4!=24$ 通り)。
📥 入力: 5 都市間の距離行列(架空値)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from itertools import permutations cities = ['札幌', '仙台', '東京', '名古屋', '大阪'] dist = { ('札幌', '仙台'): 700, ('札幌', '東京'): 1100, ('札幌', '名古屋'): 1300, ('札幌', '大阪'): 1500, ('仙台', '東京'): 350, ('仙台', '名古屋'): 600, ('仙台', '大阪'): 850, ('東京', '名古屋'): 350, ('東京', '大阪'): 500, ('名古屋', '大阪'): 180, } def d(a, b): return dist.get((a, b), dist.get((b, a))) best_path, best_cost = None, float('inf') for perm in permutations(cities[1:]): path = ['札幌'] + list(perm) cost = sum(d(path[i], path[i+1]) for i in range(len(path)-1)) if cost < best_cost: best_cost, best_path = cost, path print(f'最短ルート: {" → ".join(best_path)}') print(f'総距離: {best_cost} km') |
📤 実行結果:
💬 5 都市なら 24 通りで瞬時に最適解が出る。 ただし 20 都市で $19! ≒ 1.2×10^{17}$ となり総当たり不可能。 TSP は順列の指数爆発の代表例。
🎯 このコードでやること: 大きな $n$ の $n!$ を直接計算せず、 Stirling 近似 $n! \approx \sqrt{2\pi n} (n/e)^n$ で見積もる。
📥 入力: 数値のみ。
1 2 3 4 5 6 7 8 9 10 | import math def stirling(n): return math.sqrt(2 * math.pi * n) * (n / math.e) ** n for n in [5, 10, 20, 47, 100]: exact = math.factorial(n) approx = stirling(n) error = abs(exact - approx) / exact * 100 print(f'n={n:>3}: exact={exact:.3e}, stirling={approx:.3e}, err={error:.2f}%') |
📤 実行結果:
💬 Stirling 近似は $n$ が大きいほど精度が上がり、 $n=100$ で誤差 0.08%。 厳密値が必要ない場面では計算コスト削減に有効。
| 分野 | 具体例 | 使う順列の種類 |
|---|---|---|
| 確率論 | 抽選 / カードの並び方 | 基本順列 + 全順列 |
| 統計推測 | 並べ替え検定 | ラベル割り当ての順列 |
| 機械学習 | permutation importance / シャッフル | 特徴量の並べ替え |
| 暗号 | パスワード強度 / DES の置換 | 重複順列 ($n^k$) |
| 最適化 | TSP / スケジューリング | 巡回順列 |
| バイオ | DNA 配列の並び | 重複あり順列 |
| スポーツ | トーナメント組合せ / 打順 | 基本順列 |
順列は「並び順」を扱うあらゆる場面で登場する。 一見無関係に見える分野でも、 数学的には同じ「順列の数を数える」問題に帰着する。
| 観点 | 順列 P(n,k) | 組合せ C(n,k) |
|---|---|---|
| 並び順の扱い | 区別する | 区別しない |
| 公式 | $n!/(n-k)!$ | $n!/[(n-k)!k!]$ |
| 関係 | $P(n,k) = C(n,k) \times k!$ | $C(n,k) = P(n,k) / k!$ |
| P(5,3) | 60 | 10 |
| P(47,5) | 184,072,680 | 1,533,939 |
| 典型応用 | 並べる、 順位を決める | グループを作る |
| Python | math.perm / itertools.permutations | math.comb / itertools.combinations |
迷ったら「A→B」と「B→A」が同じか別か考える。 同じなら組合せ、 別なら順列。 また「順列の数 = 組合せの数 × $k!$」の関係を覚えておけば、 一方から他方を導出できる。
SSDSE-B-2026 の都道府県を「太平洋側 / 日本海側」に分けて出生率を比較する。 サンプル数が少ない(47)ため、 t 検定ではなく並べ替え検定で評価する。 ラベル割り当て順列の数は $C(47, 20) \approx 1.6 \times 10^{13}$ で全列挙は困難 → モンテカルロ 10000 回で近似する。
SSDSE-B-2026 の指標から「総人口」を予測するモデルを構築し、 各特徴量について permutation importance を計算。 「出生数」「死亡数」「世帯数」が上位、 「都道府県コード」が最下位(無関係)と判明する想定。 順列の操作で「どの特徴量が予測に効いているか」を客観的に評価。
10 人のスタッフを 10 個のシフトに割り当てる順列は 10! = 3,628,800 通り。 各シフトに優先スキル要件があり、 全順列を試して最高スコアを探索可能。 これが 20 人になると 20! ≒ 2.4×10^18 で全探索不可能 → 遺伝的アルゴリズム等の近似手法へ。
この 3 つを意識すれば、 順列を実務に投入する判断が早くなる。 SSDSE-B-2026 の演習を入口に、 ML や統計の応用に広げていこう。
math.factorial が任意精度整数なので安全。順列の理解は公式の暗記ではなく、 「自分の手で並べてみる」ことで深まる。 ここでは初学者から中級者まで、 段階的に難度を上げた 6 つの演習を提示する。 すべて SSDSE-B-2026 の都道府県データを材料にする。
SSDSE-B のデフォルト並びは「JIS コード順 (北海道〜沖縄)」だが、 これを県名の文字列昇順 (Unicode 順。 50 音順とは一致しない点に注意) に並び替える。 47! 通りの順列のうちのたった 1 つだが、 並び替え操作は順列の操作の基本。
1 2 3 4 5 6 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df_2022 = df[df['SSDSE-B-2026'] == 2022].copy() df_sorted = df_2022.sort_values('Prefecture') print(df_sorted[['Prefecture', 'A1101']].head(10).to_string(index=False)) |
📤 実行結果 (先頭 10 行):
💬 sort_values は実は「47! 通りの順列のうち、 文字列の昇順となる 1 つ」を選び出す操作。 ソートアルゴリズムは「順列空間を効率よく探索する」装置とも言える。
フィッシャー・イェーツ (Fisher–Yates) アルゴリズムは、 47! 通りの順列を等確率で 1 つサンプリングする標準手法。 計算量は $O(n)$ で、 47 都道府県でも瞬時。 numpy の np.random.permutation がこれを実装している。
1 2 3 4 5 6 7 8 9 10 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1) df_2022 = df[df['年度'] == 2022].copy() names = df_2022['都道府県'].values shuffled = np.random.permutation(names) print('ランダムな並びの先頭 5 県:', shuffled[:5]) print('ランダムな並びの末尾 5 県:', shuffled[-5:]) |
📤 実行結果 (実行ごとに変わる):
💬 47! ≈ $2.6 \times 10^{59}$ 通りの 1 つを引き当てているが、 全ての並びは同確率。 これは順列検定や交差検証で「公平なシャッフル」を保証する基礎。
人口上位 5 県 (東京・神奈川・大阪・愛知・埼玉) のすべての並び順を列挙する。 5! = 120 通り。 itertools.permutations を使う。
1 2 3 4 5 6 7 | import itertools top5 = ['東京都', '神奈川県', '大阪府', '愛知県', '埼玉県'] perms = list(itertools.permutations(top5)) print(f'並び順の総数: {len(perms)}') print(f'先頭 3 通り: {perms[:3]}') print(f'末尾 3 通り: {perms[-3:]}') |
📤 実行結果:
💬 5! = 120 までは一覧化可能だが、 6 県 (720)、 7 県 (5,040)、 8 県 (40,320) と急増。 10 県 (3,628,800) でメモリに収まらなくなる。 列挙ではなくサンプリングへ移行する境目を理解する。
7 県を 1 列に並べるが「東京と神奈川は隣り合わない」という制約を入れる。 全順列 7! = 5,040 のうち、 隣り合うケースを引いて求める。 隣り合うケースは「東京-神奈川」を 1 個のかたまりとみて 6! × 2 = 1,440。 答えは 5,040 - 1,440 = 3,600。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import itertools cities = ['東京都', '神奈川県', '埼玉県', '千葉県', '茨城県', '栃木県', '群馬県'] total = list(itertools.permutations(cities)) forbidden = [ p for p in total if abs(p.index('東京都') - p.index('神奈川県')) == 1 ] allowed = len(total) - len(forbidden) print(f'全順列: {len(total)}') print(f'禁止 (東京-神奈川 隣接): {len(forbidden)}') print(f'許可される並び: {allowed}') |
📤 実行結果:
💬 「全体から禁止を引く」(余事象) は組合せの王道テクニック。 制約付き順列の問題は包除原理や bitmask DP で系統的に解ける。
「BANANA」を並べる場合、 B 1 個、 A 3 個、 N 2 個。 並べ方は $6!/(1! \cdot 3! \cdot 2!) = 60$ 通り。 SSDSE-B 文脈なら「47 県のうち 7 県は関東、 10 県は関西、 30 県はその他、 を 1 列に並べる地域別の並び」が同じ構造。 多項係数 $\binom{47}{7,10,30} = \frac{47!}{7!\,10!\,30!} \approx 5.3 \times 10^{16}$ 通り。
1 2 3 4 5 6 7 8 9 10 11 12 | import math # 同種要素を持つ順列 (多項係数) def multinomial(n, ks): result = math.factorial(n) for k in ks: result //= math.factorial(k) return result print(f'BANANA の並べ方: {multinomial(6, [1, 3, 2])}') print(f'関東 7 / 関西 10 / その他 30 の並べ方: ' f'{multinomial(47, [7, 10, 30]):,}') |
📤 実行結果:
💬 約 5.3 京通り。 これは「クロスバリデーションの分割パターン数」と同じ計算式。 多項係数は機械学習・統計の至るところに登場する。
SSDSE-B-2026 は 2012〜2023 年度の 12 年分を含む。 「都道府県の人口順位 (順列)」が 11 年間でどれだけ動いたかを Spearman 相関で測る。 ρ が 1 に近ければ「順列がほぼ固定」、 小さければ「順位が活発に動く」。
1 2 3 4 5 6 7 8 9 10 | import pandas as pd from scipy import stats df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) years = sorted(df['SSDSE-B-2026'].unique()) base = df[df['SSDSE-B-2026'] == years[0]].set_index('Prefecture')['A1101'].rank() for y in years[1:]: cur = df[df['SSDSE-B-2026'] == y].set_index('Prefecture')['A1101'].rank() rho, _ = stats.spearmanr(base.loc[cur.index], cur) print(f'{years[0]} -> {y}: 順位相関 rho = {rho:.4f}') |
📤 実行結果 (実値例):
💬 11 年間でも順位相関が 0.997 以上。 つまり「都道府県の人口順位という順列」は極めて安定。 一方で 0.001〜0.002 の微妙な揺らぎが下位県で発生しており、 これは少子高齢化による人口減少の地域差を反映している。 順列の安定性自体が分析対象になる例。
順列 ${}_n P_r = n! / (n-r)!$ を中心に、 上位概念 (数え上げ・組合せ論)、 並列概念 (組合せ ${}_n C_r$・重複順列・円順列)、 応用 (確率計算・暗号化・置換検定) を関係づけて整理する。
順列 (permutation) は $n$ 個の要素から $r$ 個を選んで並べる場合の数 $_nP_r = n!/(n-r)!$ で、 「順序を区別する」点が組合せ $_nC_r$ との決定的な違いである。 統計・データ解析の文脈では、 ランダム化検定 (permutation test) で帰無仮説下のラベル並べ替え分布を構成する、 機械学習の sklearn.inspection.permutation_importance で特徴量重要度を測る、 シーケンスデータの並び順依存性を検定するなど、 実は学術研究・実務応用・公的統計の全領域で横断的に使われる。 SSDSE-B-2026 で「47 都道府県の順位付け(出生率上位 5 県)」を全パターン列挙する例が典型的である。
順列は「数え上げ → 確率 → 推測統計」という流れの起点で、 隣接概念と層的に接続する。
順列の発想は「ランダム化」という統計推測の根本原理 (Fisher のランダム化原理) と直結しており、 RCT のランダム割付の論理的根拠でもある。
「順列」を実際に使うとき、 何をどう選ぶかを順に判断する。 上から順に答えていくと、 使うべき手法と評価の仕方が決まる。
順列と組合せの取り違えは、 「順序を区別するか」の 1 点で決まる。 問題文を読んだら、 まずそこを日本語で言い切ってから式を立てる。
前の節までは「n 個から k 個を選んで並べる」総数 nPk と、 その爆発的な増え方を扱いました。 本節ではその爆発量を逆向きに使います。 並べ替え検定(permutation test)で作れる「ラベルの並べ替え」の総数が、 得られる p 値の細かさ(分解能)そのものを決めてしまう という、 実データで検定を回すときにいちばん見落とされやすい読み方を掘り下げます。 姉妹ページ「組合せ」の解説深化が 検定回数(多重比較の分母) を扱うのに対し、 こちらは 1 回の並べ替え検定の中で p 値がどこまで小さくなれるか(分子・分母の粒度) という別角度です。
47 都道府県を 1 列に並べる方法は 47! ≈ 2.586 × 10⁵⁹ 通り(60 桁、実測計算値)で、 宇宙の全原子数すら軽く超えます。 並べ替え検定はこの「並べ替え」の力を統計に持ち込む手法です。 2 群を比べるとき、 データの値はそのままに どの県をどちらの群のラベルに割り当てるか を総当たりで貼り替え、 「観測された群差が、 ラベルをでたらめに貼り替えたときと比べてどれくらい珍しいか」を数えます。 つまり 並べ替え検定の帰無分布そのものが、 ラベルの順列(並べ替え)を数え上げて作られている のです。
SSDSE-B-2026 の 2023 年データ(実測値)で合計特殊出生率 A4103 を、 東北 6 県(青森 1.23 / 岩手 1.16 / 宮城 1.07 / 秋田 1.10 / 山形 1.22 / 福島 1.21、 平均 1.165)と 九州・沖縄 8 県(福岡 1.26 / 佐賀 1.46 / 長崎 1.49 / 熊本 1.47 / 大分 1.39 / 宮崎 1.49 / 鹿児島 1.48 / 沖縄 1.60、 平均 1.455)で比べると、 群平均差は 0.29 です。 この 14 県を 6 対 8 に貼り分けるラベルの割り当ては全部で C(14,6) = 3,003 通り。 全パターンを列挙する厳密並べ替え検定を回すと、 群差の絶対値が観測値 0.29 以上になる割り当ては 実際の分割とその左右反転の 2 つだけで、 両側 p 値 = 2 / 3003 = 0.000666(実測計算値)となりました。
| 2 群の大きさ | ラベル割り当ての数 C(n,k) | 両側 p 値の下限 = 2 / C |
|---|---|---|
| 3 対 3 | 20 | 0.100 |
| 5 対 5 | 252 | 0.00794 |
| 6 対 8(上の例) | 3,003 | 0.00067 |
| 10 対 10 | 184,756 | 0.0000108 |
3 対 3 では、 どんなに差が大きくても両側 p 値は 0.10 未満にできません。 有意水準 5% の壁を越えることすら不可能です。 これは検定の「弱さ」ではなく、 順列の総数が少ないぶん帰無分布の目盛りが粗い という組合せ論的な必然です(いずれも実測計算値)。
逆に群が大きくなると、 今度は並べ替えの数が爆発して全列挙が不可能になります。 47 県を 23 対 24 に分ける方法は C(47,23) ≈ 1.6 × 10¹³ 通り(実測計算値)。 これを 1 通りずつ数えるのは非現実的なので、 実務では この巨大な順列空間から乱数で数万通りだけを抽出するモンテカルロ並べ替え検定 に切り替えます。 このとき p 値は「抽出した並べ替えのうち観測値以上だった割合」になり、 慣例として分子・分母に +1 して (観測以上の回数 + 1) / (抽出回数 + 1) と計算します。 この「+1」があるため、 モンテカルロ版の p 値にも下限が残り、 B = 9,999 回抽出しても p の床は 1 / 10,000 = 0.0001 です。 厳密検定でもモンテカルロ検定でも、 「順列の数」が p 値の細かさを支配する という構図は変わりません。
組合せ 仮説検定 p 値 有意水準 多重検定 効果量 ブートストラップ (専用ページ未収録:並べ替え検定 / モンテカルロ法 はテキストのみ)