数学オンチのプログラマが数学を学習し直すらしいです【統計その1】

# はじめに

この記事は 数学オンチのプログラマが数学を学習し直すらしいですシリーズの「統計」についての記事です。

このシリーズは、
「まず数学記号を読めて、意味が理解できるようになろう」
「ついでにそれをソースコードで表せるようになろう」
が目標の、かなり小馬鹿にしたようなものとなります。

# 背景

「統計」という言葉に関しての筆者の感想は、
「え、分からない」
である。

しかし、極論を言ってしまえば小学校で初めて習う統計学は「平均」だったりする。
そして「計算された平均」と「実際の感覚で感じる平均」のズレを感じたりもする。

とはいえ、大量の数値をまとめて傾向を導き出すのは、現代社会において必須に思うことも多々あるので、ここはちょっと頑張りたいところ。深層学習においても基本となる学問だと思うし（本当に深層学習に手を出すかは別として）

# 本題

とりあえず、[O'Reilly Japan - Pythonからはじめる数学入門](https://www.oreilly.co.jp/books/9784873117683/) を教科書にしつつ、余裕があれば「数学　こんな授業を受けたかった！（岡部恒治・長谷川愛美:日本実業出版社 2007年）」もつまみながら進める。

## ところで、統計が使えると何が分かるのか

数値をまとめて加工することで、何が分かるのだろうか。自分はよくわかってない。
ということで、「数学　こんな授業を受けたかった！（岡部恒治・長谷川愛美:日本実業出版社 2007年）」より、

> 「統計」とは集めたデータに対して、確率の理論を用いてその傾向や意味を調べる学問のことを言うのです。
>
> ---
> _数学　こんな授業を受けたかった！（岡部恒治・長谷川愛美:日本実業出版社 2007年） 10章「統計で説得する！」より_

もしかしたら
「確率の基本を分かっていないと、統計で導いたデータの意味が理解できない」
という話になりそうだが、この記事では確率については省く。[^1]

## 平均

平均と一口で言っても「算術平均（mean）」や「幾何平均」があるようだが、今回は前者。

Pythonにも表計算ソフトの様な関数があればと思ったりするかもしれないし、実際ある。

```python
from statistics import mean
data = [100, 300, 423, 328, 516, 412]
print(mean(data)) # 「346.5」と表示される。
```

とはいえ、そこまで難しいことはしない。
もちろん上記 `statistics` モジュールを使えば計算速度は早いのだが、ちゃんと学習し直していく。

```python
# 平均を愚直に実装する

data = [100, 300, 423, 328, 516, 412]

sum = 0
for item in data:
    sum = sum + item

print(sum / len(data)) # 「346.5」と表示される。
```

ご覧の通り、「全ての要素を合計した数を、要素の個数で割る」だけだ。確か小学校で習う。
せっかくなので格好つけて数式にしてみる。

```math
mean = \sum^n_{i=1} \div n
```

こう書けるはず……。

### Σ （シグマ）の読み方

筆者は事前に書いたとおり、
「数学オンチ」
である。数式の読み方をちゃんと把握していない。

ということできっと数学オンチは、
「膝にシグマの角をぶつけてしまってな……」
となると筆者は勝手に思っているので復習（？）。

```math
\sum^n_{i=1} \div n
```

Pythonっぽく訳すと、
Σ の上についている `n` は **リストの要素の個数**。
Σ の下についている `i=1` は **リストの要素の開始インデックス + 1**
……だよね？（読者に尋ねるな）

このΣに値を代入してみる。

```math
\sum^6_{i=1}
```

これで、
「6つの要素のある配列の1番目（Pythonだと0番目）から初めた値の合計」
となる……はず。

なので筆者個人的にΣはPythonの `for` だと思ってる。なお、間違ってる自信のほうがとてつもなく強い。

---

さて、先程のソースコード、もうちょっとシンプルに書こう。

```python
# 平均をもうちょっとシンプルに実装する。

'''
さっきのコードをインタプリタ（対話モード）で実行しちゃった筆者みたいな人は、
一度Pythonの対話モードを終了してまた起動することをおすすめ。
'''

data = [100, 300, 423, 328, 516, 412]
print(sum(data) / len(data)) # くどいようだが、「346.5」と表示される。
```

なんです？
「いきなり `sum()` 使うな」
ですって？

求める事がはっきりしていて、その関数が何を出力するかわかっていれば、コードは少なく書いたほうが良いでしょ（開き直り）。

## 中央値

こちらも先程の `statistics` に入っているので、それを利用してしまえば答えはすぐに出てくる。

```python
from statistics import median

data = [100, 300, 423, 328, 516, 412]
print(median(data)) # 「370.0」と表示される。
```

しかしこのシリーズの目的は学習し直しである（むしろ筆者は初めての学習）。

そもそも、
「中央値？　平均とどうしてズレるんですか？！」
という気持ちが凄く強かった。

### 中央値 is 何？ 何が分かるの？

筆者の解釈だと、数値の配列（リスト）は *砂場* だとしよう。

* 平均は、砂場の砂を、砂場の中で水平に均した時の高さ
* 中央値は、砂場の砂を均さずに、一番高いところと一番低いところの半分の高さ

だと思っている。

「平均じゃダメなんですか？！」
と思うことは多々あるものの、実は日常で使う「平均」というのは、もしかしたら「中央値」の事が多いかもしれない。

学校のテストの成績で考える（ありきたりだけど）。

* 平均は、みんなの点数を一度一緒にしてしまうので、クラストップの人が引き上げたり、逆にクラスドンケツの人が下げてしまったりする。
* 中央値は、みんなの点数を一緒にしないので、今回の問題は難しかったのか簡単だったのか、出題者が想定していた難易度との離れ具合を把握しやすい。

と考える。

---

ではその「全体の偏り具合」をどの様に計算するかというと、やっぱり公式がある。
しかし、2018年10月29日現在の筆者は、[中央値 (厳密な定義）- Wikipedia](https://ja.wikipedia.org/wiki/%E4%B8%AD%E5%A4%AE%E5%80%A4#%E5%8E%B3%E5%AF%86%E3%81%AA%E5%AE%9A%E7%BE%A9)の言いたいことは分かった気がするが、理解して数式の解説ができる気はしなかったので、今回は省く。

それを踏まえてここでも愚直にコーディングする。
数式がわからないなら、ソースコードから理解すればいいじゃない（また開き直る）。
なお、あえて関数・メソッド化はしない。

```python
data = [100, 300, 423, 328, 516, 412]

data.sort() # **超大事**

if len(data) % 2 == 0: # リストの要素の数の奇数・偶数判定
    # 偶数でした
    m1 = len(data) / 2
    m2 = (len(data) / 2) + 1
    # 整数に変換して位置合わせ
    m1 = int(m1) - 1
    m2 = int(m2) - 1
    print(data[m1] + data[m2]) / 2
else:
    # 奇数でした
    m = (len(data) + 1) / 2
    m = int(m) - 1
    print(data[m])
```

コードを書いて理解できたが、
「奇数のときはリストの真ん中を抽出、偶数のときはその境目に存在するであろう数値を計算する」
という具合だろうか。

## 最頻値

最頻値は、
「多く出現している要素」
を求めるのに使う。

TRPGで出目の偏りを可視化するのに使えそう（ただの感想）。

なお、これに関しては数式がない。地道に、
「同一要素の出現した数を数える」
とのこと。コンピューターの得意分野かもしれない。

---

ではコードを書いていく。
teratailだが、質問者さんの提示したソースコードが参考になった。
[MacOS(OSX) - コードが理解できません｜teratail](https://teratail.com/questions/153942)

```python
# 愚直編

data = [4, 4, 7, 5, 3, 8, 1, 9, 8, 2, 4, 6]

count = {}
for value in data:
    count.setdefault(value, 0)
    count[value] = count[value] + 1

print(count) # 「{4: 3, 7: 1, 5: 1, 3: 1, 8: 2, 1: 1, 9: 1, 2: 1, 6: 1}」と表示される
```

もちろん、このコードもモジュールを使えば簡単に書ける：

```python
# モジュールを使う編
from collections import Counter

data = [4, 4, 7, 5, 3, 8, 1, 9, 8, 2, 4, 6]

c = Counter(data)
print(c.most_common()) # 「[(4, 3), (8, 2), (7, 1), (5, 1), (3, 1), (1, 1), (9, 1), (2, 1), (6, 1)]」と表示される（タプル要素のリストなので、注意）
```

後はここで一番出現している要素を抽出したいので、先程のコードの最後を以下のように書き換えると、一番出現している要素を抽出することができる：

```python
print(c.most_common(1)) # 「[(4, 3)]」と表示される。
print(c.most_common(1)[0][0]) # 一番出現した要素を知りたい場合はこちら。
print(c.most_common(1)[0][1]) # 一番出現した要素の出現回数を知りたい場合はこちら。
```

なお今回は、
「複数の最頻値があった場合」
についてのケースは省く。

---

今回はここまで。

# まとめ・所感

* 平均、中央値、最頻値と見てみたが、使い分けはもっと学ばないと使いこなせなさそう。
* 調べながらまとめるのって、やっぱり難しいな。
* 「調べて終了」じゃなくて、「調べて（ある程度伝えれるように）書き出す」の、理解が深まる気がする。

# 参考資料

* [O'Reilly Japan - Pythonからはじめる数学入門](https://www.oreilly.co.jp/books/9784873117683/)
* 数学　こんな授業を受けたかった！（岡部恒治・長谷川愛美:日本実業出版社 2007年 ISBN978-4-534-04318-4）
* [Pythonで平均、中央値、最頻値、分散、標準偏差を計算する - Qiita](https://qiita.com/JunJun0706/items/9d1befb90a823c466c32)
* [9.7. statistics — 数理統計関数 — Python 3.6.5 ドキュメント](https://docs.python.jp/3/library/statistics.html)
* [総和 - Wikipedia](https://ja.wikipedia.org/wiki/%E7%B7%8F%E5%92%8C)
* [中央値 - Wikipedia](https://ja.wikipedia.org/wiki/%E4%B8%AD%E5%A4%AE%E5%80%A4)
* [MacOS(OSX) - コードが理解できません｜teratail](https://teratail.com/questions/153942)

# P.S.
[^1]: もしかしたら後日「確率」について記事を書くかも。
[^2]: 後日掲載するかも。しかし過剰な期待をするとガッカリする。

* Haskellコードは後日……。
* 続きをこの記事に加筆するかもです。