[アルゴリズム×Python]   基本統計量の計算Part2(平均値、中央値、最頻値)

アルゴリズムとPythonについて書いていきます。
今回は簡単な計算を関数を使って求めるだけでなく、関数を使わない場合にどうやって求めるかについても書いていきます。

#目次

0.算術演算子と比較演算子の確認
1.リスト内包表記について
2.平均値を求める
　2-0.算術平均
　2-1.幾何平均
　2-2.二乗平均
　2-3.調和平均
3.中央値を求める
4.最頻値を求める
最後に



#0.算術演算子と比較演算子の確認

##算術演算子

◯計算を行うための記号についてです。

| 演算子 |意味 | 例    |結果|
|:-----:|:---:|:--------:|:--------:|
| +          |  足し算 | 3 + 4        |7|
| -          |  引き算 |    10 - 2   |8|
| *          |  かけ算 | 5 * -6        |-30|
| /          |  浮動小数点数の割り算 |  5 / 2   |2.5|
| //          |  整数の割り算 | 5 // 2        |2|
| %          | 割り算のあまり | 7 % 2   |1|
| **         | ~乗(指数) | 2 ** 4   |16|

##比較演算子

◯比較を行うための記号についてです。
TrueかFalseのブール値を返します。

| 意味 |演算子 | 
|:-----:|:---:|
| 等しい         | == | 
| 等しくない         | != | 
| より小さい         | < | 
| 以下         | <= | 
| より大きい         | > | 
| 等しくない         | >= | 
| 要素になっている         | in ~  | 




#リスト内包表記について

◯様々な種類のリストを効率的につくるために、リスト内包表記が使われます。
今回の記事でも色々なリストを使って行きたいと思うので、事前に書いていきます。


##リスト内包表記とは

リストをつくる方法の1つです。
**[リストに格納する要素 for 要素 in イテラブルオブジェクト]**という形で定義します。
**リストに格納する要素**は、要素を使用した**式**です。
**イテラブルオブジェクト**は、要素を1つずつ取り出すことのできるオブジェクトのことです。

##リスト内包表記の使用例

◯range()関数はイテラブルオブジェクトを返します。

```ex0.py
#1以上10未満の整数を1つずつ取り出して、その値をnumbersリストに格納する
numbers = [number for number in range(1,10)]
print(numbers)
```

```.py
[1, 2, 3, 4, 5, 6, 7, 8, 9]
```

```ex1.py
#1以上10未満の整数を1つずつ取り出して、それに2をかけた値をnumbersリストに格納する
numbers = [number * 2 for number in range(1,10)]
print(numbers)
```

```.py
[2, 4, 6, 8, 10, 12, 14, 16, 18]
```

```ex2.py
#1以上10未満の整数を1つずつ取り出して、偶数の値のみをnumbersリストに格納していく
numbers = [number for number in range(1,10) if number % 2 == 0]
print(numbers)
```

```.py
[2, 4, 6, 8]
```


```ex3.py
#文字列のリスト
list_1 = ['1','2','3']
#文字列のリストlist_1から要素を1つずつ取り出して、整数にしてからリストnumbersに格納する
numbers = [int(i) for i in list_1]
print(numbers)
```

```.py
[1, 2, 3]
```


```ex4.py
#取り出した要素が奇数ならばそのまま格納して偶数ならば2乗してから格納する
numbers = [i if i % 2 == 1 else i**2 for i in range(10)]
print(numbers)
```


```.py
[0, 1, 4, 3, 16, 5, 36, 7, 64, 9]
```




#2.平均値を求める

##2-0.算術平均の求め方

◯一般的に知られている平均値です。
要素の合計値/要素の数で求めることができます。

-----------------------------------------------------------------------------
###mean()関数を使った平均値の求め方

◯標準ライブラリの``statistics``をimportしてmean()関数を使えるようにします。

```.py
#モジュールをimportする
import statistics

numbers = [1,2,3,4,5,6]
#statisticsモジュールの中にあるmean()関数を
mean_of_numbers = statistics.mean(numbers)
print('mean =',mean_of_numbers)
```

```.py
mean = 3.5
```

◯**Point**:モジュール
関連するPythonコードをまとめたファイルのこと。``import モジュール``という形で使います。

◯**Point**:ドット(.)
ドットは、**~の中に行く**という意味です。
モジュールの中にある関数や変数を使うためには、statistics.mean()のように、``モジュール.関数``や、``モジュール.変数``のようにします。

