結論

中央値(median・メディアン)とは、データを小さい順に並べたときに真ん中に来る値です。個数が偶数なら、真ん中の2つの平均を使います。平均値と違い、極端に大きい値(外れ値)が1つ混ざってもほとんど動かないため、年収・滞在時間・レスポンスタイムのように偏りのあるデータの「ふつう」を表すのに向きます。

この記事でわかること

  • 中央値・平均値・最頻値の違いと、奇数個・偶数個の求め方
  • 外れ値1つで平均値と中央値がどれだけ動くかの実測
  • Python・NumPy・pandas・PostgreSQL・SQLite・JavaScriptで求めた実行結果とエラー原文

2026年10月29日更新|Python 3.13・PostgreSQL 16・SQLite 3.53・Node.js 22で実行確認

平均値・中央値・最頻値は、どれもデータの「真ん中あたり」を1つの数で表す代表値です。どれを使うかは、データの分布の形で決まります。

flowchart TD
    S[データの代表値を決めたい] --> Q1{数値のデータ?}
    Q1 -->|いいえ 好きな色など| MO[最頻値]
    Q1 -->|はい| Q2{左右対称に近く<br>外れ値が少ない?}
    Q2 -->|はい| ME[平均値]
    Q2 -->|いいえ 偏りや外れ値| MD[中央値]
    MD --> P[あわせて四分位数や<br>p95も見る]

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
中央値とは 小さい順に並べて真ん中に来る値。英語で median 中央値とは
中央値の求め方(奇数個) 並べ替えて (n+1)/2 番目の値 中央値の求め方
中央値の求め方(偶数個) 真ん中の2つ(n/2 番目と n/2+1 番目)の平均 中央値の求め方
平均値と中央値の違い 平均値は全部の値を使うので外れ値に引っ張られる。中央値は順番だけを使うので動きにくい 平均値・中央値・最頻値の違い
最頻値とは いちばん多く出てくる値(mode)。文字のデータにも使える 平均値・中央値・最頻値の違い
年収は平均と中央値のどちら? 中央値のほうが「ふつうの世帯」に近い。2024年の所得は平均575.2万円、中央値451万円 所得の平均値と中央値
Python・SQLで中央値 statistics.median・np.median・Series.median・percentile_cont(0.5) Python・SQL・JavaScriptの節
p50・p95とは p50は中央値。p95は「95%がこの値以下」になる値 p50・p95と性能監視

