pandas基本操作まとめ

# 0. 本記事の概要
Kaggleのコース
https://www.kaggle.com/learn/pandas
をベースとして，pandasの基本操作を纏めました．
元コースとくらべて，若干順番が前後していたり，省略あるいは追記している部分があります．

# 1. データの作成，データの読み込み
pandasのデータ→DataFrame，またはSeriesで管理する．
DataFrame：一般のテーブルデータを取り扱う型．
Series：リストを取り扱う型．すなわち，単一の列データのみ保持する．

```.py
import pandas as pd

test_data1 = pd.DataFrame({'type': ['A', 'B', 'C', 'A', 'C',], 'columnA': [1, 2, 3, 4, 5], 'columnB': [6, 7, 8, 9, 10]})
test_data2 = pd.Series([1, 2, 3, 4, 5])

"""test_data1
  type  columnA  columnB
0    A        1        6
1    B        2        7
2    C        3        8
3    A        4        9
4    C        5       10
"""

print(test_data1.shape)  # (5, 3)
print(len(test_data1))  # 5
```
データには数値以外にも，文字列を保持させることができる．

kaggleなどで使う上では，既存のcsvデータを読み込むことが多い．
これはread_csv(データパス)で行う．

```.py
csv_data = pd.read_csv("table_data.csv")
csv_data = pd.read_csv("table_data.csv", index_col=0)  # インデックス列がcsvに含まれている場合，指定できる
# 注：インデックスは数字とは限らない（が，多くの場合は数字と思われる）．
```

# 2. データの選択
DataFrameの列データにアクセスする方法が主に2つある．
１．属性として指定する方法
２．辞書型のラベルとして指定する方法

```.py
test_data1.columnA
test_data1['columnA']

"""どちらも以下のデータを返却する
0    1
1    2
2    3
3    4
4    5
"""
```

また，各行にアクセスするには，通常のリストと同様にインデックスを指定する．

```.py
test_data1['columnA'][0]  # 1が返却される
```

一方で，pandasには独自のアクセス方法が2つほど用意されている．
1つ目はilocで，行と列を数値で指定する方法．
2つ目はlocで，行と列をラベルで指定する方法．

```.py
# 以下の2つはどちらも同じ場所を指している
test_data1.iloc[0, 1]  # 1が返却される
test_data1.loc[0, 'columnA']  # 1が返却される
```

注意すべきはlocの書き方．locでは「ラベルを指定する」という動作の関係上，スライスの定義が通常と異なる．すなわち，__loc[0:3]と書いた場合，0行目のデータから3行目のデータまで，計4行が返却される__．一方，通常のpythonのスライスでは末尾のインデックスを含まないため，[0:3]と書いた場合は0から2までのデータが返却される（ilocはこちらの方式）．この違いに注意する．

```.py
test_data1.loc[0:3]  # インデックス0から3までが返却される!!
test_data1.iloc[0:3]  # インデックス0から2までが返却される
```

# 3. 条件による抽出・データ同士の演算
pandasのデータはpython（やnumpy）のリストと同じような演算が可能．
例えば，

```.py
test_data1.type == 'A'

"""
0     True
1    False
2    False
3     True
4    False
"""
```
と書くと，各行がこの条件を満たすかどうかのブーリアンが返却される．これを利用すると，例えば，

```.py
test_data1[test_data1.type == 'A']

"""
  type  columnA  columnB  columnC  columnD
0    A        1        6        0        7
3    A        4        9        0       13
"""
```
とすることで，typeがAであるデータのみを抽出できる．
複数の条件を組み合わせるには"&（and）"や"|（or）"を使う．

また，各列同士を足し合わせるなども可能．

```.py
test_data1.columnA + test_data1.columnB

"""
0     7
1     9
2    11
3    13
4    15
"""
```

# 4. 列データの追加
既存のDataFrameに新たな列を追加する場合，以下のように書く．

```.py
test_data1['columnC'] = 0

"""
  type  columnA  columnB  columnC
0    A        1        6        0
1    B        2        7        0
2    C        3        8        0
3    A        4        9        0
4    C        5       10        0
"""
```

これでcolumnCが追加され，その全ての値が0で初期化される．
イテレータで初期化することも可能．

3.の演算と組み合わせることで，既存のデータから新たなデータ列を作ることができる．
（例えば，質量と体積というデータから，質量÷体積＝密度　を新たに作る，など）

```.py
test_data1['columnD'] = test_data1.columnA + test_data1.columnB

"""
  type  columnA  columnB  columnC  columnD
0    A        1        6        0        7
1    B        2        7        0        9
2    C        3        8        0       11
3    A        4        9        0       13
4    C        5       10        0       15
"""
```

# 5. データ統計
あるデータについて平均や標準偏差を求めたい．
その場合，describe()を使う．