◯**Point**:
[公式ドキュメント:statistics](https://docs.python.org/ja/3/library/statistics.html#statistics.harmonic_mean)

-----------------------------------------------------------------------------


###合計値を使った平均値の求め方

◯**要素の合計値/要素の数**で求めます。

```.py

#リスト内包表記でリストの要素を定義してみる
#定義するリストは[1,2,3,4,5,6]
numbers = [number for number in range(1,7)]

#平均　＝　合計値　/　要素数
average = sum(numbers)/len(numbers)

print('numbers = ',numbers)
print('average of numbers = ', average)
```

```.py
numbers =  [1, 2, 3, 4, 5, 6]
average of numbers =  3.5
```


##2-1.幾何平均の求め方

◯幾何平均は、**変化率で表されるデータ**を扱う場合に使います。
(例)
貯金が年率5％で値が大きくなったら、1.05を掛けた値になります。翌年に年率7％で値が大きくなったら、最初の貯金額に1.05を掛けたものに、さらに1.07を掛けた金額になります。
このときの**変化率の年平均を求める**ために、幾何平均を使います。

◯幾何平均(geometric_mean)を求める公式は、**xG = n√ x1*x2*x3*…*xn**　です。
つまり、各データ(変化率)を全て掛け合わせて、データ数の累乗根をとることで求めることができます。

◯[幾何平均（相乗平均）の意味と計算方法～伸び率の平均を求める～](https://toukeigaku-jouhou.info/2015/08/23/geometric-mean/#:~:text=%E5%B9%BE%E4%BD%95%E5%B9%B3%E5%9D%87%EF%BC%88geometric%20mean%EF%BC%89%E3%81%AF,%E3%81%A7%E5%BE%97%E3%82%89%E3%82%8C%E3%82%8B%E5%80%A4%E3%81%A7%E3%81%99%E3%80%82&text=%EF%BD%81%E3%81%A8%EF%BD%82%E3%81%A8%EF%BD%83,%E3%82%92%E3%81%97%E3%81%A6%E3%81%84%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82)

-----------------------------------------------------------------------------

###gmean()関数を使った幾何平均の求め方

◯会社の売り上げの例について計算します。

1年目:1000万円
2年目:2500万円(前年比2.5倍)
3年目:4000万円(前年比1.6倍)

売り上げの変化率の年平均を``geometric_mean``とすると、

```.py
#gmean()関数をimportする
from scipy.stats import gmean

# 2√ 2.5*1.6　を計算する
#売り上げの変化率の年平均をgeometric_meanとする
geometric_mean = gmean([2.5,1.6])
print(geometric_mean)
```

```.py
2.0
```

◯**Point**:Scipy
高度な科学計算を行うためのライブラリのことです。
参考記事
[絶対に知っとくべきライブラリscipyの基本的な使い方](https://programming-study.com/technology/python-scipy/)

◯**Point**:gmean()関数
参考記事
[公式ドキュメント](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mstats.gmean.html)

-----------------------------------------------------------------------------

###geometric_mean()関数を使った幾何平均の求め方

◯Python3.8から、statisticsモジュールの中に、###geometric_mean()関数が導入されたようです。なのでPython3.8以上の方は使用可能です。

```.py
from statistics import geometric_mean
print(geometric_mean([2.5,1.6]))
```
```.py
2.0
```

[公式ドキュメント:statistics](https://docs.python.org/ja/3/library/statistics.html#statistics.geometric_mean)

-----------------------------------------------------------------------------

###root()関数を使った幾何平均の求め方

```.py
import sympy

#売り上げ(sales)のリスト
sales = [1000,2500,4000] 

#変化率(rate_of_change)のリスト
#このリストは[2.5, 1.6]になる
rate_of_changes = [sales[i+1]/sales[i] if i < len(sales)-1 for i in range(len(sales))]

#変数mulには全ての要素の積を代入していく
#まず、変化率のリストの先頭の要素を変数mulに代入する
#この時点ではmul = 2.5になる
mul = rate_of_changes[0]

#要素を掛け合わせていく
for i in range(len(rate_of_changes)):
    #いつまで繰り返すか
    if i < len(rate_of_changes)-1:
       #変数mulに、i+1の要素をかけて代入する
       mul *= rate_of_changes[i+1]
    else:
        break
#root(ルートの中身,~乗根)
#今回のルートの中身は全ての要素の積
geometric_mean = sympy.root(mul,len(rate_of_changes))
print('geometric_mean = ',geometric_mean)
```

```.py
geometric_mean = 2.00000000000000
```

[Python, SymPyの使い方（因数分解、方程式、微分積分など）](https://note.nkmk.me/python-sympy-factorization-solve-equation/)










##2-2.二乗平均の求め方

◯二乗平均は、**平均したい数値を2乗して合計し要素数ｎで割った値を平方根して算出します**。輸送機関の時刻表に対する到着時間との差分を算出したいときなどに利用します。

◯2分の遅れも2分早く到着することも、時刻の乱れがあることに違いはありません。しかし、**プラスマイナスがあるまま算術平均をする**とその**誤差を相殺**してしまいます。
なので、**2乗することでマイナスをなくして**計算をおこないます。

-----------------------------------------------------------------------------

###root()関数を使った二乗平均の求め方

```.py
#root()関数を使うためにsympyをimportする
import sympy
#標準ライブラリのdecimalモジュールを使って最終的な値を四捨五入してみる
from decimal import Decimal, ROUND_HALF_UP

#誤差のリスト
data_list = [-2,3,4,-5]
#誤差のリストの各要素を2乗して新しいリストsquared_listをつくる
#squared_listは[4,9,16,25]になる
squared_list = [i**2 for i in data_list]
#squared_listの平均をまず求める(合計値/要素数)
#平均値 = (4+9+16+25)/4 = 13.5
mean_square = sum(squared_list)/len(squared_list)
#mean_squareの平方根をとる
# √13.5 = 3.67423461417477
root_mean_square = sympy.root(mean_square,2)

print('RMS = ',root_mean_square)
#正確にその値のDecimal型として扱うためにstr()で型を変換する
#Decimal('求めたい桁数')で、桁を指定する
#ROUND_HALF_UPで一般的な四捨五入を行う
print('Rounded RMS = ',Decimal(str(root_mean_square)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP))
```


```.py
RMS = 3.67423461417477
Rounded RMS =  3.7
```

◯**Point**:Decimal.quantize
参考記事
[Pythonで小数・整数を四捨五入するroundとDecimal.quantize](https://note.nkmk.me/python-round-decimal-quantize/)





##2-3.調和平均の求め方

◯調和平均とは、時速の平均値などを求める時に使うものです。

◯例として、車が時速80kmで往路200kmを、時速30kmで復路200kmを走行したときの平均時速を求めます。
時速は、距離/時間であり、平均時速は**総距離/総時間**で求められます。
**総距離**は200 + 200 = 400(km)
**総時間**(距離/速さ)は、200/80 + 200/30 = 2.5(時間) + 6.666(時間) = 9.166(時間)
**時速の平均** = 総距離/総時間 = 400/9.166 = 43.636(km/時間)

----------------------------------------------------------------------

###harmonic_mean()関数を使った調和平均の求め方

```.py
import statistics

#harmonic_mean([x1,x2,...xn])
print(statistics.harmonic_mean([80,30]))
```
```.py
43.63636363636363
```

----------------------------------------------------------------------

###距離を使った調和平均の求め方

```.py
distance = 200
#距離を使って総距離を求める
total_distance = distance* 2

#速度の値を格納したリストを用意する
speed_list = [80,30]
#リストの速度からそれぞれの所要時間(距離/速度)を取得してリスト化する
time_list = [distance/speed for speed in speed_list]
#それぞれの所要時間の合計値(総時間)を求める
total_time = sum(time_list)

#調和平均 = 総距離/総時間
harmonic_mean = total_distance/total_time
print('harmonic_mean = ',harmonic_mean)
```

```.py
harmonic_mean =  43.63636363636363
```

◯**Point**:参考記事
[調和平均の意味と計算方法](https://toukeigaku-jouhou.info/2016/06/23/harmonic-mean/)








#3.中央値を求める

◯データを昇順、または降順に並べた時の真ん中の値を中央値といいます。データ数が偶数の時は、真ん中の数が2つになるので、それらを足して、2で割ったものが中央値になります。中央値のメリットは、外れ値(極端に他と離れた値)に左右されにくい点です。

-----------------------------------------------------------------------------

##median()関数を使った中央値の求め方

◯要素数が奇数でも偶数かで場合分けする必要はありません。

```.py
#median()関数を使う為にモジュールをimportする
import statistics

#リストの要素数は奇数
numbers = [1,100,4,7,3]
#変数median_of_numberに中央値の値を代入する
#モジュール内の関数を使うためにドットを使ってアクセスする
median_of_numbers = statistics.median(numbers)
print('median of numbers = ',median_of_numbers)
```
```.py
median of numbers = 4
```

-----------------------------------------------------------------------------

##要素数を2分して中央値を求める

◯要素数が奇数か偶数かで場合分けして考えていきます。
奇数の場合は、要素の真ん中の数のインデックスを**要素数 // 2**(整数の割り算)で求めます。
偶数の場合は、要素の真ん中の数2つのインデックスを**要素数 / 2**と**要素数/2-1**で求めます。

```.py
#リストの要素数は偶数
numbers = [1,100,4,7,3,8]
#まずリストを昇順に並べ替える
numbers.sort()
#変数length_of_numbersに、numbersリストの要素数を代入する
length_of_numbers = len(numbers)

#もし、要素数が奇数ならば、
if(length_of_numbers % 2 == 1):
    #変数median_indexに、リストの要素の真ん中の値のインデックスを代入する
    #例えば、要素数5のリストの真ん中の値のインデックスは、5//2 = 2
    median_index = length_of_numbers//2
    print('median of numbers = ',numbers[median_index])
#もし、要素数が偶数ならば、
else:
    #リストの要素数の2分の1の値を変数median_indexに代入する
    median_index = length_of_numbers//2
    #要素数が偶数の場合の中央値の値は、真ん中の値2つを足して、2で割ったもの
    #例えば要素数が6のとき、真ん中の値2つのインデックスは6/2-1 = 2と6/2 = 3
    print('median of numbers = ',(numbers[median_index-1] + numbers[median_index])/2)
 
```
```.py
#(4 + 7)/2 = 5.5
median of numbers = 5.5
```








#4.最頻値を求める

◯リストの中から最も頻繁に現れる要素を見つけます。

-----------------------------------------------------------------------------

##Counterクラスを使った最頻値の求め方

```.py
#collectionsモジュールからCounterクラスをimportする
from collections import Counter
medals = ['gold','silver','gold','silver','silver','bronze']
#インスタンスの生成
medal_counter = Counter(medals)

print(medal_counter)
```
```.py
Counter({'silver': 3, 'gold': 2, 'bronze': 1})
```

◯**Point**:クラスの使い方
クラスは関連のあるフィールドとメソッドをまとめたものです。
ただし、クラスそれ自体は抽象的な存在で、プログラム内で直接使うことはできません。
なので、まずクラスを具体化してオブジェクトをつくります。
今回は、``Counter(medals)``がそれに当たります。
これをインスタンスといいます。これをプログラム内で扱う時は、変数として扱います。
そのために、``medal_counter = Counter(medals)``のようにして変数``medal_counter``にインスタンスを代入します。


◯**Point**:most_common()
Counterクラスのメソッドです。全ての要素を降順で返します。
引数として整数を指定すると、最上位から数えてその個数分だけを表示します。
(例)

```.py
print(medal_counter.most_common(1))
```

```.py
[('silver', 3)]
```

-----------------------------------------------------------------------------

##multimode()関数を使った複数の最頻値の求め方

◯最頻値は複数ある可能性もあります。その場合は、全ての最頻値を取得できるようにmultimode()関数を使って求めます。

```.py
import statistics

#文字列をリスト化してlettersに代入する
letters = list('text_book')
#リストlettersの最頻値(一番多い文字)を求める
print('modes = ',statistics.multimode(letters))
```
```.py
modes =  ['t', 'o']
```


---




##Counterクラスを使った複数の最頻値の求め方

◯複数の最頻値が存在する場合に備えたプログラムをつくります。
**最頻値のリストを返す関数**を自分で定義します。

```.py
#Counterクラスをimportする
from collections import Counter

#最頻値のリストを返す自作の関数(mode_func)を定義する
def mode_func(letters):
    #まずletter(文字)とその出現数を取得する
    #letter_counter = Counter({'t': 2, 'o': 2, 'e': 1, 'x': 1, '_': 1, 'b': 1, 'k': 1})
    letter_counter = Counter(letters)
    #次に文字とその出現数のセットを降順に並べたリストを取得して、変数letter_and_countに代入する
    #[('t', 2), ('o', 2), ('e', 1), ('x', 1), ('_', 1), ('b', 1), ('k', 1)]
    letter_and_count = letter_counter.most_common()
    #降順に並べたので、"一番左の要素の出現数"(ここでは2)が最頻値の1つであることは確定している
    #それを変数max_countに代入する
    max_count = letter_and_count[0][1]
    #最頻値を格納するリストを作って、他にも同じ出現数を持つ要素があれば随時追加していく
    mode_list = []
    for letter in letter_and_count:
        #もし、要素(文字)の出現数が最頻値のそれと同じなら
        if letter[1] == max_count:
            #その文字を最頻値のリストに追加する
            mode_list.append(letter[0])
    #最終的に最頻値のリストを返す
    return(mode_list)

#関数を再利用しやすくするために書いておく
if __name__ == '__main__':
    #文字列をリスト化したものを変数lettersに代入する
    letters = list('text_book')
    #mode_func()が返すリストを変数mode_listに代入する
    mode_list = mode_func(letters)
    #mode_listに、複数の最頻値があるかもしれないので、それを全て取り出して書き出す
    for mode in mode_list:
        print('Mode = ',mode)

```

```.py
Mode = t
Mode = o
```

◯**Point**:``if __name__ == '__main__'``:
参考記事
[Pythonの`if __name__ == '__main__'`とはなんですかへの回答](https://blog.pyq.jp/entry/Python_kaiketsu_180207)


#最後に

読んでいただきありがとうございました。
次回は、基礎統計量の計算Part3(範囲、分散、標準偏差)について書いていきます。
間違いや改善点等のご指摘いただけると嬉しいです。