確認環境:Ubuntu 24.04.5 LTS(2コアの共有マシン)で、Python 3.13.18 の statistics、NumPy 2.5.3、pandas 3.0.5、Python に組み込みの SQLite 3.53.1 を実行しました。PostgreSQL 16.15 は作業用のフォルダーにデータベースを作って 127.0.0.1 で起動し、psql 16.15 から問い合わせています。JavaScript は Node.js 22.23.4 で実行しました。年収・滞在時間・レスポンスタイムのデータは、乱数の種(シード)を 152 に固定して自作したもので、実在の人や会社のデータではありません。所得の統計は厚生労働省「2025(令和7)年 国民生活基礎調査の概況」、代表値の定義は総務省統計局「なるほど統計学園」、関数の仕様は Python・NumPy・pandas・PostgreSQL・SQLite・MDN・Prometheus の公式ドキュメントで確かめました。図は mermaid 11.16.0 で描画を確かめています(確認日 2026年10月29日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「中央値とは?平均値・最頻値との違いと求め方をPython・SQLで実演」

中央値とは(意味と読み方)

この節では、中央値という言葉の意味と、ほかの呼び方が分かります。

中央値は、データを大きさの順に並べたとき、ちょうど真ん中に位置する値です。英語では median(メディアン)と呼び、統計の本では「メジアン」と書くこともあります。総務省統計局の「なるほど統計学園」は、中央値を「分布の中央にくる値」とし、中央値より大きい値の個数と小さい値の個数は同じになる、と説明しています。

たとえば5人のテストの点が 1・3・5・7・9 点なら、真ん中の3人目の 5 点が中央値です。値が何点かではなく、何番目かだけで決まるのが中央値の特徴です。そのため、いちばん上の人が 9 点でも 900 点でも、中央値は 5 点のまま変わりません。

1順番で決まる

値を小さい順に並べ、真ん中の位置の値を取る。値の大きさそのものは足し算に使わない。

2半分ずつに分ける線

中央値より下に半分、上に半分のデータがある。「上位50%の境目」と言い換えられる。

3外れ値に強い

1つだけ極端な値が混ざっても動きにくい。Python の公式ドキュメントも「外れ値の影響を受けにくい」と説明している。

統計やデータ分析では、中央値は「50パーセンタイル」や「第2四分位数(Q2)」とも呼ばれます。性能監視の画面で見かける p50 も同じものです。呼び方が違うだけで、どれも「半分がこの値以下」になる点を指しています。

平均値・中央値・最頻値の違い(一覧表)

この節では、3つの代表値がそれぞれ何を表し、どこが違うのかが一覧で分かります。

平均値は「全部を足して個数で割った値」、中央値は「真ん中の順位の値」、最頻値は「いちばん多く出てくる値」です。同じデータでも、分布が左右対称でなければ3つは別々の数になります。

代表値 求め方 外れ値の影響 向くデータ
平均値(mean) 合計 ÷ 個数 受けやすい(1つで大きく動く) 左右対称に近い数値(身長・測定誤差)
中央値(median) 並べ替えて真ん中の値 受けにくい 偏りのある数値(年収・待ち時間・レスポンスタイム)
最頻値(mode) いちばん多い値(連続値は区間に分けて数える) ほぼ受けない カテゴリー(購入した商品・回答の選択肢)・山の位置を知りたいとき

手元で、同じデータから3つを求めた結果です。

import statistics as st
d = [2, 3, 3, 5, 5, 5, 8]
print(st.mean(d), st.median(d), st.mode(d))
4.428571428571429 5 5

平均値が合うとき

分布が左右対称で、山が1つ

合計に意味がある(売上の合計÷日数 など)

外れ値を除いたあと、または外れ値も含めた「総量」を知りたい

中央値が合うとき

右に裾が長い(一部の大きい値が平均を押し上げる)

「典型的な1人・1件」を知りたい

入力ミスや計測の異常値が混ざっている可能性がある

なるほど統計学園も、外れ値の影響は平均値がいちばん受けやすく、中央値は多くの場合それほど受けず、最頻値はまず受けない、と整理しています。データ数が多く山が1つで分布にゆがみがあると、3つの大小関係に違いが出ます。右に裾が長い分布では、多くの場合「最頻値 < 中央値 < 平均値」の順になります。

中央値の求め方(奇数個・偶数個の手順)

この節では、手計算で中央値を求める手順と、偶数個のときの扱いが分かります。

1

小さい順に並べ替える

並べ替えずに真ん中を取るのが、いちばん多い間違い。3, 7, 1, 9, 5 なら 1, 3, 5, 7, 9 にする。

すべての場合

2

個数 n を数える

空欄(欠損値)は数に入れない。0 と空欄は別物として扱う。

すべての場合

3

n が奇数なら (n+1)/2 番目

n=5 なら 3番目。1, 3, 5, 7, 9 の中央値は 5。

奇数個

4

n が偶数なら真ん中の2つの平均

n=6 なら 3番目と4番目。1, 3, 5, 7, 8, 9 なら (5+7)÷2=6。データに無い値が答えになることがある。

偶数個

手順をそのまま Python で確かめた結果です。偶数個のときに「データにある値」を返したい場合は、median_low(小さいほう)と median_high(大きいほう)が使えます。

import statistics as st
odd  = [3, 7, 1, 9, 5]
even = [3, 7, 1, 9, 5, 8]
print(sorted(odd), sorted(even))
for d in (odd, even):
    print(st.mean(d), st.median(d), st.median_low(d), st.median_high(d))
[1, 3, 5, 7, 9] [1, 3, 5, 7, 8, 9]
5 5 5 5
5.5 6.0 5 7

偶数個の中央値 6.0 は、データの中にはない値です。人数や個数のように「途中の値が意味を持たない」データで、実在する値を答えたいときは median_low か median_high を選びます。SQL の percentile_disc も同じ考え方で、データにある値を返します(後の節で実行結果を示します)。

計算の手間と並べ替え

平均値は足し算を1回通すだけで求まりますが、中央値は並べ替え(または真ん中を探す選択のアルゴリズム)が必要です。データが数件なら差はありませんが、数千万件になると時間の差が出ます。手元で1,000万件の乱数を使って測った結果は、Python の節で示します。

外れ値で平均値と中央値はどれだけ変わるか(実測)

この節では、極端な値が1つ混ざったとき、平均値と中央値がどれだけ動くかが数字で分かります。

10人の年収(万円)を自作しました。320・350・380・400・420・450・480・520・560・600 です。この10人に、年収の高い人を1人だけ足したときの変化を測りました。

import numpy as np
inc = [320, 350, 380, 400, 420, 450, 480, 520, 560, 600]
print(np.mean(inc), np.median(inc))
for extra in [3000, 10000, 100000]:
    d = inc + [extra]
    print(extra, round(np.mean(d), 1), np.median(d))
d = inc[:-1] + [6000]   # 600 を 6000 と打ち間違えた
print('typo', np.mean(d), np.median(d))
448.0 435.0
3000 680.0 450.0
10000 1316.4 450.0
100000 9498.2 450.0
typo 988.0 435.0
11人目の年収 平均値 中央値 平均値を下回る人
(10人のまま) 448.0万円 435.0万円 10人中5人
3,000万円 680.0万円 450.0万円 11人中10人
1億円 1,316.4万円 450.0万円 11人中10人
10億円 9,498.2万円 450.0万円 11人中10人
21倍平均値の変化10億円の人を1人足すと 448→9,498.2万円
+15万円中央値の変化同じ1人で 435→450万円。何億円でも同じ
2.2倍打ち間違い1件600を6000と入力すると平均は988万円、中央値は435万円のまま

中央値は11人になって真ん中の位置が1つずれたため、435万円から450万円に変わりました。しかし11人目が3,000万円でも10億円でも中央値は450万円で同じです。中央値は「1人の値がどれだけ極端か」を見ず、「上に何人いるか」だけを見るためです。

一方、入力ミスのような異常値も中央値は隠してしまいます。打ち間違いを見つけたいときは、中央値だけでなく最大値・最小値や後で説明する箱ひげ図も見ます。

滞在時間のような右に長いデータ

Webページの滞在時間のように、多くの人は数十秒で離れ、一部の人だけが開いたまま30分〜1時間放置するデータも自作しました(1,000件。950件は平均40秒の指数分布、50件は1,800〜3,600秒)。

stay mean 171.2 median 27.9

平均値は171.2秒(約3分)ですが、中央値は27.9秒でした。「平均滞在時間は約3分」という報告は、950人の典型的な行動(30秒弱)とは大きく離れています。上位記事にもある「SaaS のログイン時間の平均を基準に一律の施策を打って外した」という失敗は、この形のデータで起きます。

所得の平均値と中央値(国民生活基礎調査)

この節では、公的な統計で平均値と中央値がどれだけ違うか、そして分布の形と3つの代表値の関係が分かります。

厚生労働省「2025(令和7)年 国民生活基礎調査の概況」によると、2024年の1世帯当たり平均所得金額は575万2千円、所得の中央値は451万円です。平均所得金額以下の世帯は61.5%で、所得金額の階級でいちばん多かったのは「200〜300万円未満」の13.5%でした。

代表値 2024年の所得(全世帯) 読み方
平均値 575万2千円 高所得の世帯に引き上げられている
中央値 451万円 全世帯を並べたときの真ん中の世帯
最も多い階級(最頻値に近い考え方) 200〜300万円未満(13.5%) 山がいちばん高い位置
平均以下の世帯の割合 61.5% 平均値が「半分の線」になっていない

出典:厚生労働省「2025(令和7)年 国民生活基礎調査の概況」Ⅱ 各種世帯の所得等の状況(表7・図9)。所得は2024年1月1日〜12月31日の1年間の額です(2026年10月29日時点で公表されている最新の概況)。

「最頻値 < 中央値 < 平均値」の順に並んでおり、右に裾が長い分布の典型です。平均値は「真ん中の世帯」よりも約124万円高く、6割以上の世帯が平均を下回ります。世帯の所得を「ふつうはどのくらいか」で語るなら中央値が向きます。一方、国全体の所得の総額を世帯数で割った値が欲しい(総量の話をする)なら平均値が正しい指標です。どちらが正しいかではなく、問いに合う代表値を選ぶことが大切です。

同じ形のデータを自作して確かめる

年収に近い形(対数正規分布)のデータを1,000人分作り、同じ関係になるかを確かめました。

import numpy as np
rng = np.random.default_rng(152)
x = np.round(rng.lognormal(mean=np.log(450), sigma=0.55, size=1000))
print(round(x.mean(), 1), np.median(x), round((x < x.mean()).mean() * 100, 1))
h, edges = np.histogram(x, bins=np.arange(0, int(x.max()) + 100, 100))
i = h.argmax(); print(edges[i], edges[i + 1], h[i])
538.2 463.0 60.8
300 400 181

平均値538.2万円、中央値463万円、平均を下回る人は60.8%、いちばん多い区間は300〜400万円(181人)でした。公的統計と同じく「最頻の区間 < 中央値 < 平均値」の順になり、平均以下が6割前後になる点も一致しました。

山が2つあるときは中央値も「ふつう」を表さない

中央値は万能ではありません。30前後と70前後に山が2つあるデータ(各500件)を作ると、次のようになりました。

bimodal mean 50.0 median 48.8 count within 45-55 0

平均値も中央値も50前後ですが、45〜55の範囲に入るデータは0件でした。山が2つある(二極化している)データでは、平均値も中央値も「誰もいない場所」を指します。この場合は、ヒストグラムで山の位置(最頻値)を見て、2つのグループに分けて考えます。

Pythonで中央値を求める(statistics・NumPy・pandas)

この節では、Python の3つの方法で中央値を求めたときの結果の違いと、欠損値・速さの差が分かります。

方法 書き方 欠損値(NaN) 向く場面
statistics(標準ライブラリ) statistics.median(d) 結果が狂う(公式が除去を推奨) 数件〜数万件・追加のインストール無し
NumPy np.median(a) nan を返す。np.nanmedian で除外 大量の数値・配列の計算
pandas df["列"].median() 既定で除外(skipna=True) 表のデータ・グループごとの集計
import numpy as np, pandas as pd
print(np.median([3, 7, 1, 9, 5]), np.median([3, 7, 1, 9, 5, 8]))
print(np.median([1, 2, np.nan, 4]), np.nanmedian([1, 2, np.nan, 4]))
s = pd.Series([1, 2, None, 4, 100])
print(s.median(), s.mean(), s.median(skipna=False))
print(pd.Series([2, 3, 3, 5, 5]).mode().tolist())
5.0 6.0
nan 2.0
3.0 26.75 nan
[3, 5]

NumPy の np.median は NaN が1つでもあると nan を返し、np.nanmedian は NaN を除いて 2.0 を返しました。pandas の median() は既定で欠損値を除くので 3.0 です。最頻値は、pandas の mode() が同数の値を全部返すのに対し、Python 3.8 以降の statistics.mode は最初に出てきた1つだけを返します(公式ドキュメントの変更点に記載)。同数を全部知りたいときは statistics.multimode を使います。

import statistics as st
print(st.mode([1, 1, 2, 2, 3]), st.multimode([1, 1, 2, 2, 3]))
1 [1, 2]

describe で平均と中央値を並べて見る

pandas の describe() は、平均値(mean)と中央値(50%)を同時に出します。この2つが大きく離れていたら、外れ値か偏りがあるサインです。

count      4.000000
mean      26.750000
std       48.849258
min        1.000000
25%        1.750000
50%        3.000000
75%       28.000000
max      100.000000
dtype: float64

グループごとの中央値(groupby)

df = pd.DataFrame({'dept': list('AAABBBB'),
                   'sal':  [400, 420, 2000, 380, 390, 410, 430]})
print(df.groupby('dept')['sal'].agg(['mean', 'median', 'count']))
       mean  median  count
dept
A     940.0   420.0      3
B     402.5   400.0      4

部署Aは1人の2,000万円で平均値が940万円になりますが、中央値は420万円で部署Bとほぼ同じです。部署ごとの比較で平均値だけを見ると、「部署Aは給与が高い」という誤った結論になります。

速さの実測(1万件〜1,000万件)

同じ乱数の配列で、平均値と中央値の計算時間を測りました(各5回の中央値、1,000万件は3回の中央値。ミリ秒)。2コアの共有マシンなので、絶対値ではなく比で見ます。

件数 NumPy 平均 np.median pandas median statistics.median
1万件 0.01 0.21 0.23 1.30
100万件 0.33 14.38 19.70 283.16
1,000万件 9.39 158.80 275.10 4,850.17

1,000万件では、NumPy の中央値は平均値の約17倍の時間がかかりました。statistics.median は Python のリストを並べ替えるため、NumPy の約30倍(約4.9秒)かかっています。大量のデータは NumPy か pandas で計算します。参考までに、同じ配列を np.sort で全部並べ替える時間は140.15ミリ秒で、この環境では np.median とほぼ同じでした。

配列の作り方や演算の基本は関連記事「pandasの使い方 逆引き81選|pandas 3.0の実行結果つき」で扱っています。

SQLで中央値を求める(PostgreSQL・SQLite)

この節では、SQL に MEDIAN 関数が無いデータベースでの書き方と、実際に出たエラーの原文が分かります。

PostgreSQL には median という関数がなく、percentile_cont(0.5) WITHIN GROUP (ORDER BY 列) で中央値を求めます。SQLite は、3.51.0 以降で特定のコンパイル設定(SQLITE_ENABLE_PERCENTILE)を有効にした場合に median() が使えます。

CREATE TABLE salary(dept text, amount int);
INSERT INTO salary VALUES ('A',400),('A',420),('A',2000),
  ('B',380),('B',390),('B',410),('B',430),('B',NULL);

SELECT dept, avg(amount)::numeric(10,1) AS mean,
  percentile_cont(0.5) WITHIN GROUP (ORDER BY amount) AS median_cont,
  percentile_disc(0.5) WITHIN GROUP (ORDER BY amount) AS median_disc,
  count(amount) AS n
FROM salary GROUP BY dept ORDER BY dept;
 dept | mean  | median_cont | median_disc | n
------+-------+-------------+-------------+---
 A    | 940.0 |         420 |         420 | 3
 B    | 402.5 |         400 |         390 | 4
(2 rows)

部署Bは NULL を除いた4件(380・390・410・430)です。percentile_cont は真ん中の2つを補間して 400、percentile_disc は「データにある値」から 390 を返しました。PostgreSQL の公式ドキュメントは、これらの関数が NULL を無視すること、percentile_cont が必要に応じて隣の値の間を補間することを明記しています。四分位数は配列で一度に求められ、最頻値は mode() で求まります。

SELECT percentile_cont(ARRAY[0.25,0.5,0.75]) WITHIN GROUP (ORDER BY amount) FROM salary;
SELECT mode() WITHIN GROUP (ORDER BY v) FROM (VALUES (2),(3),(3),(5),(5)) t(v);
       q
---------------
 {395,410,425}
(1 row)

 mode
------
    3
(1 row)

mode() は同数の値が複数あるとき、並べ替えた順で最初のもの(ここでは 3)を返します。公式ドキュメントにも「同じ頻度の値が複数あれば最初のものを任意に選ぶ」とあります。

PostgreSQLで出たエラー

ERROR:  function median(integer) does not exist
LINE 1: SELECT median(amount) FROM salary;
               ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

ERROR:  OVER is not supported for ordered-set aggregate percentile_cont
LINE 1: SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY amount) O...