```.py
test_data1.columnA.describe()

"""
count    5.000000
mean     3.000000
std      1.581139
min      1.000000
25%      2.000000
50%      3.000000
75%      4.000000
max      5.000000
Name: columnA, dtype: float64
"""
```

データ数，平均，標準偏差，最小値，各パーセンタイル値，最大値が表示された．
describe()は対象とするデータの種類によって異なるサマリーを出力する．

```.py
test_data1.type.describe()  # 文字列の場合

"""
count     5
unique    3
top       C
freq      2
Name: type, dtype: object
"""
```

特定の統計値を指定して出力させることもできる．

```.py
test_data1.columnA.mean()  # 平均値が出力される
test_data1.type.unique()  # ユニークな要素がリストとして出力される
test_data1.type.value_counts()  # ユニークな要素と，その頻度が出力される
```

# 6. 関数によるデータ処理
3.では列同士の演算を行った．pandasでは更に，自作の関数を各データに適用することができる．
これには3通りの方法がある．
・map()を使う方法：ある列のデータ（Series）に対して操作を行う．

```.py
columnA_max = test_data1.columnA.max()
columnA_min = test_data1.columnA.min()
mapped = test_data1.columnA.map(lambda x: (x - columnA_min) / (columnA_max - columnA_min))

"""
0    0.00
1    0.25
2    0.50
3    0.75
4    1.00
"""
```
この場合，mapに渡す関数は単一の引数を受け取り，操作を施したSeriesを返却する．

・apply()を使う方法：各行（列）のデータに対して操作を行う．

```.py
def convert_columnD(row):
    row.columnD = row.columnD * row.columnA
    return row

applied = test_data1.apply(convert_columnD, axis='columns')

"""
  type  columnA  columnB  columnC  columnD
0    A        1        6        0        7
1    B        2        7        0       18
2    C        3        8        0       33
3    A        4        9        0       52
4    C        5       10        0       75
"""
```
この場合，applyに渡す関数は各行（列）のデータを丸ごと受け取り，操作を施したDataFrameを返却する．そのため，上の例のように，複数の列（行）データを使った演算が可能．

・applymap()を使う方法：elementwise（各要素ごと）に操作を行う（See https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.applymap.html）．

# 7. グループ化
例えば，test_data1のデータをtype毎に分析したいとする．
3.のように条件式で指定してもよいが，typeの種類が増えるにつれて煩雑になってしまう．
こういった場合，groupby()という関数でグループ化を行うと便利．

```.py
test_data1.groupby('type').columnA.mean()  # 各typeのデータ内で平均を求める

"""
type
A    2.5
B    2.0
C    4.0
"""
```

groupbyにはグループ化対象のラベルを渡す．これは，リストの形で複数渡すことも可能で，この場合，データはMulti-indexed（階層化されたインデックスを持つデータ）となる．

# 8. ソート
データをソートするには，sort_valuesを使う．

```.py
sorted_data = test_data1.sort_values(by='columnA', ascending=False)  # データAの降順に並び替え

"""
  type  columnA  columnB  columnC  columnD
4    C        5       10        0       15
3    A        4        9        0       13
2    C        3        8        0       11
1    B        2        7        0        9
0    A        1        6        0        7
"""
```
インデックスで並べ直したい場合はsort_index()を使う．


データのざっくりした傾向を掴むために，各グループをそのデータ数順に表示したい場合がある．
7.と組み合わせて，以下のようにする．

```.py
group_sizes_sorted = test_data1.groupby('type').size().sort_values(ascending=False)

"""
type
A    2
C    2
B    1
"""
```


# 9. リネーム
ある列（あるいは行）をリネームするには，rename()を使う．
引数には，リネームする対象を渡す．

```.py
renamed = test_data1.rename(columns={'type': 'class'})  # 列のリネーム

"""
  class  columnA  columnB  columnC  columnD
0     A        1        6        0        7
1     B        2        7        0        9
2     C        3        8        0       11
3     A        4        9        0       13
4     C        5       10        0       15
"""
```

行（インデックス）のリネームもできる．

```.py
index_renamed = test_data1.rename(index={0: 'first', 1: 'second'})  # 行のリネーム

"""
       type  columnA  columnB  columnC  columnD
first     A        1        6        0        7
second    B        2        7        0        9
2         C        3        8        0       11
3         A        4        9        0       13
4         C        5       10        0       15
"""
```

ただし，行インデックスそのものをリネームするより，ある列をインデックス列として指定することの方が多いかも．これはset_index()で行う．

```.py
type_indexed = test_data1.set_index('type')

"""
      columnA  columnB  columnC  columnD
type                                    
A           1        6        0        7
B           2        7        0        9
C           3        8        0       11
A           4        9        0       13
C           5       10        0       15
"""
```

