Python, SciPyでトリム平均を算出(ndarray, DataFrame)

Posted: | Tags: Python, SciPy, NumPy, pandas

Pythonでnumpy.ndarraypandas.DataFrameのトリム平均(調整平均)を算出するには、SciPyのscipy.stats.trim_mean(), scipy.stats.tmean()を使う。

scipy.stats.trim_mean()は指定した割合の要素を最大値・最小値から順に除外、scipy.stats.tmean()は指定した値以上・以下の要素を除外して平均を算出する。

ここでは以下の内容について説明する。

  • トリム平均(調整平均)
  • scipy.stats.trim_mean()の使い方
    • 基本的な使い方
    • 注意点: ソートされてから除外される
    • 二次元配列の場合
  • scipy.stats.tmean()の使い方
    • 基本的な使い方
    • 注意点: SciPy1.7.1時点のバグ
  • pandas.DataFrame, Seriesに対するトリム平均
    • scipy.stats.trim_mean(), trimboth()の例
    • scipy.stats.tmean()の例
    • 注意点: 文字列などが含まれる場合

以下のようにライブラリをインポートする。

import scipy.stats
import numpy as np
import pandas as pd

なお、以下のように関数を個別にインポートすることも可能。

from scipy.stats import trim_mean

サンプルコードのSciPyのバージョンは1.7.1

import scipy

print(scipy.__version__)
# 1.7.1

トリム平均(調整平均)

トリム平均(調整平均)は、元のデータから上位・下位の値をいくつか取り除いて算出する平均値。極端に大きかったり小さかったりする外れ値・異常値の影響を避けることができる。

英語ではtrimmed meanやtruncated meanなどと呼ばれる。

scipy.stats.trim_mean()の使い方

基本的な使い方

numpy.ndarrayに対する通常の平均値(算術平均)はmean()メソッドやnp.mean()関数で算出できる。

a = np.array([2**n for n in range(10)])
print(a)
# [  1   2   4   8  16  32  64 128 256 512]

print(type(a))
# <class 'numpy.ndarray'>

print(a.mean())
# 102.3

print(np.mean(a))
# 102.3

トリム平均はscipy.stats.trim_mean()を使う。第一引数に対象のnumpy.ndarray、第二引数proportiontocutに除外する要素の割合を指定する。

例えば第二引数が0.1の場合は上位・下位からそれぞれ10%ずつ、0.2の場合はそれぞれ20%ずつの要素が取り除かれる。

print(scipy.stats.trim_mean(a, 0.1))
# 63.75

print(scipy.stats.trim_mean(a, 0.2))
# 42.0

トリムされた後の配列はscipy.stats.trimboth()で確認できる。

print(scipy.stats.trimboth(a, 0.1))
# [  2   8   4  16  32  64 128 256]

print(scipy.stats.trimboth(a, 0.2))
# [  4  16   8  32  64 128]

除外される個数は小数点以下切り捨て。例えば、元の配列の個数が10個で第二引数を0.19とすると、上位・下位からそれぞれ除外される個数は10 * 0.19 = 1.9個 → 1個(小数点以下切り捨て)となる。

print(scipy.stats.trimboth(a, 0.19))
# [  2   8   4  16  32  64 128 256]

print(scipy.stats.trim_mean(a, 0.19))
# 63.75

注意点: ソートされてから除外される

scipy.stats.trim_mean(), scipy.stats.trimboth()では、最大値・最小値から順に要素が除外される。当然ながら、元の配列がソートされていない場合も、ソートされてから値の大きさを元に除外される。

a_unsorted = np.array([100, 1000, 0, 10000, 10])
print(a_unsorted)
# [  100  1000     0 10000    10]

print(scipy.stats.trimboth(a_unsorted, 0.2))
# [  10  100 1000]

print(scipy.stats.trim_mean(a_unsorted, 0.2))
# 370.0

元の配列の並びのまま左端・右端の要素を除外したい場合はスライスを用いればよい。

print(a_unsorted[1:-1])
# [ 1000     0 10000]

print(a_unsorted[1:-1].mean())
# 3666.6666666666665

二次元配列の場合

以下の二次元配列を例とする。

a_2d = np.array([2**n for n in range(16)]).reshape(4, 4)
print(a_2d)
# [[    1     2     4     8]
#  [   16    32    64   128]
#  [  256   512  1024  2048]
#  [ 4096  8192 16384 32768]]

scipy.stats.trim_mean(), scipy.stats.trimboth()もNumPyの多くの関数と同様に引数axisを指定できる。

デフォルトでは列ごとに処理される(axis=0)。

print(scipy.stats.trimboth(a_2d, 0.25))
# [[  16   32   64  128]
#  [ 256  512 1024 2048]]

print(scipy.stats.trim_mean(a_2d, 0.25))
# [ 136.  272.  544. 1088.]

axis=1とすると行ごと、axis=Noneとすると一次元化された配列に対して処理される。

print(scipy.stats.trimboth(a_2d, 0.25, axis=1))
# [[    2     4]
#  [   32    64]
#  [  512  1024]
#  [ 8192 16384]]

print(scipy.stats.trim_mean(a_2d, 0.25, axis=1))
# [3.0000e+00 4.8000e+01 7.6800e+02 1.2288e+04]

print(scipy.stats.trimboth(a_2d, 0.25, axis=None))
# [  16   32   64  128  512  256 1024 2048]

print(scipy.stats.trim_mean(a_2d, 0.25, axis=None))
# 510.0

scipy.stats.tmean()の使い方

基本的な使い方

scipy.stats.tmean()では、上限・下限の値を指定してその範囲内の値から平均が算出される(範囲外の値が除外される)。

第一引数に対象のnumpy.ndarray、第二引数limits(下限値, 上限値)のタプルを指定する。上限値・下限値にNoneを指定した場合は除外されずにすべての値が残る。