1つ目は、ほかのデータベースの MEDIAN 関数をそのまま書いたときのエラーです。2つ目は、各行の横に中央値を並べようとして OVER を付けたときのエラーで、percentile_cont はウィンドウ関数として使えません。各行に並べたいときは、部署ごとの中央値を副問い合わせ(または CTE)で求めて JOIN します。

SQLiteの median() と、使えない版での書き方

Python に組み込まれた SQLite 3.53.1 で実行した結果です。このビルドは ENABLE_PERCENTILE 付きでした(PRAGMA compile_options で確認)。

SELECT dept, avg(amount), median(amount) FROM salary GROUP BY dept;
SELECT percentile_cont(amount, 0.5), percentile_disc(amount, 0.5), percentile(amount, 50) FROM salary;
[('A', 940.0, 420.0), ('B', 402.5, 400.0)]
[(410.0, 410.0, 410.0)]

SQLite の関数は引数の順が PostgreSQL と違い、percentile_cont(列, 0.5) の形です。percentile(列, 50) だけは 0〜100 で指定します。公式ドキュメントによると、この4つの関数はウィンドウ関数としても使えます(PostgreSQL と違い median(amount) OVER (PARTITION BY dept) が動きました)。

median() を持たない SQLite のビルドや、ほかのデータベースでも動く書き方は、ウィンドウ関数で順位を付けて真ん中の行だけ平均する方法です。