実は，pandasのDataFrameでは，行と列「そのもの」にname属性が定義されている．
これを設定するには，rename_axis()を使う．

```.py
axis_renamed = test_data1.rename_axis('product', axis='rows').rename_axis('field', axis='columns')

"""
field   type  columnA  columnB  columnC  columnD
product                                         
0          A        1        6        0        7
1          B        2        7        0        9
2          C        3        8        0       11
3          A        4        9        0       13
4          C        5       10        0       15
"""
```


# 10. データの結合
別々のテーブルデータを読み込んで，それらを1つのデータに結合したい場合がある．
pandasにはこれを実現する関数がいくつか存在する．

・concat()：2つのDataFrameが同じ種類の列を持っている場合など
この場合は純粋に2つのデータをくっつける操作になる．

```.py
test_data_one = pd.DataFrame({'type': ['A', 'B', 'C'], 'columnA': [1, 2, 3], 'columnB': [11, 12, 13]})
test_data_another = pd.DataFrame({'type': ['C', 'C', 'B'], 'columnA': [2, 4, 8], 'columnB': [3, 9, 27]})
concated = pd.concat([test_data_one, test_data_another])

"""
  type  columnA  columnB
0    A        1       11
1    B        2       12
2    C        3       13
0    C        2        3
1    C        4        9
2    B        8       27
"""
```

インデックスを0からふり直すには，ignore_indexオプションをTrueにする．
（参考：https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.concat.html）

```.py
concated = pd.concat([test_data_one, test_data_another], ignore_index=True)

"""
  type  columnA  columnB
0    A        1       11
1    B        2       12
2    C        3       13
3    C        2        3
4    C        4        9
5    B        8       27
"""
```

また，列方向に結合する（＝データ数は同じで，各データに追加の列データを加える）ことも可能．

・join()：ある共通のインデックスに従って結合したい場合など
（説明が難しいが，）例えば，同じ日に別々の地域で観測した天候を纏めたい場合を想定する．
データを日付毎に纏めたいが，地域ごとで区別できるようにしたい．

```.py
weather_region_A = pd.DataFrame({'date': ['01', '02', '03'], 'weather': ['cloudy', 'sunny', 'sunny']})
weather_region_B = pd.DataFrame({'date': ['01', '02', '03'], 'weather': ['sunny', 'rainy', 'rainy']})
```

この場合，まず，
・set_index()によって，日付をインデックス列に指定する．

```.py
data_left = weather_region_A.set_index('date')
data_right = weather_region_B.set_index('date')
```


・join()によって，データを日付のインデックス毎に結合する．
なお，join()はDataFrameの関数なので，data_left.join()という風に書く．
また，suffix（接尾辞）を指定することができる．

```.py
data_joined = data_left.join(data_right, lsuffix='_A', rsuffix='_B')

"""
     weather_A weather_B
date                    
01      cloudy     sunny
02       sunny     rainy
03       sunny     rainy
"""
```


# 11. データ型の確認・変換
データ型はdtypes属性で確認可能．

```.py
test_data1.dtypes
# test_data1.columnD.dtypes  # 各列に対しても動作する

"""
type       object
columnA     int64
columnB     int64
columnC     int64
columnD     int64
"""
```
ただし，文字列から成るデータはobject型と見なされる．
データの変換はastype(変換後のデータ型)で行う．

```.py
converted_columnD = test_data1.columnD.astype('float64')

"""
0     7.0
1     9.0
2    11.0
3    13.0
4    15.0
Name: columnD, dtype: float64
"""
```


# 12. 欠損値の検索・データの置き換え
テーブルデータに欠損値（データが存在しない，NaNで表される）がある場合を考える．

```.py
"""test_data_missing
  type  columnA  columnB
0    A      1.0      6.0
1    B      2.0      7.0
2    C      NaN      8.0
3    A      NaN      9.0
4    C      5.0      NaN
"""
```

特定の列にNaNを含むデータの抽出は以下のように行う．

```.py
test_data_missing[pd.isnull(test_data_missing.columnA)]

"""
  type  columnA  columnB
2    C      NaN      8.0
3    A      NaN      9.0
"""
```

pd.isnull(検索する列のラベル)は各行の該当列がNaNかどうかのブーリアンを返却する．
これによって抽出を行っている．

NaNをあるデータで置き換える（埋める）場合，fillna(置き換え後のデータ)を使う．

```.py
filled_columnA = test_data_missing.columnA.fillna('Unknown')

"""
0        1.0
1        2.0
2    Unknown
3    Unknown
4        5.0
"""
```

また，fillnaと同じような動作で，特定のデータを別のデータに置き換えたい場合，replace(置き換え対象，置き換え後データ)を使う．

```.py
replaced_type = test_data_missing.type.replace('A', 'AAA')

"""
0    AAA
1      B
2      C
3    AAA
4      C
"""
```