pandas基本操作まとめ


0. 本記事の概要

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

1. データの作成,データの読み込み

pandasのデータ→DataFrame,またはSeriesで管理する.
DataFrame:一般のテーブルデータを取り扱う型.
Series:リストを取り扱う型.すなわち,単一の列データのみ保持する.

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(データパス)で行う.

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

2. データの選択

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

test_data1.columnA
test_data1['columnA']

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

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

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

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

# 以下の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はこちらの方式).この違いに注意する.

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

3. 条件による抽出・データ同士の演算

pandasのデータはpython(やnumpy)のリストと同じような演算が可能.
例えば,

test_data1.type == 'A'

"""
0     True
1    False
2    False
3     True
4    False
"""

と書くと,各行がこの条件を満たすかどうかのブーリアンが返却される.これを利用すると,例えば,

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)"を使う.

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

test_data1.columnA + test_data1.columnB

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

4. 列データの追加

既存のDataFrameに新たな列を追加する場合,以下のように書く.

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.の演算と組み合わせることで,既存のデータから新たなデータ列を作ることができる.
(例えば,質量と体積というデータから,質量÷体積=密度 を新たに作る,など)

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()を使う.

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()は対象とするデータの種類によって異なるサマリーを出力する.

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

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

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

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

6. 関数によるデータ処理

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

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()を使う方法:各行(列)のデータに対して操作を行う.

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()という関数でグループ化を行うと便利.

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

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

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

8. ソート

データをソートするには,sort_valuesを使う.

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.と組み合わせて,以下のようにする.

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

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

9. リネーム

ある列(あるいは行)をリネームするには,rename()を使う.
引数には,リネームする対象を渡す.

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
"""

行(インデックス)のリネームもできる.

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()で行う.

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()を使う.

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つのデータをくっつける操作になる.

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)

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():ある共通のインデックスに従って結合したい場合など
(説明が難しいが,)例えば,同じ日に別々の地域で観測した天候を纏めたい場合を想定する.
データを日付毎に纏めたいが,地域ごとで区別できるようにしたい.

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()によって,日付をインデックス列に指定する.

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

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

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属性で確認可能.

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

"""
type       object
columnA     int64
columnB     int64
columnC     int64
columnD     int64
"""

ただし,文字列から成るデータはobject型と見なされる.
データの変換はastype(変換後のデータ型)で行う.

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で表される)がある場合を考える.

"""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を含むデータの抽出は以下のように行う.

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(置き換え後のデータ)を使う.

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

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

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

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

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