データサイエンス必携!`numpy.percentile()`の基礎から応用まで
numpy.percentile()
とは
numpy.percentile()
は、NumPy ライブラリが提供する非常に便利な関数で、与えられた配列(または多次元配列)の指定されたパーセンタイルを計算します。パーセンタイルとは、データセットを100等分したときの特定の位置を示す値です。
例えば、25パーセンタイル(第一四分位点)はデータセットの25%がそれ以下の値であることを意味し、50パーセンタイル(中央値)はデータセットのちょうど真ん中の値を示します。
基本的な使い方
numpy.percentile()
の基本的な書式は以下の通りです。
numpy.percentile(a, q, axis=None, out=None, overwrite_input=False, method='linear', keepdims=False, interpolation='linear')
主要な引数は以下の2つです。
q
: 計算したいパーセンタイル値(0から100までの数値、または数値のリスト)。単一のパーセンタイルを求める場合は数値、複数のパーセンタイルを求める場合は数値のリストを指定します。a
: パーセンタイルを計算したい入力配列(NumPy 配列)。
例
簡単な例を見てみましょう。
import numpy as np
# 1次元配列
data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# 50パーセンタイル(中央値)を計算
median = np.percentile(data, 50)
print(f"50パーセンタイル (中央値): {median}") # 出力: 50パーセンタイル (中央値): 5.5
# 25パーセンタイルと75パーセンタイルを計算
q1_q3 = np.percentile(data, [25, 75])
print(f"25パーセンタイルと75パーセンタイル: {q1_q3}") # 出力: 25パーセンタイルと75パーセンタイル: [3.25 7.75]
# 多次元配列の場合
matrix = np.array([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])
# 配列全体の50パーセンタイル
overall_median = np.percentile(matrix, 50)
print(f"配列全体の50パーセンタイル: {overall_median}") # 出力: 配列全体の50パーセンタイル: 50.0
# axisを指定して、行ごと(axis=1)の50パーセンタイルを計算
row_medians = np.percentile(matrix, 50, axis=1)
print(f"行ごとの50パーセンタイル: {row_medians}") # 出力: 行ごとの50パーセンタイル: [20. 50. 80.]
# axisを指定して、列ごと(axis=0)の50パーセンタイルを計算
col_medians = np.percentile(matrix, 50, axis=0)
print(f"列ごとの50パーセンタイル: {col_medians}") # 出力: 列ごとの50パーセンタイル: [40. 50. 60.]
多次元配列の場合、axis
引数を指定することで、どの軸に沿ってパーセンタイルを計算するかを制御できます。
axis=1
: 各行に沿ってパーセンタイルを計算します。結果は、行の数と同じ要素数を持つ配列になります。axis=0
: 各列に沿ってパーセンタイルを計算します。結果は、列の数と同じ要素数を持つ配列になります。axis=None
(デフォルト): 配列全体を1次元のデータとして扱い、そのパーセンタイルを計算します。
-
interpolation
: パーセンタイルの計算方法を指定します。データが連続的でない場合(例えば、離散値の場合)に、パーセンタイルがちょうどデータ点の間にある場合にどのように値を決定するかを制御します。デフォルトは'linear'
ですが、'lower'
,'higher'
,'midpoint'
,'nearest'
などのオプションもあります。'linear'
(デフォルト): 2つのデータ点間の線形補間を使用します。'lower'
: 計算されたパーセンタイル位置以下の最大のデータ点を使用します。'higher'
: 計算されたパーセンタイル位置以上の最小のデータ点を使用します。'nearest'
: 最も近いデータ点を使用します。'midpoint'
:'lower'
と'higher'
の平均を使用します。
numpy.percentile()
は非常に便利ですが、使い方を誤ると意図しない結果になったり、エラーが発生したりすることがあります。ここでは、よくある問題とその解決策を説明します。
パーセンタイル値が期待と異なる
これは最もよくある問題の一つで、特に統計学的な定義とプログラミング言語の実装の違いから生じます。
-
トラブルシューティング:
interpolation
引数の確認と変更: 以下のいずれかを試してみてください。'linear'
(デフォルト): データ点間の線形補間'lower'
: 計算されたパーセンタイル位置以下の最大のデータ点'higher'
: 計算されたパーセンタイル位置以上の最小のデータ点'nearest'
: 最も近いデータ点'midpoint'
:'lower'
と'higher'
の平均'hazen'
,'weibull'
,'inverted_cdf'
,'averaged_inverted_cdf'
,'closest_observation'
,'interpolated_inverted_cdf'
,'median_unbiased'
,'normal_unbiased'
など、さらに多くのオプションがあります。NumPy の公式ドキュメントで詳細を確認し、期待する計算方法に最も近いものを選択してください。
- 小さいデータセットでの挙動の理解: データ点数が少ない場合、補間方法による結果の違いが顕著になります。サンプルサイズを増やして試すか、補間方法を慎重に選択してください。
- 期待するパーセンタイルの定義の確認: どのパーセンタイル定義が最も適切かを統計学的な観点から確認し、それに合わせて
interpolation
引数を調整します。
例:
import numpy as np data = np.array([1, 2, 3, 4, 5, 6, 7, 8]) # デフォルト (linear) print(f"25パーセンタイル (linear): {np.percentile(data, 25)}") # 2.75 # lower print(f"25パーセンタイル (lower): {np.percentile(data, 25, interpolation='lower')}") # 2.0 # midpoint print(f"25パーセンタイル (midpoint): {np.percentile(data, 25, interpolation='midpoint')}") # 2.5
-
原因:
- 補間方法 (
interpolation
引数):numpy.percentile()
は、パーセンタイルがデータ点とデータ点の間に落ちる場合に、どのように値を補間するかを決定するinterpolation
引数を持っています。デフォルトは'linear'
(線形補間) ですが、他のソフトウェアでは異なる補間方法(例: 最も近い値、中央値など)を使用している場合があります。 - パーセンタイルの定義: 統計学にはパーセンタイルの定義がいくつか存在します。NumPy のデフォルトの計算方法(線形補間)が、あなたが考えている定義と異なる可能性があります。特に、0% や 100% のパーセンタイルで問題が発生することがあります。
- 補間方法 (
-
エラー/問題:
np.percentile()
で計算したパーセンタイル値が、手計算や他のソフトウェア(Excelなど)で計算した値と異なる。
ValueError: Input array must be at least 1-d (入力配列が1次元以上である必要があります)
-
トラブルシューティング:
- 入力
a
が有効なNumPy配列であり、少なくとも1つの要素が含まれていることを確認してください。 - リストやタプルなどのPythonのシーケンスを渡すこともできますが、その場合も要素が含まれていることを確認してください。
例:
import numpy as np # 良い例 data1 = np.array([1, 2, 3]) print(np.percentile(data1, 50)) # 悪い例 (空の配列) # data2 = np.array([]) # print(np.percentile(data2, 50)) # ValueError # 悪い例 (スカラ) # data3 = 5 # print(np.percentile(data3, 50)) # ValueError (NumPy配列に変換される際に問題が生じる場合がある)
- 入力
-
原因:
numpy.percentile()
に空の配列や、NumPy 配列ではないスカラ値などを渡した場合に発生します。 -
エラー:
ValueError: Input array must be at least 1-d
TypeError: 'list' object cannot be interpreted as an integer (リストオブジェクトを整数として解釈できません)
-
トラブルシューティング:
q
引数には、単一のパーセンタイルを求める場合は数値(例:50
)、複数のパーセンタイルを求める場合は数値のリスト(例:[25, 50, 75]
)を正しく渡しているか確認してください。axis
引数には、整数(軸のインデックス)またはNone
を渡しているか確認してください。
例:
import numpy as np data = np.array([[1, 2, 3], [4, 5, 6]]) # 良い例: qは数値 print(np.percentile(data, 50)) # 良い例: qは数値のリスト print(np.percentile(data, [25, 75])) # 良い例: axisは整数 print(np.percentile(data, 50, axis=0)) # 悪い例 (axisにリストを渡そうとしている場合など) # print(np.percentile(data, 50, axis=[0])) # TypeError
-
原因:
q
引数にパーセンタイル値を指定する際に、誤ってリストを渡すべきでない場所に渡してしまった場合や、逆にリストを渡すべきところで数値一つを渡してしまった場合。特に、axis
引数を整数で指定しようとしているところにリストを渡してしまうような間違いで発生することがあります。 -
エラー:
TypeError: 'list' object cannot be interpreted as an integer
q の範囲外のパーセンタイル値を指定した場合
-
トラブルシューティング:
q
の値が0 <= q <= 100
の範囲内にあることを確認してください。NumPy はこの範囲外の値を指定するとエラーではなく、警告を出すか、NaN(Not a Number)を返すことがあります。古いバージョンの NumPy ではエラーになる場合もあります。
例:
import numpy as np data = np.array([1, 2, 3, 4, 5]) # 0未満 # print(np.percentile(data, -10)) # ValueError または警告とNaN # 100超 # print(np.percentile(data, 110)) # ValueError または警告とNaN
-
原因:
q
はパーセンタイル値であり、0から100の範囲でなければなりません。 -
問題:
q
に0未満または100を超える値を指定した場合。
axis のインデックスが範囲外
-
トラブルシューティング:
- 入力配列
a
の次元数(a.ndim
)を確認し、axis
の値が-a.ndim
からa.ndim - 1
の範囲内にあることを確認してください。
例:
import numpy as np matrix = np.array([[1, 2, 3], [4, 5, 6]]) # matrix は2次元配列 (axis 0 と axis 1 が存在する) # 良い例 print(np.percentile(matrix, 50, axis=0)) print(np.percentile(matrix, 50, axis=1)) # 悪い例 (存在しない軸) # print(np.percentile(matrix, 50, axis=2)) # AxisError: axis 2 is out of bounds for array of dimension 2
- 入力配列
-
原因: 例えば2次元配列に対して
axis=2
などと指定した場合、その軸は存在しないためエラーになります。 -
エラー/問題:
axis
に配列の次元数を超えるインデックスを指定した場合。
numpy.percentile()
でエラーや予期せぬ結果に遭遇した場合、多くは以下のポイントを確認することで解決できます。
interpolation
引数の設定: 期待するパーセンタイルの定義と一致しているか。a
(入力配列) の有効性: 空でないNumPy配列であるか。q
(パーセンタイル値) の形式と範囲: 単一値かリストか、0-100の範囲内か。axis
(軸) の有効性: 配列の次元数に対して有効なインデックスか。
numpy.percentile()
は、データの統計的分析、特に分布の理解に非常に役立つ関数です。様々なシナリオでの使用例を見ていきましょう。
まず、NumPy ライブラリをインポートします。
import numpy as np
例1: 1次元配列のパーセンタイル計算
最も基本的な使い方です。データセットの中央値(50パーセンタイル)や四分位数(25パーセンタイル、75パーセンタイル)を計算します。
# 1次元の数値データ
data_1d = np.array([10, 12, 15, 18, 20, 22, 25, 28, 30, 35])
print("--- 1次元配列のパーセンタイル計算 ---")
# 50パーセンタイル(中央値)を計算
# データがソートされていなくてもNumPyが内部でソートして計算します
median_val = np.percentile(data_1d, 50)
print(f"中央値 (50パーセンタイル): {median_val}")
# 25パーセンタイル(第一四分位数)を計算
q1_val = np.percentile(data_1d, 25)
print(f"第一四分位数 (25パーセンタイル): {q1_val}")
# 75パーセンタイル(第三四分位数)を計算
q3_val = np.percentile(data_1d, 75)
print(f"第三四分位数 (75パーセンタイル): {q3_val}")
# 複数のパーセンタイルを一度に計算
# 結果は指定した順序のリストで返されます
percentiles_multi = np.percentile(data_1d, [10, 50, 90])
print(f"10, 50, 90パーセンタイル: {percentiles_multi}")
# 実行結果例:
# --- 1次元配列のパーセンタイル計算 ---
# 中央値 (50パーセンタイル): 21.0
# 第一四分位数 (25パーセンタイル): 16.75
# 第三四分位数 (75パーセンタイル): 27.25
# 10, 50, 90パーセンタイル: [11.8 21. 30.5]
例2: 多次元配列での axis
引数の使用
axis
引数を使うことで、特定の軸(行または列など)に沿ってパーセンタイルを計算できます。
# 2次元配列(行列)
data_2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print("\n--- 多次元配列での axis 引数の使用 ---")
# 配列全体の50パーセンタイル(axis=None がデフォルト)
# 配列が1次元に平坦化されて計算されます
overall_median = np.percentile(data_2d, 50)
print(f"配列全体の50パーセンタイル: {overall_median}")
# axis=0: 各列に沿って計算 (列ごとのパーセンタイル)
# 結果は各列のパーセンタイル値を含む配列になります
col_50th_percentile = np.percentile(data_2d, 50, axis=0)
print(f"列ごとの50パーセンタイル: {col_50th_percentile}")
# 1列目: [1, 4, 7] の50% -> 4.0
# 2列目: [2, 5, 8] の50% -> 5.0
# 3列目: [3, 6, 9] の50% -> 6.0
# axis=1: 各行に沿って計算 (行ごとのパーセンタイル)
# 結果は各行のパーセンタイル値を含む配列になります
row_50th_percentile = np.percentile(data_2d, 50, axis=1)
print(f"行ごとの50パーセンタイル: {row_50th_percentile}")
# 1行目: [1, 2, 3] の50% -> 2.0
# 2行目: [4, 5, 6] の50% -> 5.0
# 3行目: [7, 8, 9] の50% -> 8.0
# 実行結果例:
# --- 多次元配列での axis 引数の使用 ---
# 配列全体の50パーセンタイル: 5.0
# 列ごとの50パーセンタイル: [4. 5. 6.]
# 行ごとの50パーセンタイル: [2. 5. 8.]
例3: interpolation
引数の使用
パーセンタイルがデータ点とデータ点の間に位置する場合、どのように値を補間するかを interpolation
引数で指定できます。
# 補間方法を比較するためのデータ
data_interpolation = np.array([1, 2, 3, 4, 5, 6, 7, 8])
print("\n--- interpolation 引数の使用 ---")
# 25パーセンタイルを計算
# データ点数8個の場合、25パーセンタイルは (8+1)*0.25 = 2.25 番目の値
# つまり、2番目の値 (2) と3番目の値 (3) の間に来る
# デフォルト (linear): 線形補間
# 2 と 3 の間の 0.25 の位置なので、2 + (3-2)*0.25 = 2.25
percentile_linear = np.percentile(data_interpolation, 25, interpolation='linear')
print(f"25パーセンタイル (linear): {percentile_linear}")
# lower: 計算されたパーセンタイル位置以下の最大のデータ点
# 2.25 番目の値の以下なので、2 (2番目の値)
percentile_lower = np.percentile(data_interpolation, 25, interpolation='lower')
print(f"25パーセンタイル (lower): {percentile_lower}")
# higher: 計算されたパーセンタイル位置以上の最小のデータ点
# 2.25 番目の値の以上なので、3 (3番目の値)
percentile_higher = np.percentile(data_interpolation, 25, interpolation='higher')
print(f"25パーセンタイル (higher): {percentile_higher}")
# nearest: 最も近いデータ点
# 2.25 に最も近いのは 2 (2番目の値)
percentile_nearest = np.percentile(data_interpolation, 25, interpolation='nearest')
print(f"25パーセンタイル (nearest): {percentile_nearest}")
# midpoint: lower と higher の中間点
# (2 + 3) / 2 = 2.5
percentile_midpoint = np.percentile(data_interpolation, 25, interpolation='midpoint')
print(f"25パーセンタイル (midpoint): {percentile_midpoint}")
# 実行結果例:
# --- interpolation 引数の使用 ---
# 25パーセンタイル (linear): 2.75
# 25パーセンタイル (lower): 2.0
# 25パーセンタイル (higher): 3.0
# 25パーセンタイル (nearest): 3.0 # ここはデータによってnearestが3.0になる場合も
# 25パーセンタイル (midpoint): 2.5
注: nearest
の結果は、数値の丸め方によって期待と異なる場合があります。上記の例では、2.25 は2と3の真ん中より3に近いため、3.0となります。
例4: データクレンジングとの組み合わせ (NaN値の扱い)
実データには欠損値(NaN)が含まれることがよくあります。numpy.percentile()
はデフォルトでは NaN を無視せず、NaN が含まれると結果も NaN になります。NaN を無視して計算したい場合は、np.nanpercentile()
を使用します。
# NaN を含むデータ
data_with_nan = np.array([10, 12, np.nan, 18, 20, np.nan, 25, 28, 30])
print("\n--- NaN値の扱い ---")
# np.percentile() は NaN を無視しないため、結果も NaN になる
percentile_with_nan = np.percentile(data_with_nan, 50)
print(f"NaNを含むデータの中央値 (np.percentile): {percentile_with_nan}")
# np.nanpercentile() は NaN を無視して計算する
median_ignoring_nan = np.nanpercentile(data_with_nan, 50)
print(f"NaNを無視したデータの中央値 (np.nanpercentile): {median_ignoring_nan}")
# 実行結果例:
# --- NaN値の扱い ---
# NaNを含むデータの中央値 (np.percentile): nan
# NaNを無視したデータの中央値 (np.nanpercentile): 20.0
例5: 統計分析での利用例(外れ値の検出)
パーセンタイルは、データセットの外れ値を検出するためにも使用できます。
# サンプルデータ(株価の収益率など)
stock_returns = np.array([-0.05, 0.01, 0.02, 0.03, 0.015, -0.01, 0.04, 0.005, 0.10, -0.08])
print("\n--- 統計分析での利用例 (外れ値の検出) ---")
# IQR (Interquartile Range) を計算して外れ値の範囲を定義
q1_returns = np.percentile(stock_returns, 25)
q3_returns = np.percentile(stock_returns, 75)
iqr = q3_returns - q1_returns
# 外れ値の閾値を計算
# 下限: Q1 - 1.5 * IQR
# 上限: Q3 + 1.5 * IQR
lower_bound = q1_returns - 1.5 * iqr
upper_bound = q3_returns + 1.5 * iqr
print(f"Q1 (25パーセンタイル): {q1_returns:.4f}")
print(f"Q3 (75パーセンタイル): {q3_returns:.4f}")
print(f"IQR: {iqr:.4f}")
print(f"下限 (外れ値): {lower_bound:.4f}")
print(f"上限 (外れ値): {upper_bound:.4f}")
# 外れ値と見なされるデータ点を探す
outliers = stock_returns[(stock_returns < lower_bound) | (stock_returns > upper_bound)]
print(f"検出された外れ値: {outliers}")
# 実行結果例:
# --- 統計分析での利用例 (外れ値の検出) ---
# Q1 (25パーセンタイル): 0.0038
# Q3 (75パーセンタイル): 0.0325
# IQR: 0.0288
# 下限 (外れ値): -0.0394
# 上限 (外れ値): 0.0756
# 検出された外れ値: [-0.05 0.1 -0.08]
numpy.percentile()
はパーセンタイル計算の標準的な方法ですが、状況によっては他の関数やアプローチがより適切である場合があります。ここでは、いくつかの代替方法とその使い分けについて解説します。
numpy.quantile()
これは numpy.percentile()
の最も直接的な代替であり、推奨される方法でもあります。
-
使い分け: 基本的には
numpy.quantile()
を使用することが推奨されます。numpy.percentile()
は互換性のために残されています。 -
使い方:
q
引数に0から1までの浮動小数点数(またはその配列)を指定します。import numpy as np data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) print("--- numpy.quantile() ---") # 0.5分位数(50パーセンタイル、中央値) median_quantile = np.quantile(data, 0.5) print(f"0.5分位数 (中央値): {median_quantile}") # 0.25分位数と0.75分位数 q1_q3_quantile = np.quantile(data, [0.25, 0.75]) print(f"0.25分位数と0.75分位数: {q1_q3_quantile}") # 実行結果例: # --- numpy.quantile() --- # 0.5分位数 (中央値): 5.5 # 0.25分位数と0.75分位数: [3.25 7.75]
-
利点:
- 統計学の文献で「分位数」として議論されることが多いため、より標準的で分かりやすい概念です。
q
の範囲が0〜1なので、割合としての直感的な理解が得やすいです。
numpy.median()
特定のパーセンタイル(50パーセンタイル)を計算する場合に特化しています。
-
使い分け: 50パーセンタイル(中央値)だけが必要な場合に限ります。それ以外のパーセンタイルが必要な場合は
numpy.percentile()
またはnumpy.quantile()
を使用します。 -
使い方:
import numpy as np data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) print("\n--- numpy.median() ---") median_val = np.median(data) print(f"中央値 (np.median): {median_val}") # 実行結果例: # --- numpy.median() --- # 中央値 (np.median): 5.5
-
利点:
- 50パーセンタイルを計算するだけであれば、コードがより簡潔で意図が明確になります。
- 計算速度がわずかに速い可能性があります(ただし、ほとんどの場合、無視できる差です)。
scipy.stats.scoreatpercentile()
SciPyライブラリの統計モジュールに含まれる関数です。
-
使い分け: NumPy単独で十分な場合は
numpy.percentile()
(numpy.quantile()
が望ましい) を使用するのが一般的です。SciPyの他の統計機能と組み合わせて使用する場合や、特定の理由でSciPyの定義を使用したい場合に検討します。 -
使い方:
from scipy import stats import numpy as np data = np.array([10, 12, 15, 18, 20, 22, 25, 28, 30, 35]) print("\n--- scipy.stats.scoreatpercentile() ---") percentile_scipy = stats.scoreatpercentile(data, 50) print(f"50パーセンタイル (scipy): {percentile_scipy}") # 実行結果例: # --- scipy.stats.scoreatpercentile() --- # 50パーセンタイル (scipy): 21.0
-
利点:
- SciPyの統計モジュールに慣れている場合、一貫性のあるインターフェースを提供します。
- 一部の古いコードベースではこちらが使われている場合があります。
手動でのソートとインデックス指定
パーセンタイルの定義を完全に制御したい場合や、非常に特殊なケースで利用できます。
-
使い分け: ほとんどの場合、推奨されません。教育目的や、
numpy.percentile()
では実現できない非常に特殊なカスタム計算が必要な場合にのみ検討します。 -
使い方: (例として50パーセンタイル)
import numpy as np data = np.array([10, 12, 15, 18, 20, 22, 25, 28, 30, 35]) print("\n--- 手動でのソートとインデックス指定 ---") sorted_data = np.sort(data) n = len(sorted_data) # 50パーセンタイル(中央値)の計算(データ数が偶数の場合) # (n-1) * q/100 の位置 # 位置が整数でない場合、補間が必要 # 例: データ数10の場合、50パーセンタイルは (10-1)*0.5 = 4.5番目の要素 # 4番目の要素 (インデックス3) と5番目の要素 (インデックス4) の中間 if n % 2 == 1: # 奇数個の場合、真ん中の要素 manual_median = sorted_data[n // 2] else: # 偶数個の場合、真ん中2つの平均 manual_median = (sorted_data[n // 2 - 1] + sorted_data[n // 2]) / 2 print(f"手動計算の中央値: {manual_median}") # 実行結果例: # --- 手動でのソートとインデックス指定 --- # 手動計算の中央値: 21.0
-
欠点:
- 非常に冗長になり、エラーを起こしやすいです。
- 大規模なデータセットでは非効率的です。
- NumPyの最適化されたC実装に比べてはるかに遅いです。
-
利点:
- パーセンタイルの計算ロジックを完全に理解し、制御できます。
- NumPyやSciPyが提供しない独自の補間方法を実装できます。
- カスタムロジック: 非常にまれなケースで、独自のパーセンタイル定義を実装する必要がある場合にのみ、手動での計算を検討します。
- 互換性: 既存のコードや特定の統計ツールとの互換性が必要な場合は、
numpy.percentile()
やscipy.stats.scoreatpercentile()
を使用することもできます。 - 特殊な要件: 欠損値(NaN)を無視したい場合は
numpy.nanpercentile()
またはnp.nanquantile()
を使用します。 - 簡潔性: 中央値のみが必要な場合は
numpy.median()
が最も簡潔です。 - 推奨: ほとんどの場合、
numpy.quantile()
を使用するのが最も適切です。NumPyの最新の推奨事項と一致し、0-1の分位数の概念がより普遍的です。