WITH r AS (
  SELECT dept, amount,
         ROW_NUMBER() OVER (PARTITION BY dept ORDER BY amount) AS rn,
         COUNT(*)     OVER (PARTITION BY dept)                 AS cnt
  FROM salary WHERE amount IS NOT NULL)
SELECT dept, AVG(amount) AS median
FROM r
WHERE rn IN ((cnt + 1) / 2, (cnt + 2) / 2)
GROUP BY dept;
[('A', 420.0), ('B', 400.0)]

整数の割り算を使い、奇数個なら同じ行を2回、偶数個なら真ん中の2行を選んで平均しています。WHERE amount IS NOT NULL を忘れると NULL が順位に入り、真ん中がずれます。なお、関数が無いビルドで呼ぶと no such function のエラーになります。手元で mode() を呼んだときの原文は sqlite3.OperationalError: no such function: mode でした。

AVG・SUM・COUNT と NULL の扱いは、関連記事「SQL集計関数一覧|SUM・AVG・MAX・MIN・COUNTとNULLの扱い」で扱っています。

JavaScriptで中央値を求める(sortの落とし穴)

この節では、JavaScript に中央値の関数が無いことと、自作するときに必ず踏む sort() の落とし穴が分かります。

JavaScript の sort() は、比較関数を渡さないと数値を文字列として並べ替えます。MDN は「既定のソート順は昇順で、要素を文字列に変換してから、UTF-16 コード単位の値の並びとして比較」すると説明しています。中央値の関数を自作するとき、これで答えが変わります。

