Pythonで0からディシジョンツリーを作って理解する　（2. Pythonプログラム基礎編）

**Pythonで0からディシジョンツリーを作って理解する**
[1.概要編](https://qiita.com/toyohisa/items/ab60d3eab11730f156da) - 2. Pythonプログラム基礎編 - [3. データ分析ライブラリPandas編](https://qiita.com/toyohisa/items/180c0b1df42fb188f920) - [4.データ構造編](https://qiita.com/toyohisa/items/de59b5f01b34f69e596d) - [5.情報エントロピー編](https://qiita.com/toyohisa/items/eb5bfd2e08e580d99690) - [6.ツリー生成編](https://qiita.com/toyohisa/items/cf38e4f815f4a7a03e09) - [(番外編) 離散化](https://qiita.com/toyohisa/items/4b8e6baf8a8a88783254)

デシジョンツリーを作るためのPythonプログラムについて、一通り説明いたします。

### 2.1 コメントは、# の右側か ''' (3つのシングルクオーテーション）で囲まれたところ
```python
# コメント
a = 1 # コメント

''' 以下はコメントになる
b = c
c = d
'''
```

### 2.2 型は動的型付け(型が自動で決まる)
データの型には、整数、実数、文字、ブール(trueかfalseのみが入る)、リスト(配列とも呼んだりします)などがあります。

```python
# 変数は動的型付け
# = は、右から左に値を代入（コピー）する。
i = 1 # 整数型 (int)
f = 2.1 # 浮動小数点数型 (float)
s = "a" # 文字型 (string)
b = True # 真偽値型 (bool)
l = [0,1,2] # 配列、リスト型
t = (0,1,2) # タプル型
d = {"a":0, "b":1} # 辞書、連想配列型

print(i,f,s,b,l,t,d)
# 1 2.1 a True [0, 1, 2] (0, 1, 2) {'a': 0, 'b': 1}

# 型を調べたいときは、typeを使用する。
print(type(i)) # 出力 <class 'int'>

# 変数は、実際の値を保持するのではなく、実際の値が格納された
# 場所を指し示す参照型と呼ばれる変数になります。
# 実際の値の識別子を id で取得できます。
print(id(l)) # 00000000000000 (実行毎に異なります)
l2 = l # 例えば配列のコピーは、参照するものを2つにするだけで、実際の配列は1つだけです。
print(id(l2)) # 00000000000000 (上の id(l) と同じ値となります)
# 実際の配列は1つなので、l2の参照で要素を追加しても 配列l にも追加されたように見えます。
l2.append(1)
```

### 2.3 演算子（計算したり、値を代入したりする。計算結果は、基本は変数に入れておく）

```python
a = 1 # aには1が入っている
b = a # bにも1が入っている
a = 2 # aは、2に上書きされる、bは変わらない
a += 1 # aの値が1つ増える(この時点でaは3)
a -= 1 # aの値が1つ減る(この時点でaは2)
a *= 2 # aの値が2倍になる(この時点でaは4)
a /= 3 # aの値が3分の1になる(この時点でaは1.3333333333333333)
a = 1+2-3*4/5 # 掛け算、割り算、足し算、引き算の順に計算されてaは0.6
# (ただし浮動小数点誤差で実際には0.6000000000000001となる)
```

### 2.4 インデント（右側に文字をずらす）ことによって、プログラムのグループを作成する
```python
a = 0
# 下の右側にインデントされたプログラムグループの実行条件を指定する。
# この後に示す、条件、繰り返し、関数化などがある。
if a == 1:
    # ifの後のインデントで右にずらしたこの行から行の左側が同じ分だけ右にずれた行は
    # 1つのプログラムグループを構成する。
    print(1)
    print(2)
    pass # 明示的にインデントで表されたここまでのグループの終わりを示す。
    # この上の行までが、ifの条件が一致したときに実行されるプログラムのグループとなる。
print(3) # aの値とは関係なく出力、上のifの対象外

# 出力 3
```

#### 2.4.1 グループ化されたプログラムには、その動作に関する制御等を付けられる。
```python
# 実行するかしないか、実行する場合は1回のみ
if 条件:
    # program group

# 配列の要素だけ実行を繰り返す。
# 繰り返している途中の変数vには、配列の要素が前から順に代入されていく。
for v in 配列:
    # program group

# プログラムグループを条件が一致する限り実行し続ける
while 条件:
    # program group

# 実行するタイミングを後から決める
#（とりあえずグループを作って名前を付けておく）
def プログラムグループの名前():
    # program group
    # このプログラムグループを実行した結果を、return を使って、実行した
    # 場所に戻すこともある。
    return

# これはエラー
# インデントが変わるprint(2)は別のプログラムグループとなる。
# しかしprogram group 2には、何の説明も
if a == 1:
    # program group 1 このプログラムグループの制御は if a==1:
    print(1)
        # program group 2 このプログラムグループの制御部分が無いため、エラーとなる。
        print(2)
```

#### 2.4.2 条件文
```python
a = 1
b = 2
c = 2

# aとbが同じ、かつ、aとcが違う
if a==b and a!=c:
    print("cだけ違う")

# aがb以上(aとbが同じを含む)、または、aがcを超える(aとcが同じを含まない)
elif a>=b or a>c:
    print("aはb以上かcを超える値")

# if, elif の条件以外
else:
    print("それ以外")

# 出力 それ以外
```

##### 2.4.2.1 条件文の書き方の別例
```python
a = 1
b = 2
# vには、aとbが同じ場合0、そうではない場合には1が代入される。
v = 0 if a==b else 1
print(v)
# 出力 1
```


#### 2.4.3 繰り返し文(for)
```python
for v in [0,1,2,3]: # 配列[0,1,2,3]の要素数だけ、以下の処理を繰り返す。
    # 処理、このときに v には、配列の要素が前から順にセットされる。
    print(v)
    pass # 明示的にインデントをここで終了させる。
# 出力 0 1 2 3

# enumerateを使用すると、配列のインデックス、値を繰り返し処理内で取得することができる。
for i,v in enumerate([5,3,7,8]):
    # iにはインデックス、vには要素の値が入る。
    print("(",i,v,")")
# 出力 ( 0 5 ) ( 1 3 ) ( 2 7 ) ( 3 8 )

# zipによって2つの配列を1つにして繰り返し処理ができる。
for v0,v1 in zip([1,2,3,4],[5,6,7,8]):
    # v0にはzipの第一引数の配列の要素、v1には第二引数の要素が入る。
    print("(",v0,v1,")")
# 出力 ( 1 5 ) ( 2 6 ) ( 3 7 ) ( 4 8 )
```

#### 2.4.4 繰り返し文(while)
```python
a = 3

# a が0以上
while a>=0:
    print(a)
    # aの値を1つ減らす
    a -= 1

# 出力 3 2 1 0
```

#### 2.4.5 関数、メソッド
```python
# 関数の定義 名前はfunction_name, 引数はparam1とparam2、param2にはデフォルト引数という
# 関数呼び出し時にセットしなかった場合に用いられる引数が指定されている。
def function_name(param1,param2=1):
    print("p1:",param1,"p2",param2)
    # この関数は、呼び出し場所に param1+param2の結果を戻す。
    return param1 + param2

# 関数の呼び出し（ここで初めて、function_nameと名付けられたプログラムが動く
# param1の引数は5、param2はセットしない（デフォルト引数を使用する）
v = function_name(5)
# 出力(function_name関数の中のprint文による出力) p1: 5 p2 1
print("戻り値",v)
# 出力 戻り値 6
```

##### 2.4.5.1 ラムダ式、関数の書き方の別例
```python
# 関数名 = lambda (引数) : (戻り値) の書き方で関数を記述する。
f = lambda x: x*x
# 呼び出すときは、defによる関数定義と同じ
v = f(2)
print(v) # 4と表示
```


### 2.5 文字型 ⇔ 数値型変換
```python
si = "1"   #文字型
sf = "2.3" #文字型
i = 4      #整数型

# 整数 ⇒ 文字変換、文字列同士の足し算は、文字結合となる。
print(str(i)+si)
# 41

# 文字 ⇒ 整数変換、整数変換後の足し算は、そのまま整数の足し算
print(i+int(si))
# 5

# 文字 ⇒ 浮動小数点変換、整数+浮動小数点の計算結果は、自動的に浮動小数点となる
print(i+float(sf))
# 6.3
```

### 2.6 配列、リスト
```python
# 配列の生成
a = [1,1,2,3,2]              # a=[[1,1,2,3,2]
b = [n for n in range(8)]  # b=[0, 1, 2, 3, 4, 5, 6, 7]

# 参照
# マイナス値は、-1を最後の要素として後ろから数えるインデックスとなる。
v = a[0]  # v=1
v = a[-1] # v=2

# 追加
a += [4]     # a=[1, 1, 2, 3, 2, 4]
a.append(5)  # a=[1, 1, 2, 3, 2, 4, 5]

# 取り出し(実行前のa=[1, 1, 2, 3, 2, 4, 5])
v = a.pop(0) # v=1, a=[1, 2, 3, 2, 4, 5]

# 要素数
cnt = len(a) # cnt=6 (a=[1, 2, 3, 2, 4, 5])

# スライス(実行前のa=[1, 2, 3, 2, 4, 5])
# a[最初のインデックス : 終わりの1つ先のインデックス]
# a[:]のようにインデックスを省略すると、最初のは0、終わりのは要素数となる。
c = a[1:3] # c=[2, 3]

# 最大最小(実行前のa=[1, 2, 3, 2, 4, 5])
mx,mi = max(a),min(a) # mx=5, mi=1

# 平均(mean)、中央値(median)、最頻値(mode)、標準偏差(stdev)、分散(variance)
# (実行前のa=[1, 2, 3, 2, 4, 5])
from statistics import mean,median,mode,stdev,variance
v = mean(a) # v=2.8333333333333335
v = median(a) # v=2.5
v = mode(a) # v=2
v = stdev(a) # v=1.4719601443879744
v = variance(a) #v=2.1666666666666665

# 重複除去(実行前のa=[1, 2, 3, 2, 4, 5])
c = set(a) # c={1, 2, 3, 4, 5}

# ソート（新しいソートされた配列を作る）(実行前のa=[1, 2, 3, 2, 4, 5])
c = sorted(a) # c=[1, 2, 2, 3, 4, 5] (aは変わらず)

# ソート（配列の中身をソートしたものに入れ替える）(実行前のa=[1, 2, 3, 2, 4, 5])
a.sort()      # a=[1, 2, 2, 3, 4, 5]

# すべての要素を同じように変換する（写像） (実行前のa=[1, 2, 2, 3, 4, 5])
# lambda式で与えられた x: x+2 によって、要素x は、 x+2によって2が加えられる。
# listは、mapの結果を配列に変換する。
c = list(map(lambda x:x+2,a)) #c=[3, 4, 4, 5, 6, 7]

# フィルタリングする　(実行前のa=[1, 2, 2, 3, 4, 5])
# lambda式で与えられた x: x%2==0によって、
# 要素xは、x%2==0 (2で割った余りが0)の条件に充たすものだけとなる。
c = list(filter(lambda x:x%2==0,a)) #c=[2, 2, 4]

# すべての要素から1つの値を計算する。
# xは、前から受け継いだ値（繰り返しの最初のときは、最初の要素）
# xiは、処理をする対象の要素
from functools import reduce
# x+xiにすると、合計が求まる。（実行前のa=[1, 2, 2, 3, 4, 5]）
c = reduce(lambda x,xi:x+xi,a) #c=17
# max(xi,x)にすると、最大値が求まる。（実行前のa=[1, 2, 2, 3, 4, 5]）
c = reduce(lambda x,xi:max(x,xi),a) #c=5
```

### 2.7 辞書型(dictionary)、連想配列
```python
# 生成、キーは天気、温度、その値に配列が指定されている。
d = {
    "国語" : [81, 60, 97, 96],
    "数学" : [80, 78, 75, 96],
    "英語" : [76, 85, 65, 88],
}
print(d)
# 出力 {'国語': [81, 60, 97, 96], '数学': [80, 78, 75, 96], '英語': [76, 85, 65, 88]}

# forを用いた初期化
d1 = {name:[0,1] for name in ["国語","数学","英語"]}
# 出力 {'国語': [0, 1], '数学': [0, 1], '英語': [0, 1]}

# キーを指定して値の取得
a = d["数学"] # a=[80, 78, 75, 96]

# キーを指定して値のセット（上書き）
d["英語"] = [77, 61, 91, 87] # d["英語"]=[77, 61, 91, 87]

# データのループ
for k,v in d.items():
    print(k,v)
# 出力 国語 [81, 60, 97, 96] 数学 [80, 78, 75, 96] 英語 [77, 61, 91, 87]

# 最大、最小（国語の得点の場合）
jmx,jmi = max(d["国語"]),min(d["国語"])
#print(jmx,jmi)

# 平均値が最も高い科目名の取得
from statistics import mean
# 辞書型配列のmaxには、何を使って最大を求めるのかをkey引数に関数を指定することができる。
# この場合は、引数k(辞書型のキー値)からその値の平均を求めるラムダ式をしてしている。
kmx = max(d,key=lambda k:mean(d[k])) # kmx="国語"
```

### 2.8 ファイル操作 [with](https://docs.python.org/ja/3/reference/compound_stmts.html#the-with-statement) [open](https://docs.python.org/ja/3/library/functions.html#open)
```python
# with, ファイル操作

# with は、開始処理と終了処理が必要な処理の、終了を自動化します。
# 例えば、openによって開かれたファイルは、closeを呼び出さないといけません。
# withを使用すると、openが成功すれば、withの範囲を抜けるときに自動的にcloseが呼び出されます。

# modeは、r:読み込み、w:上書きの書き込み、a:追加の書き込み
# openで得られた戻り値を、fという名前で使用します。
with open("09_memo.txt",mode="a") as f:
    # ファイルに書き込みます。
    f.write("こんにちは\n")

# ファイルの読み込み
with open("09_memo.txt","r") as r:
    print(r.read())
```

### 2.9 乱数 [random](https://docs.python.org/ja/3.6/library/random.html)

```python
# 乱数の使用
import random

# 乱数生成器の初期化
# 乱数は実験の再現性のために、同じ乱数列を何度も生成できるようになっている。
# seedの引数を数値で指定すると、常に同じ乱数が発生する。
random.seed(0)

# 0以上、1未満の乱数を生成する。
print(random.random()) # 出力 0.8444218515250481

# 第一引数以上、第2引数以下の整数値の乱数を生成する。
print(random.randint(1,3)) # 出力 1,2,3のどれか

# 配列からランダムに1つの要素を選択する。
print(random.choice([0,1,2])) # 出力 0,1,2のどれか

# 配列から複数個のデータを抽出する。
# 第二引数に、抽出するデータ個数を指定する。
print(random.sample([0,1,2],2)) # 出力例 [1, 2]

# 配列をシャッフルする。
# 配列aをシャッフルした新しい配列を作るのではなく、配列a自体がシャッフルされる。
a = [0,1,2]
random.shuffle(a)
print(a) # 出力例 [0, 2, 1]
print(random.sample(a,len(a))) # 新しい配列を作る場合には、sampleの利用もできる。
```