print(a)
# [  1   2   4   8  16  32  64 128 256 512]

print(scipy.stats.tmean(a, (4, 32)))
# 15.0

print(scipy.stats.tmean(a, (None, 32)))
# 10.5

第三引数inclusiveに上限値・下限値に等しい値を残すかどうかを指定できる。

デフォルトはinclusive=(True, True)下限値 <= x <= 上限値の範囲の値が残る。inclusive=(False, False)とすると下限値 < x < 上限値の範囲の値が残る。

print(scipy.stats.tmean(a, (4, 32), (False, False)))
# 12.0

なお、scipy.stats.trim_mean()に対するscipy.stats.trimboth()のように、scipy.stats.tmean()でトリムされた後の配列を返す関数は公開されていない(SciPy1.7.1時点)。

注意点: SciPy1.7.1時点のバグ

scipy.stats.tmean()にも引数axisがあるが、SciPy1.7.1時点では正しく動作していない。

print(a_2d)
# [[    1     2     4     8]
#  [   16    32    64   128]
#  [  256   512  1024  2048]
#  [ 4096  8192 16384 32768]]

print(scipy.stats.tmean(a_2d, (16, 2048)))
# 510.0

print(scipy.stats.tmean(a_2d, (16, 2048), axis=0))
# 510.0

# print(scipy.stats.tmean(a_2d, (16, 2048), axis=1))
# AxisError: axis 1 is out of bounds for array of dimension 1

例えば各列に対してscipy.stats.tmean()を適用したい場合は、以下のような方法がある。

for i in range(a_2d.shape[1]):
    print(scipy.stats.tmean(a_2d[:, i], (16, 2048)))
# 136.0
# 272.0
# 544.0
# 1088.0

pandas.DataFrame, Seriesに対するトリム平均

scipy.stats.trim_mean()などの関数はpandas.DataFrame, Seriesに対して適用することも可能。

以下のpandas.DataFrameを例とする。

df = pd.DataFrame(a_2d,
                  index=['R1', 'R2', 'R3', 'R4'],
                  columns=['C1', 'C2', 'C3', 'C4'])
print(df)
#       C1    C2     C3     C4
# R1     1     2      4      8
# R2    16    32     64    128
# R3   256   512   1024   2048
# R4  4096  8192  16384  32768

scipy.stats.trim_mean(), trimboth()の例

pandas.Series(= pandas.DataFrameの列・行)に対する例。一次元配列として処理され、scipy.stats.trimboth()numpy.ndarrayを返す。

print(type(df['C1']))
# <class 'pandas.core.series.Series'>

print(scipy.stats.trim_mean(df['C1'], 0.25))
# 136.0

print(scipy.stats.trimboth(df['C1'], 0.25))
# [ 16 256]

print(type(scipy.stats.trimboth(df['C1'], 0.25)))
# <class 'numpy.ndarray'>

pandas.DataFrameに対する例。二次元配列として処理され、scipy.stats.trim_mean()scipy.stats.trimboth()numpy.ndarrayを返す。

print(scipy.stats.trim_mean(df, 0.25))
# [ 136.  272.  544. 1088.]

print(type(scipy.stats.trim_mean(df, 0.25)))
# <class 'numpy.ndarray'>

print(scipy.stats.trimboth(df, 0.25))
# [[  16   32   64  128]
#  [ 256  512 1024 2048]]

print(type(scipy.stats.trimboth(df, 0.25)))
# <class 'numpy.ndarray'>

引数axisの指定も可能。

print(scipy.stats.trim_mean(df, 0.25, axis=1))
# [3.0000e+00 4.8000e+01 7.6800e+02 1.2288e+04]

print(scipy.stats.trim_mean(df, 0.25, axis=None))
# 510.0

pandas.DataFrameの各列に対する結果をpandas.Seriesとして扱いたい場合は、以下のような方法がある。

s_tm = pd.Series(scipy.stats.trim_mean(df, 0.25),
                 index=df.columns)

print(s_tm)
# C1     136.0
# C2     272.0
# C3     544.0
# C4    1088.0
# dtype: float64

scipy.stats.tmean()の例

scipy.stats.tmean()に対する例。上述のように、SciPy1.7.1時点では引数axisが正常に動作しないので注意。

print(scipy.stats.tmean(df, (16, 2048)))
# 510.0

print(scipy.stats.tmean(df['C1'], (16, 2048)))
# 136.0

各列に対してscipy.stats.tmean()を適用したい場合は、以下のような方法がある。

for col in df:
    print(col, scipy.stats.tmean(df[col], (16, 2048)))
# C1 136.0
# C2 272.0
# C3 544.0
# C4 1088.0

注意点: 文字列などが含まれる場合

文字列などの数値以外の要素が含まれる場合、scipy.stats.trim_mean()などの関数はエラーとなる。

df['C5'] = ['A', 'B', 'C', 'D']
print(df)
#       C1    C2     C3     C4 C5
# R1     1     2      4      8  A
# R2    16    32     64    128  B
# R3   256   512   1024   2048  C
# R4  4096  8192  16384  32768  D

# print(scipy.stats.trim_mean(df, 0.25))
# TypeError: unsupported operand type(s) for /: 'str' and 'int'

pandas.DataFrameselect_dtypes()メソッドで数値列のみを取り出してから適用する必要がある。

print(df.select_dtypes(include='number'))
#       C1    C2     C3     C4
# R1     1     2      4      8
# R2    16    32     64    128
# R3   256   512   1024   2048
# R4  4096  8192  16384  32768

print(scipy.stats.trim_mean(df.select_dtypes(include='number'), 0.25))
# [ 136.  272.  544. 1088.]

関連カテゴリー

関連記事