const data = [10, 9, 100, 2];
console.log([...data].sort());
console.log([...data].sort((a, b) => a - b));

function medianWrong(arr) {
  const s = arr.sort();                 // 比較関数なし・元の配列を書き換える
  const m = Math.floor(s.length / 2);
  return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
}
function median(arr) {
  if (arr.length === 0) return NaN;
  const s = [...arr].sort((a, b) => a - b);   // コピーして数値で並べる
  const m = Math.floor(s.length / 2);
  return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
}
const d2 = [10, 9, 100, 2];
console.log('wrong:', medianWrong(d2), 'and original now', d2);
console.log('right:', median([10, 9, 100, 2]), median([3, 7, 1, 9, 5]), median([]));
console.log('strings:', median(['10', '9', '100', '2']));
[ 10, 100, 2, 9 ]
[ 2, 9, 10, 100 ]
wrong: 51 and original now [ 10, 100, 2, 9 ]
right: 9.5 5 NaN
strings: 455

比較関数を付けない版は 51 を返し(正しくは 9.5)、しかも元の配列の順番まで書き換えました。もう1つの落とし穴は、文字列のまま渡したときです。並べ替えは a - b で数値として比べられますが、最後の s[m - 1] + s[m] が文字列の連結になり、"9" + "10" = "910" を2で割って 455 という値が出ました。フォームや CSV から読んだ値は Number() で数値にしてから渡します。元の配列を変えたくないときは、コピーするか toSorted() を使います(Node.js 22 で動作を確認)。

並べ替え

やってはいけないarr.sort() をそのまま使う。[10, 9, 100, 2] が [10, 100, 2, 9] になる
→
代わりに[...arr].sort((a, b) => a - b) か arr.toSorted((a, b) => a - b)

比較関数とコピーの2つをそろえる

入力の型

やってはいけないinput の value や CSV の文字列をそのまま渡す。足し算が文字の連結になる
→
代わりにarr.map(Number) で数値にし、Number.isNaN で空欄を除いてから計算する

Python の CSV でも同じ問題が起きる(よくあるエラーの節)

四分位数・箱ひげ図と中央値の関係

この節では、中央値を拡張した四分位数と、箱ひげ図の読み方、ツールごとの四分位数の違いが分かります。

四分位数は、データを4等分する3つの境目です。下から25%の位置が第1四分位数(Q1)、50%が第2四分位数(Q2)=中央値、75%が第3四分位数(Q3)です。Q3−Q1 を四分位範囲(IQR)と呼び、真ん中の半分のデータがどのくらいの幅に散らばっているかを表します。

箱ひげ図は、最小値・Q1・中央値・Q3・最大値(5数要約)を1本の図にしたものです。箱の中の線が中央値で、箱の幅が IQR です。多くのツールでは、Q1−1.5×IQR より小さいか Q3+1.5×IQR より大きい値を外れ値として点で描きます。前の節の年収1,000人のデータで5数要約を計算しました。

q1, q2, q3 = np.percentile(x, [25, 50, 75]); iqr = q3 - q1
lo, hi = q1 - 1.5 * iqr, q3 + 1.5 * iqr
print(x.min(), q1, q2, q3, x.max(), iqr, lo, hi, int(((x < lo) | (x > hi)).sum()))
84.0 314.0 463.0 660.0 3531.0 346.0 -205.0 1179.0 47
値 年収1,000人(万円) 意味
最小値 84 ひげの下端(外れ値が無い側)
Q1(25%) 314 箱の下端
Q2(中央値) 463 箱の中の線
Q3(75%) 660 箱の上端
上のしきい値 1,179 Q3+1.5×IQR。これを超えた47人が点で描かれる

中央値463から Q1 までは149、Q3 までは197で、箱の上半分のほうが長くなっています。箱ひげ図で中央値の線が箱の下寄りにあるときは、右に裾が長い分布です。

四分位数はツールで値が違う

中央値はどのツールでも同じ値になりますが、Q1・Q3 は計算方法(補間のしかた)によって変わります。1〜10 の10個で確かめました。

方法 Q1 Q2(中央値) Q3
statistics.quantiles(既定 exclusive) 2.75 5.5 8.25
statistics.quantiles(inclusive) 3.25 5.5 7.75
np.percentile(既定 linear)・pandas quantile 3.25 5.5 7.75
np.percentile(lower/higher) 3/4 5/6 7/8
np.percentile(midpoint) 3.5 5.5 7.5

Python の statistics.quantiles は既定が exclusive で、NumPy・pandas の既定(linear)とは Q1・Q3 が違いました。PostgreSQL の percentile_cont は linear と同じ考え方です(前の節の {395,410,425})。レポートで四分位数を出すときは、どの方法で計算したかを書き添えます。lower・higher の Q2 が 5・6 になるのは、偶数個のときに補間せず下(上)の値を取るためで、median_low・median_high と同じ動きです。

p50・p95とは:性能監視での中央値(レスポンスタイム)

この節では、システムの監視で使う p50・p95・p99 の意味と、平均値で見てはいけない理由が実測で分かります。

p50 は50パーセンタイル=中央値、p95 は「95%のリクエストがこの時間以内に終わった」値です。レスポンスタイムは、ほとんどが速く、一部だけが極端に遅い形になりやすく、平均値では「速い大多数」も「遅い一部」もどちらも見えなくなります。

10,000件のうち98%(9,800件)は80ミリ秒前後、2%(200件)はタイムアウト寸前の2〜5秒、というデータを作って測りました。

rt = np.concatenate([rng.lognormal(np.log(80), 0.3, 9800),
                     rng.uniform(2000, 5000, 200)])
