Pandasの基礎

# Pandasとは
PandasはPythonの拡張モジュールで、データ解析を支援する機能を提供してくれます。
AIを作るにも事前のデータ解析が重要ですが、Pandas(+Jupyter Notebook)があればとても便利に解析できます。
また、人工知能フレームワークの入力に使われていたりますので、人工知能を勉強する上で使い方を知っておくことは重要です。

ここでは、Pandasでできることをまとめています。

# Pandasのインストール
pipを使えば、以下のコマンドでPandasを簡単にインストールすることが出来ます。

```
# pip install pandas
```

# Pandasのインポート
以下の「おまじない」でPandasの使用が可能になります。省略名は「pd」にすることが多いようです。

```python
import pandas as pd
```

以降のコードは、上記の「おまじない」が実行済みであることを前提に記述します。
また、[NumPy](https://qiita.com/mochidan/items/50a18a663aa97f62c83c)もよく使いますので、NumPyも省略名「np」としてインポートされている前提で記述します。

# Series型とDataFrame型
Pandasを使用してデータ解析を行っていくのですが、Pandasでは解析対象のデータを格納するための型が存在します。それが、Series型とDataFrame型です。

# Series型
[NumPy配列](https://qiita.com/mochidan/items/50a18a663aa97f62c83c)が「Pythonのlist型のようなもの」とするなら、Series型は「Pythonの辞書型(dict型)のようなもの」です。
辞書型のキーのようにデータにラベルを付けれますし、他にもいろいろな処理を行うことができます。
また、次に紹介するDataFrame型での1列分のデータ、または1レコード（1行）分のデータに相当します。

## Series型の作成
主にlist型と辞書型から、Series型を作成できます。

### 辞書型(dict型)から作成
Pandasの*Series()*で、辞書型(dict型)からSeries型を作成することができます。
辞書型のキーがSeries型のラベルに、辞書型の要素がSeries型のデータになります。

```python
Science = {"akiyama": 100, "satou": 75, "tanaka": 120}

# SR = akiyama    100
#      satou       75
#      tanaka     120
#      dtype:   int64
SR = pd.Series(Science)
```

### NumPy配列とlist型から作成
Pandasの*Series()*で、list型またはNumPy配列からSeries型を作成することができます。
list型で作成した場合、ラベルは「0」からの連番が振られますが、別途ラベルを指定することもできます。

```python
record = [100, 75, 120]
record_np = np.array(record)
labels = ["akiyama", "satou", "tanaka"]

# ラベルは「0」からの連番
# SR = 0        100
#      1         75
#      2        120
#      dtype: int64
SR = pd.Series(record)
SR = pd.Series(record_np)

# 「index」でラベルを指定
# SR = akiyama    100
#      satou       75
#      tanaka     120
#      dtype:   int64
SR = pd.Series(record, index=labels)
SR = pd.Series(record_np, index=labels)
```

## Series型の操作
Series型にすると、様々な操作を行うことができます。

### ラベルのみ表示、データのみ表示
Series型はindexでラベルのみ表示、valuesでデータのみ表示させることができます。

```python
Science = {"akiyama": 100, "satou": 75, "tanaka": 120}

# SR = akiyama    100
#      satou       75
#      tanaka     120
#      dtype:   int64
SR = pd.Series(Science)

# SR.index = Index(['akiyama', 'satou', 'tanaka'], dtype='object')
SR.index

# SR.valuesはNumPy配列
# SR.values = [100 75 120]
SR.values
```

### Series型とラベルに名前を付ける
Series型はnameで名前を付けられます。また、index.nameでラベルにも名前を付けられます。

```python
Science = {"akiyama": 100, "satou": 75, "tanaka": 120}
SR = pd.Series(Science)

# SR = Students
#      akiyama    100
#      satou       75
#      tanaka     120
#      Name: Science, dtype: int64
SR.name = 'Science'
SR.index.name = 'Students'
```

### 新しいラベルを追加・ラベルを変更
Series型に新しいラベルを追加したい場合は、以下のようにすることで追加ですます。

```python
Science = {"akiyama": 100, "satou": 75, "tanaka": 120}

# SR = akiyama    100
#      satou       75
#      tanaka     120
#      dtype:   int64
SR = pd.Series(Science)

# ラベルのlist型を使ってSeries型を作り直すと、新しいラベルが追加されたSeries型を取得できる
# この際、元のSeries型にないラベルは引き継がれない
# SR_new = akiyama     100.0
#          satou        75.0
#          nico          NaN
#          mochidan      NaN
#          dtype:    float64
labels = ["akiyama", "satou", "nico", "mochidan"]
SR_new = pd.Series(SR, index=labels)
```

### Series型のデータにアクセス
Series型は、添字やラベル名でデータにアクセスすることができます。

```python
Science = {"akiyama": 100, "satou": 75, "tanaka": 120}

# SR = akiyama    100
#      satou       75
#      tanaka     120
#      dtype:   int64
SR = pd.Series(Science)

# tmp = 75
tmp = SR[1]
tmp = SR["satou"]

# スライスやlist型を使った指定も可能
# この場合、Series型が返却される
# tmp2 = akiyama    100
#        satou       75
#        dtype:   int64
tmp2 = SR[0:2]
tmp2 = SR[["akiyama", "satou"]]
```

### データにnull（欠損値）があるか判定
Pandasの*isnull()*、または*notnull()*でデータにnullがあるか判定できます。

```python
Science = {"akiyama": 100, "satou": 75, "tanaka": 120}
SR = pd.Series(Science)
SR["satou"] = np.nan

# SR["satou"]が欠損値(null)
# 欠損値があると、データ型がfloat64に変わる
# SR = akiyama    100.0
#      satou        NaN
#      tanaka     120.0
#      dtype:   float64
SR

# akiyama    False
# satou       True
# tanaka     False
# dtype:      bool
pd.isnull(SR)

# akiyama     True
# satou      False
# tanaka      True
# dtype:      bool
pd.notnull(SR)
```

### 条件を満たすデータを表示・変更
条件を満たすデータの判定、および条件を満たすデータを表示・変更が行えます。

```python
Science = {"akiyama": 100, "satou": 75, "tanaka": 120}

# SR = akiyama    100
#      satou       75
#      tanaka     120
#      dtype:   int64
SR = pd.Series(Science)

# 80を超えるデータの判定
# 判定結果はSeries型として取得
# is_Excellent = akiyama     True
#                satou      False
#                tanaka      True
#                dtype:      bool
is_Excellent = SR > 80

# 80を超えるデータのみ、Series型として取得
# Excellent = akiyama    100
#             tanaka     120
#             dtype:   int64
Excellent = SR[SR > 80]
Excellent = SR[is_Excellent]

# 値を代入することで、条件を満たすデータを更新
# SR = akiyama     80
#      satou       75
#      tanaka      80
#      dtype:   int64
SR[SR > 80] = 80
```

### 指定したデータを削除
Series型の*drop()*を使用することで、指定したデータが削除されたSeries型を取得することができます。

```python
Science = {"akiyama": 100, "satou": 75, "tanaka": 120}

# SR = akiyama    100
#      satou       75
#      tanaka     120
#      dtype:   int64
SR = pd.Series(Science)

# 削除する行のラベル名を指定
# SR_new = akiyama    100
#          tanaka     120
#          dtype:   int64
SR_new = SR.drop("satou")
```

### データをソート
Series型の*sort_index()*でラベルをキーに、*sort_values()*でデータをキーに並び替えを行うことができます。

```python
Science = {"satou": 100, "akiyama": 75, "tanaka": 120}

# SR = satou      100
#      akiyama     75
#      tanaka     120
#      dtype:   int64
SR = pd.Series(Science)

# ラベルをキーに昇順でソート
# SR_index = akiyama     75
#            satou      100
#            tanaka     120
#            dtype:   int64
SR_index = SR.sort_index()

# 引数の「inplace」を「True」にすると、Series型そのものが更新される
SR.sort_index(inplace = True)

# 引数の「ascending」を「False」にすると、降順ソートとなる
SR.sort_index(inplace = True, ascending=False)

# データをキーに昇順でソート
# SR_values = akiyama     75
#             satou      100
#             tanaka     120
#             dtype:   int64
SR_values = SR.sort_values()

# 引数の「inplace」を「True」にすると、Series型そのものが更新される
SR.sort_values(inplace = True)

# 引数の「ascending」を「False」にすると、降順ソートとなる
SR.sort_values(inplace = True, ascending=False)
```


# DataFrame型
DataFrame型は「表のようなもの」です。
解析対象となるデータは、csv形式、Excel形式やHTML上のデータだったりしますが、これらを読み込んで表として操作する機能を提供してくれます。

## DataFrame型の作成
様々な形式のデータをDataFrame型として読み込むことができます。

### csvファイルから作成
Pandasの*read_csv()*、または*read_table()*で、csvファイルからDataFrame型を作成することができます。
例として、以下のようなcsvファイルが存在する。

```test.csv
No,Name,Score
Students,Science,Math
akiyama,100,100
satou,75,99
tanaka,120,150
suzuki,50,50
mochidan,0,10
```

*read_csv()*を使用する場合は、以下のようにしてDataFrame型を作成できます。

```python
# 引数は読み込み先のパスで、カレントディレクトリからの相対パスで指定
DF = pd.read_csv('test.csv')

# 「header」にNoneを指定すると、1行目からデータとして読み込む
DF = pd.read_csv('test.csv', header=None)
```

*read_table()*を使用する場合は、セパレータの指定が可能です。
以下のようにしてDataFrame型を作成できます。

```python
# 引数は読み込み先のパスで、カレントディレクトリからの相対パスで指定
# 「sep」にセパレータを指定
DF = pd.read_table('test.csv', sep=',')

# 「header」にNoneを指定すると、1行目からデータとして読み込む
DF = pd.read_table('test.csv', , sep=',', header=None)
```

これら場合、DFは以下のようなDataFrame型となります。
※「*header=None*」無しで作成した場合のDataFrame型です。１行目は列名として読まれています。

|  | Students | Science | Math |
|:-:|:-:|:-:|:-:|
| 0  | akiyama  | 100  | 100  |
| 1  | satou  | 75  | 99  |
| 2  | tanaka  | 120  | 150  |
| 3  | suzuki  | 50  | 50  |
| 4  | mochidan  | 0  | 10  |

### クリップボードから作成
Pandasの*read_clipboard()*で、クリップボードからDataFrame型を作成することができます。
例として、以下のような表が存在する。

|  | Students | Science | Math |
|:-:|:-:|:-:|:-:|
| 0  | akiyama  | 100  | 100  |
| 1  | satou  | 75  | 99  |
| 2  | tanaka  | 120  | 150  |
| 3  | suzuki  | 50  | 50  |
| 4  | mochidan  | 0  | 10  |

上記の表をコピーして、クリップボードに保存したとします。
その後は以下のようにして、DataFrame型を作成できます。

```python
DF = pd.read_clipboard()

# 「header」にNoneを指定すると、1行目からデータとして読み込む
DF = pd.read_clipboard(header=None)
```

### Excelファイルから作成
Pandasの*read_excel()*で、ExcelファイルからDataFrame型を作成することができます。
ただし、結合されたセルが存在した場合、セルの結合が解除され、値であれば「NaN」、列名であれば「Unnamed: *x*」となってしまうことに注意してください。

```python
# 第一引数は読み込み先のパスで、カレントディレクトリからの相対パスで指定
# sheet_nameに読み込むシートの名前を指定
DF = pd.read_excel('test.xlsx', sheet_name='Sheet1')

# 「header」にNoneを指定すると、Excelファイルの1行目からデータとして読み込む
DF = pd.read_excel('test.xlsx', sheet_name='Sheet1', header=None)
```

### 辞書型(dict型)から作成
辞書型(dict型)からも、DataFrame型を作成することができます。
HTTPリクエスト等でJSONのデータが返却される場合、そのJSONをうまく辞書型にできればDataFrame型としてデータ操作することができます。

```python
import pandas as pd
import json

# 例えば、こんなJSONがあった場合
json_obj = """
{    
    "result": [{"Students": "akiyama", "Science": 100, "Math": 100}, 
               {"Students": "satou"  , "Science":  75, "Math":  99}, 
               {"Students": "tanaka" , "Science": 120, "Math": 150}]
}
"""

# JSONオブジェクトを作成
data = json.loads(json_obj)

# この場合、data["result"]は辞書型のリスト
# data["result"] = [{'Students': 'akiyama', 'Science': 100, 'Math': 100},
#                   {'Students': 'satou'  , 'Science':  75, 'Math':  99},
#                   {'Students': 'tanaka' , 'Science': 120, 'Math': 150}]
data["result"]

# PandasのDataFrame()の引数に辞書型のリストを渡せば、DataFrame型にできる
# 1辞書型、1行のDataFrame型になる
DF = pd.DataFrame(data["result"])
```

この場合、DFは以下のようなDataFrame型となります。

|  | Students | Science | Math |
|:-:|:-:|:-:|:-:|
| 0  | akiyama  | 100  | 100  |
| 1  | satou  | 75  | 99  |
| 2  | tanaka  | 120  | 150  |

### NumPy配列とlist型から作成
NumPy配列とlist型からも、DataFrame型を作成することができます。

```python
# data_np = [['akiyama' 100 100],
#            ['satou'    75  99]]
data = [["akiyama", 100, 100], ["satou", 75, 99]]
data_np = np.array(data)

# 「columns」に列名を指定（指定なしの場合は「0」からの連番）
DF = pd.DataFrame(data, columns = ['No', 'Name', 'Score'])
DF = pd.DataFrame(data_np, columns = ['No', 'Name', 'Score'])
```

この場合、DFは以下のようなDataFrame型となります。

|  | Students | Science | Math |
|:-:|:-:|:-:|:-:|
| 0  | akiyama  | 100  | 100  |
| 1  | satou  | 75  | 99  |

## DataFrame型の保存
DataFrame型をファイルとして保存することができます。

### csv形式で保存
DataFrame型の*to_csv()*を使用することで、DataFrame型をcsv形式で保存することができます。

```python
# なんらかの方法でDataFrame型を作成し、いろいろ編集を行ったとします
DF = pd.read_csv('test.csv')

# 第一引数は保存先のパスで、カレントディレクトリからの相対パスで指定
# セパレータのデフォルトは「,」
DF.to_csv('test_2.csv')

# 「sep」でセパレータを指定することもできます
DF.to_csv('test_3.csv', sep='_')

# インデックスとヘッダの有無を選択可能
DF.to_csv("test_4.csv", header=False, index=False)
```

## DataFrame型の操作
DataFrame型にすると、様々な操作を行うことができます。

### 列名を表示、列名を指定して表示
DataFrame型はcolumnsで列名を取得したり、列名を指定してその列だけを取得することができます。

```python
data = [["akiyama", 100, 100],
        ["satou"  ,  75,  99]]
DF = pd.DataFrame(data, columns = ['Students', 'Science', 'Math'])

# 列名を表示
# DF.columns = Index(['Students', 'Science', 'Math'], dtype='object')
DF.columns

# 以下のようにすると、指定した列をSeries型で取得できる
# 0    akiyama
# 1      satou
# Name: Students, dtype: object
DF["Students"]
DF.Students

# 2列以上取得したい場合は、それらの列名をlist型にして指定する
# 2列以上取得する場合、DataFrame型で取得される
DF[["Students", "Math"]]
```

上記のDF[["Students", "Math"]]は、以下のようなDataFrame型となります。

|  | Students　| Math |
|:-:|:-:|:-:|
| 0  | akiyama  | 100  |
| 1  | satou  | 99  |

### 行数を指定して表示
DataFrame型のilocを使用することで、指定した行のみ取得することができます。

```python
data = [["akiyama", 100, 100],
        ["satou"  ,  75,  99]]
DF = pd.DataFrame(data, columns = ['Students', 'Science', 'Math'])

# 以下のようにすると、指定した行をSeries型で取得できる
# 行番号は「0」から始まることに注意
# Students    satou
# Science        75
# Math           99
# Name: 1, dtype: object
DF.iloc[1]

# 2行以上取得したい場合は、取得したい範囲をスライスで記述
# 2行以上取得する場合、DataFrame型で取得される
DF.iloc[0:2]
```

上記のDF.iloc[0:2]は、以下のようなDataFrame型となります。

|  | Students | Science | Math |
|:-:|:-:|:-:|:-:|
| 0  | akiyama  | 100  | 100  |
| 1  | satou  | 75  | 99  |

### 先頭だけを表示、最後だけ表示
DataFrame型の*head()*で先頭だけを表示、*tail()*で最後だけ表示させることができます。

```python
data = [["akiyama", 100, 100],
        ["satou"  ,  75,  99]]
DF = pd.DataFrame(data, columns = ['Students', 'Science', 'Math'])

# 引数なしの場合は、先頭の5行まで表示
# 引数に行数を指定すると、先頭から指定した行数だけ表示
DF.head()
DF.head(1)

# 引数なしの場合は、最後の5行を表示
# 引数に行数を指定すると、最後から指定した行数だけ表示
DF.tail()
DF.tail(1)
```

### 特定の列で新しくDataFrame型を作成
以下のように、DataFrame型の特定の列だけを使って、新たにDataFrame型を作成することができます。

```python
data = [["akiyama", 100, 100],
        ["satou"  ,  75,  99]]
DF = pd.DataFrame(data, columns = ['Students', 'Science', 'Math'])

# DataFrame型「DF」の「Students」と「Math」だけを使って、新しいDataFrame型を作成
# 「columns」に行を指定しますが、存在しない列を指定した場合、その列のデータは全て「NaN」として作成される
DF_new = DataFrame(DF, columns=['Students', 'Math'])
```

上記のDF_newは、以下のようなDataFrame型となります。

|  | Students　| Math |
|:-:|:-:|:-:|
| 0  | akiyama  | 100  |
| 1  | satou  | 99  |

### 指定した行、または列を削除
DataFrame型の*drop()*を利用すると、指定した列・行が削除されたDataFrame型を取得することができます。

```python
data = [["akiyama", 100, 100],
        ["satou"  ,  75,  99]]
DF = pd.DataFrame(data, columns = ['Students', 'Science', 'Math'])

# 行を削除する場合、削除する行のindexを指定（ラベル名が付いてる場合は、ラベル名を指定）
# indexは「0」から始まるため、この場合は2行目を削除
DF_drop_axis0 = DF.drop(1)

# 行を削除する場合、引数の「axis」に「1」を指定
# また、削除する列のindexを指定（列名が付いてる場合は、列名を指定）
# 列名が付いているので、列名「Science」を削除
DF_drop_axis1 = DF.drop("Science", axis=1)
```

上記ののDF_drop_axis0は、以下のようなDataFrame型となります。

|  | Students | Science | Math |
|:-:|:-:|:-:|:-:|
| 0  | akiyama  | 100  | 100  |

DF_drop_axis1は、以下のようなDataFrame型となります。

|  | Students　| Math |
|:-:|:-:|:-:|
| 0  | akiyama  | 100  |
| 1  | satou  | 99  |

### 列を追加
以下のように、DataFrame型に新しい列を追加することができます。

```python
data = [["akiyama", 100, 100],
        ["satou"  ,  75,  99]]
DF = pd.DataFrame(data, columns = ['Students', 'Science', 'Math'])

# 列「English」を追加。初期値に「NaN」を指定。
DF['English'] = np.nan
```

|  | Students | Science | Math | English |
|:-:|:-:|:-:|:-:|:-:|
| 0  | akiyama  | 100  | 100  | NaN  |
| 1  | satou  | 75  | 99  | NaN  |

また、Series型を利用して、列を追加することもできます。

```python
data = [["akiyama", 100, 100],
        ["satou"  ,  75,  99]]
DF = pd.DataFrame(data, columns = ['Students', 'Science', 'Math'])

# English = 0        100
#           1         30
#           dtype: int64
English = pd.Series([100, 30], index=[0, 1])

# Series側のindexと、DataFrame型のindexが一致した箇所にデータが挿入
# 一致するものがない場合は、「NaN」となる
DF['English'] = English
```

|  | Students | Science | Math | English |
|:-:|:-:|:-:|:-:|:-:|
| 0  | akiyama  | 100  | 100  | 100  |
| 1  | satou  | 75  | 99  | 30  |

### 条件を満たすデータを表示
条件を満たすデータの判定、および条件を満たすデータを表示できます。

```python
data = [["akiyama", 100, 100],
        ["satou"  ,  75,  99]]
DF = pd.DataFrame(data, columns = ['Students', 'Science', 'Math'])

# 「Science」、 「Math」について、80を超えるデータが存在するか判定
DF_80over = DF[["Science", "Math"]] > 80

# 「Science」が 80を超える行を表示
Science_80over = DF[DF['Science'] > 80]
```

上記ののDF_80overは、以下のようなDataFrame型となります。

|  | Science | Math |
|:-:|:-:|:-:|:-:|
| 0  | True  | True  |
| 1  | False  | True  |

Science_80overは、以下のようなDataFrame型となります。

|  | Students | Science | Math |
|:-:|:-:|:-:|:-:|
| 0  | akiyama  | 100  | 100  |