print(rt.mean(), np.percentile(rt, [50, 95, 99]))
80.9msp50(中央値)典型的な1件の速さ
151.6ms平均値典型より約2倍遅く見える
3,383msp99100件に1件はこれより遅い

平均値の151.6ミリ秒は、9,800件の典型(約81ミリ秒)より遅く、200件の遅いリクエスト(2秒以上)よりははるかに速い、どちらの利用者にも当てはまらない値です。速さの典型は p50、遅い人の体験は p95・p99 で見るのが監視の基本です。SLO(サービスレベル目標)でも「p99 が 300ms 以下」のようにパーセンタイルで目標を書くことがよくあります。目標の決め方は関連記事「SREとは|SLI・SLO・SLAの違いとエラーバジェット計算」で扱っています。

中央値は平均できない(サーバー2台の例)

監視でよくある間違いは、サーバーごとの p50・p95 を平均して「全体の p50・p95」とすることです。9,000件を処理する速いサーバーAと、1,000件だけ処理する遅いサーバーBで確かめました。

指標 サーバーA サーバーB 2台の値の平均 全件から計算
p50 79.8ms 298.2ms 189.0ms 83.4ms
p95 130.8ms 504.2ms 317.5ms 298.2ms

2台の中央値を平均すると189.0ミリ秒ですが、全件の本当の中央値は83.4ミリ秒でした。件数の違うグループの中央値は、足して割っても全体の中央値になりません。全体の値が欲しいときは、元のデータか、合算できる形(ヒストグラムのバケットの件数)から計算し直します。

ヒストグラムから推定した p95 はずれることがある

Prometheus の histogram_quantile() は、全件を保存せず「バケット(区間)ごとの件数」から分位数を推定します。公式ドキュメントによると、分位数がバケットの境目に無いときはバケットの中で線形補間し、バケット内では値が均等に散らばっていると仮定します。同じ10,000件を、クライアントライブラリでよく使われる区切り(0.005〜10秒の11区間)に入れて、同じ計算で推定しました。

0.5 exact 80.9 bucket est 82.0
0.95 exact 141.9 bucket est 230.5
0.99 exact 3383.3 bucket est 3466.3

p50 はほぼ合いましたが、p95 は本当は141.9ミリ秒なのに230.5ミリ秒と推定されました。p95 が 0.1〜0.25秒という幅の広いバケットに入ったためです。ヒストグラムで p95 を監視するなら、目標値(SLO のしきい値)の近くに細かいバケットの境目を置きます。PromQL の書き方や監視の構築は関連記事「Prometheus Grafana入門|監視構築とPromQL一覧」で扱っています。

移動中央値はスパイクを消す

1回だけ飛び抜けた値(スパイク)があるグラフを、3点の移動平均と移動中央値でならした結果です。

            0      1      2      3       4       5       6      7      8      9
v       100.0  102.0   98.0  101.0  3000.0    99.0   103.0  100.0   97.0  101.0
rmean3    NaN    NaN  100.0  100.3  1066.3  1066.7  1067.3  100.7  100.0   99.3
rmed3     NaN    NaN  100.0  101.0   101.0   101.0   103.0  100.0  100.0  100.0

移動平均は1回のスパイクで3点続けて1,000を超えましたが、移動中央値(rolling(3).median())は100前後のままでした。センサーの値のノイズ除去や、監視のアラートが一瞬のスパイクで鳴りすぎるのを防ぐのに使えます。逆に、スパイクそのものを見つけたいときは移動中央値では消えてしまうので、最大値も並べて見ます。

度数分布表(階級)から中央値を求める

この節では、元のデータが無く「〇〜〇点が何人」という表しか無いときの中央値の求め方が分かります。

統計の授業や公的統計では、値が区間(階級)ごとの人数で示されることがあります。このときは、中央値が入る階級を見つけ、その階級の中で値が均等に並んでいると考えて比例配分します。

点数の階級 人数 累積人数
0〜10点未満11
10〜20点未満23
20〜30点未満47
30〜40点未満512
40〜50点未満1022(20人目が入る)
50〜60点未満628
60〜70点未満1139
70〜80点未満140
1

真ん中の順位

40人なので n÷2=20人目。

n/2 を使う

2

入る階級

累積が12→22になる 40〜50点の階級。手前までの累積は12人。

累積人数で探す

3

階級の中で比例配分

40+(20−12)÷10×10=48点。

下限+(n/2−手前の累積)÷その階級の人数×幅

4

Pythonで確かめる

statistics.median_grouped(階級値のリスト, interval=10) も 48.0。

階級値の単純な中央値は45.0

import statistics as st
bins = [(0,10,1),(10,20,2),(20,30,4),(30,40,5),(40,50,10),(50,60,6),(60,70,11),(70,80,1)]
data = [(L + U) / 2 for (L, U, f) in bins for _ in range(f)]   # 階級値を人数分
print(st.median_grouped(data, interval=10), st.median(data))
48.0 45.0

階級値(各階級の真ん中の値)を並べて普通の中央値を取ると45.0になり、比例配分した48.0とずれます。階級の幅が広いほど差は大きくなります。なるほど統計学園では、階級の真ん中の値を使う最頻値の求め方(最も人数の多い階級の階級値)も紹介されています。この表なら60〜70点未満の11人がいちばん多いので、最頻値は65点です。

中央値と平均値の使い分け(判断表)

この節では、手元のデータでどの代表値を使えばよいかを、問いの種類から決められます。

  1. 1数値でない(カテゴリー) → 最頻値好きな色・流入経路・購入した商品。平均も中央値も計算できない
  2. 2順序だけに意味がある → 中央値5段階の満足度・ランク。間の差が等しい保証が無い
  3. 3合計や総量を知りたい → 平均値売上合計÷日数、1人あたりの費用。外れ値も含めた総量が答え
  4. 4典型的な1人・1件を知りたい → 中央値年収・家賃・滞在時間・レスポンスタイム
  5. 5左右対称で外れ値が少ない → 平均値(中央値とほぼ同じ)身長・製品の寸法・測定誤差。平均値は標準偏差と組み合わせやすい
  6. 6山が2つある → 代表値を1つにしないグループに分ける。ヒストグラムで山の位置を見る
  7. 7遅い一部の体験が問題 → p95・p99中央値(p50)とセットで見る
場面 使う代表値 理由
世帯年収・貯蓄額中央値(+平均値)一部の高額で平均が上がる。2024年の所得は平均以下が61.5%
テストの平均点平均値(+中央値)分布が対称に近ければ平均で良い。0点の欠席者がいるなら中央値も
アンケートの5段階評価中央値・各選択肢の割合順序データ。手元の例は平均3.7・中央値4.5・最頻値5
Webの滞在時間中央値放置タブで平均が伸びる。手元の例は平均171.2秒・中央値27.9秒
APIのレスポンスタイムp50・p95・p99平均値は典型にも遅い一部にも合わない
月の売上の見込み平均値合計を出したい(日次平均×日数)
人気の商品・サイズ最頻値在庫や品ぞろえは「いちばん多いもの」で決める
センサーのノイズ除去移動中央値1回のスパイクを消せる

迷ったら、平均値と中央値の両方を出して比べるのが確実です。2つが近ければ分布は対称に近く、どちらを使っても大きく外れません。大きく離れていれば偏りか外れ値があるので、ヒストグラムか箱ひげ図で形を確かめます。確率分布ごとの平均と中央値の関係は関連記事「確率分布とは|種類一覧・平均と分散・選び方をPythonで実演」で扱っています。

  • 1平均値と中央値を並べた大きく離れていたら、分布の偏りか外れ値がある
  • 2件数と欠損値の数を書いたNULL・NaN・空欄を除いたかどうかで中央値が変わる
  • 3ヒストグラムか箱ひげ図で形を見た山が2つなら、代表値1つでまとめない
  • 4最大値・最小値を見て入力ミスを探した中央値は打ち間違いを隠してしまう
  • 5グループの中央値を平均していない全体の中央値は全件から計算し直す
  • 6四分位数の計算方法を書いたexclusive と linear で Q1・Q3 が違う

中央値でよくある間違いとエラー(原文つき)

この節では、中央値を計算するときに実際に出たエラーと、間違った値が出る原因が分かります。

出た表示・結果 どこで 原因 直し方
statistics.StatisticsError: no median for empty data Python statistics 空のリストを渡した(絞り込みで0件) 件数を先に確かめ、0件なら別の扱いにする
TypeError: '<' not supported between instances of 'str' and 'int' Python statistics 数値と文字列が混ざっている float() などで数値にそろえる
RuntimeWarning: Mean of empty slice と nan np.median([]) 空の配列。エラーにならず nan が返る len を確かめる。nan のまま集計に混ぜない
nan が返る np.median NaN が1つでも入っている np.nanmedian を使う
TypeError: Cannot perform reduction 'median' with string dtype pandas 3.0 CSV の "1,200" のようなカンマ付きの数字で列全体が文字列になった カンマを除いて pd.to_numeric で数値にする(直したあとは410.0)
ERROR: function median(integer) does not exist PostgreSQL median 関数が無い percentile_cont(0.5) WITHIN GROUP (ORDER BY 列)
ERROR: OVER is not supported for ordered-set aggregate percentile_cont PostgreSQL ウィンドウ関数として使おうとした グループごとに求めて JOIN する
no such function SQLite median() の無いビルド(ENABLE_PERCENTILE 無し) ROW_NUMBER と COUNT のウィンドウ関数で書く
51(正しくは9.5) JavaScript sort() に比較関数を渡していない sort((a, b) => a - b)
455(正しくは9.5) JavaScript 文字列の配列で、最後の足し算が連結になった map(Number) で数値にする

pandas 3.0 では、文字列の列が専用の文字列型(StringDtype)になり、median を呼ぶとはっきりエラーになりました。手元で確かめた読み込みの例です。

import pandas as pd, io
df = pd.read_csv(io.StringIO('amount\n400\n420\n"1,200"\n380\n'))
print(df.dtypes.to_dict())
df['amount'].median()      # TypeError
df['amount'] = pd.to_numeric(df['amount'].str.replace(',', ''))
print(df['amount'].median())
{'amount': <StringDtype(storage='python', na_value=nan)>}
TypeError: Cannot perform reduction 'median' with string dtype
410.0

エラーにならない間違い

次の間違いはエラーが出ないまま、もっともらしい値が返るので気づきにくいものです。

  • 並べ替えずに真ん中を取る:[3, 7, 1, 9, 5] の3番目は 1 だが、中央値は 5。
  • 文字列のまま並べ替える:Python でも ['10', '9', '100', '2'] の median_low は '100' になった(文字の順で '10'・'100'・'2'・'9' と並ぶため)。
  • 欠損値を0で埋めてから中央値を取る:空欄は「0円」ではない。0を足すと中央値が下にずれる。
  • グループごとの中央値を平均する:前の節の例では189.0ミリ秒になり、本当の83.4ミリ秒の2倍以上だった。
  • NaN を含んだまま statistics.median に渡す:Python の公式ドキュメントは、NaN は比較の性質が特殊で結果が狂うため、先に取り除くよう書いている。
  • 平均値だけで「平均より下の人は半分」と考える:2024年の所得では平均以下が61.5%。半分になるのは中央値。

よくある質問

Q. 中央値とは簡単に言うと何ですか? A. データを小さい順に並べたとき、ちょうど真ん中に来る値です。半分のデータがそれより小さく、半分がそれより大きくなります。英語では median(メディアン)です。

Q. データが偶数個のとき、中央値はどうなりますか? A. 真ん中の2つの値の平均を使います。1, 3, 5, 7, 8, 9 なら (5+7)÷2=6 です。データにある値を使いたい場合は、Python の median_low・median_high や SQL の percentile_disc で小さいほう・大きいほうを選べます。

Q. 平均値と中央値はどちらを使えばよいですか? A. 典型的な1人・1件を知りたいなら中央値、合計や総量を知りたいなら平均値です。年収・滞在時間・レスポンスタイムのように一部の大きな値があるデータでは、中央値のほうが実感に近くなります。迷ったら両方を出して比べます。

Q. 平均値より中央値が小さいのはなぜですか? A. 一部の大きな値が平均値だけを押し上げているからです(右に裾が長い分布)。2024年の世帯所得は平均575.2万円、中央値451万円で、平均を下回る世帯が61.5%でした。

Q. 最頻値とは何ですか? A. データの中でいちばん多く出てくる値です。「好きな色」のような文字のデータにも使えます。連続した数値では同じ値がほとんど出ないため、区間(階級)に分けて、いちばん人数の多い区間の真ん中の値を使います。

Q. 中央値と平均値が同じになるのはどんなときですか? A. 分布が左右対称のときです。正規分布に近いデータ(身長・測定誤差など)では、平均値・中央値・最頻値がほぼ一致します。1, 3, 5, 7, 9 のような等間隔のデータでも同じ5になります。

Q. p50 と中央値は同じですか? A. 同じです。p50 は50パーセンタイルで、「50%がこの値以下」になる点です。第2四分位数(Q2)も同じものを指します。ただし、ヒストグラムのバケットから推定した p50 は、本当の中央値と少しずれることがあります。

Q. SQLで中央値を求めるには? A. PostgreSQL では percentile_cont(0.5) WITHIN GROUP (ORDER BY 列) を使います。median 関数はありません。SQLite は ENABLE_PERCENTILE 付きのビルド(3.51.0 以降)で median(列) が使え、無い場合は ROW_NUMBER と COUNT のウィンドウ関数で書けます。

Q. 中央値に弱点はありますか? A. あります。外れ値に強い反面、入力ミスのような異常値も隠してしまいます。また、山が2つあるデータでは、中央値も「誰もいない場所」を指すことがあります。合計を計算に使えない点も平均値との違いです。

Q. アンケートの5段階評価は平均と中央値のどちらで見ますか? A. 順序だけに意味があるデータなので、中央値か各選択肢の割合が向いています。手元の例(1, 2, 2, 3, 4, 5, 5, 5, 5, 5)では平均3.7、中央値4.5、最頻値5で、平均だけを見ると「5を付けた人が半分いる」ことが見えません。

Q. 中央値の計算は遅いのですか? A. 並べ替えか選択の処理が要るため、平均値より時間がかかります。手元の1,000万件では NumPy の平均値が9.39ミリ秒、中央値が158.80ミリ秒でした。Python の statistics.median はリストを並べ替えるので約4.9秒かかりました。

統計の検定まで進むなら、関連記事「p値とは|意味・有意水準・よくある誤解をPythonで実演」で扱っています。

まとめ

  • 中央値(median)は、小さい順に並べて真ん中に来る値。偶数個なら真ん中の2つの平均。並べ替えを忘れない。
  • 中央値は外れ値に強い。10人の年収に10億円の人を1人足すと、平均値は448万円→9,498.2万円になったが、中央値は450万円のままだった。
  • 年収・滞在時間・レスポンスタイムのような右に裾が長いデータでは「最頻値 < 中央値 < 平均値」になる。2024年の世帯所得は平均575.2万円、中央値451万円。
  • Python は statistics.median・np.median(NaN は nanmedian)・pandas の median(既定で欠損を除く)。PostgreSQL は percentile_cont(0.5)、SQLite は median()、JavaScript は比較関数付きの sort で自作する。
  • 監視では p50 と p95・p99 をセットで見る。グループごとの中央値は平均できず、ヒストグラムから推定した p95 はバケットの区切りによってずれる。

参考・出典

確認日はいずれも 2026年10月29日です。

  • 厚生労働省「2025(令和7)年 国民生活基礎調査の概況」:https://www.mhlw.go.jp/toukei/saikin/hw/k-tyosa/k-tyosa25/index.html
  • 厚生労働省「2025(令和7)年 国民生活基礎調査の概況」Ⅱ 各種世帯の所得等の状況:https://www.mhlw.go.jp/toukei/saikin/hw/k-tyosa/k-tyosa25/dl/03.pdf
  • 総務省統計局「なるほど統計学園」中心的な傾向を捉える:https://www.stat.go.jp/naruhodo/5_tokucho/chushin.html
  • Python「statistics — Mathematical statistics functions」:https://docs.python.org/3/library/statistics.html
  • NumPy「numpy.median」:https://numpy.org/doc/stable/reference/generated/numpy.median.html
  • NumPy「numpy.percentile」:https://numpy.org/doc/stable/reference/generated/numpy.percentile.html
  • pandas「pandas.Series.median」:https://pandas.pydata.org/docs/reference/api/pandas.Series.median.html
  • PostgreSQL 16「Aggregate Functions」(Ordered-Set Aggregate Functions):https://www.postgresql.org/docs/16/functions-aggregate.html
  • SQLite「The Percentile Extension」:https://sqlite.org/percentile.html
  • MDN「Array.prototype.sort()」:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
  • Prometheus「Query functions」(histogram_quantile):https://prometheus.io/docs/prometheus/latest/querying/functions/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「中央値とは?平均値・最頻値との違いと求め方をPython・SQLで実演」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

成長のヒミツ、 ぜんぶ丸わかり