Python - pandas

公開日:2019-06-22 更新日:2019-06-22
[Python]

1. 概要

pandas の使い方についてです。
pandas は、データ操作を行うためのモジュールです。
例えば、CSVを読み込んで、2次元のデータを必要な形式に加工したり、集計することが簡単に行えます。
データを、機械学習で使いやすいように、整える際にも使用されます。



2. データ

pandas では、
1次元のデータを Series
2次元のデータを DataFrame
3次元のデータを Panel
で保持します。

3. Series

1次元のデータを保持するクラスです。
DataFrame に対して、行・列の設定・取得をする際に Series を使います。

3-1. 生成・値の設定・値の取得

import pandas as pd

# 生成
s = pd.Series(['Apple', 'Red', 500])

# 取得
print(s[0]) # Apple
print(s[1]) # Red
print(s[2]) # 500
print(s.count()) # 要素数:3

# 設定
s[0] = 'Orange'
print(s[0]) # Orange

3-2. インデックス(キー)の設定

インデックス(キー)を指定すると、辞書(連想配列)と同じように文字列で要素を参照できます。
s = pd.Series(
        ['Apple', 'Red', 500],
        index = ['name', 'color', 'price']
)

print(s['name'])  # Apple
print(s['color']) # Red

s['price'] = 700
print(s['price']) # 700

3-3. 値の取得 その2

at、iat で1つの値、loc、iloc で複数の値を取得できます。
先頭に i が付く方は [ ] に数値のインデックスを指定して、
i が付かない方には、文字列のキーを指定します。

iloc、loc は、1つの値も取得できますが、スライスを使って、複数の値を持った Series を取得できます。

s = pd.Series(['Apple', 'Red', 500], index = ['name', 'color', 'price'])

print(s.iat[1])       # Red
print(s.at['color'])  # Red
print(s.iloc[1])      # Red
print(s.loc['color']) # Red

s2 = s.iloc[0:2] # または s.loc['name':'color']
print(s2[0])       # Apple
print(s2[1])       # Red
print(s2['name'])  # Apple
print(s2['color']) # Red

3-4. データの追加

未使用のインデックスを指定して代入すると追加されます。
s = pd.Series(['Apple', 'Red', 500])
s[3]     = 'abc'
s[999]   = '123'
s['key'] = 'test'
print(s)

# 0      Apple
# 1        Red
# 2        500
# 3        abc
# 999      123
# key     test

append() で 他の Series を追加することもできます。
s = pd.Series(['Apple', 'Red', 500])
s = s.append(pd.Series([1, 2, 3]), ignore_index = True)
print(s)

# 0    Apple
# 1      Red
# 2      500
# 3        1
# 4        2
# 5        3
ignore_index を True にすると、全てのインデックスが 0 から振り直されます。
False にすると、追加した要素だけインデックスが 0 から振られます。
既存のインデックスと重複してもエラーにならないため要注意。

3-5. データの削除

drop() で削除すると、要素が削除された新しい Series が返ります。
削除してもインデックスは変わりません。
s = pd.Series(['Apple', 'Red', 500])
s = s.drop(0)
print(s)

# 1    Red
# 2    500

inplace = True にすると、メソッドを実行した Series 自体から要素が削除されます。
s.drop(0, inplace = True)

3-6. その他

最大、最小、平均
s = pd.Series([1,2,3,4,5])
print(s.max())  # 5
print(s.min())  # 1
print(s.mean()) # 3

絞り込み
s = pd.Series([1,2,3,4,5,6,7,8,9,10])
s = s[ s >= 5 ]
print(s) # 5 6 7 8 9 10

重複の削除
s = pd.Series([1,1,1,1,1, 2,2,2,2,2, 3,4,5])
s.drop_duplicates(inplace = True)

print(s) # 1 2 3 4 5

ソート
s = pd.Series([5, 1, 3, 2, 4])
s.sort_values(inplace = True)
print(s) # 1 2 3 4 5

各要素にラムダ式を適用する
s = pd.Series([1,2,3,4,5])
s = s.apply(lambda v : v ** 2)
 
print(s) # 1 4 9 16 25

この他にも様々なメソッドが用意されています。

4. DataFrame


4-1. 生成

index は行、columns は列(ラベル)に指定する名前で、要素にアクセスする際に使用します。
省略すると 0 から連番が振られます。
import pandas as pd

df = pd.DataFrame(
    [
        ['太郎', 5, 4, 4, 3],
        ['花子', 4, 5, 5, 4],
        ['次郎', 1, 2, 1, 2],
    ],
    index   = ['no1', 'no2', 'no3'],
    columns = ['名前', '国語', '算数', '理科', '社会']
)
print(df)

実行結果
     名前  国語  算数  理科  社会
no1  太郎  5     4     4     3
no2  花子  4     5     5     4
no3  次郎  1     2     1     2

4-2. 値の取得と設定

iloc はインデックス、loc は名前を指定して、要素に参照できます。
どちらも [行, 列] の順番になります。
また、df[列名] で列を特定して、そこから行のインデックス、または名前を指定して、要素を参照することができます。

取得
print(df.iloc[2, 0])          # 次郎
print(df.loc['no3', '名前'])  # 次郎
print(df['名前'].no3)         # 次郎
print(df['名前'][2])          # 次郎

設定
df.loc['no2', '名前'] = '三郎'
df.iloc[2, 0]         = '四郎'

また、以下のようにして設定しようとすると、警告が出ます。
これは、列(Series)を取得してから、列の要素を設定することになりますが、
元の DataFrame に反映されるためです。
df['名前'][1] = '三郎'
df['名前'][2] = '四郎'

# 警告
# SettingWithCopyWarning:
# A value is trying to be set on a copy of a slice from a DataFrame

上記警告は以下により消せますが、loc か iloc を使った方法に書き直した方が良いかもしれません。
参考:警告の制御
import warnings
warnings.filterwarnings('ignore')

以下のように列をコピーしてから値を設定して、元の DataFrame に列を設定すると、警告が出なくなります。
s = df['名前'].copy()
s[1] = '三郎'
s[2] = '四郎'
df['名前'] = s

4-3. いろんな取得方法

以下は「1. 生成」のソースの続きです。

1列の取得
s = df.iloc[:, 0]
#s = df.loc[:, '名前']
#s = df['名前']
print(type(s)) # <class 'pandas.core.series.Series'>
print(s)

# no1    太郎
# no2    花子
# no3    次郎

複数列の取得
s = df.iloc[:, [0, 1]]
#s = df.loc[:, ['名前', '国語']]
#s = df.loc[:, '名前':'国語']
print(type(s)) # <class 'pandas.core.frame.DataFrame'>
print(s)

#      名前  国語
# no1  太郎   5
# no2  花子   4
# no3  次郎   1

1行の取得
s = df.iloc[1]
#s = df.loc['no2']

print(type(s))   # <class 'pandas.core.series.Series'>
print(s['国語']) # 4
print(s)

# 名前    花子
# 国語     4
# 算数     5
# 理科     5
# 社会     4

複数行の取得
s = df.iloc[0:3]
#s = df.loc['no1':'no3']

print(type(s))   # <class 'pandas.core.frame.DataFrame'>
print(s)
#      名前  国語  算数  理科  社会
# no1  太郎   5    4     4     3
# no2  花子   4    5     5     4
# no3  次郎   1    2     1     2

部分的な範囲の取得
#s = df.loc['no1':'no2', ['国語', '算数']]
s = df.iloc[0:2, 1:3]
print(type(s))
print(s)

#      国語  算数
# no1  5     4
# no2  4     5

ランダム、先頭、最後の行の取得
import pandas as pd

df = pd.DataFrame(
    [
        ['太郎', 5, 4, 4, 3],
        ['花子', 4, 5, 5, 4],
        ['次郎', 1, 2, 1, 2],
        ['三郎', 3, 3, 3, 3],
        ['四郎', 4, 4, 4, 4],
        ['五郎', 5, 5, 5, 5],
    ],
    columns = ['名前', '国語', '算数', '理科', '社会']
)

# ランダムで3行を取得
print(df.sample(n = 3))

#    名前  国語  算数  理科  社会
# 2  次郎   1   2   1   2
# 1  花子   4   5   5   4
# 0  太郎   5   4   4   3


# 先頭の 2行を取得
print(df.head(2))

#    名前  国語  算数  理科  社会
# 0  太郎   5   4   4   3
# 1  花子   4   5   5   4


# 最後の 2行を取得
print(df.tail(2))

#    名前  国語  算数  理科  社会
# 4  四郎   4   4   4   4
# 5  五郎   5   5   5   5

4-4. 行の追加

Series を生成して、append() で追加します。
引数の name には、DataFrame の Index に該当する部分を設定します。
d = {'名前':'三郎', '国語': 1, '算数': 2, '理科': 3, '社会': 4}
s = pd.Series(d, name = 'no4')
df = df.append(s)

#      名前  国語  算数  理科  社会
# no1  太郎  5     4     4     3
# no2  花子  4     5     5     4
# no3  次郎  1     2     1     2
# no4  三郎  1     2     3     4     [ 追加 ] 

上記は以下のように Series を生成しても同じ動作になります。
index は要素毎のキーになります。
s = pd.Series(['三郎', 1, 2, 3, 4], index = df.columns, name = 'no4')

Series を生成せずに、辞書(連想配列)を渡して追加します。
この方法だと Index が振り直されます。
また、ignore_index = True にしないと、エラーになります。
d = {'名前':'三郎', '国語': 1, '算数': 2, '理科': 3, '社会': 4}
df = df.append(d, ignore_index = True)
print(df)

#    名前  国語  算数  理科  社会
# 0  太郎  5     4     4     3
# 1  花子  4     5     5     4
# 2  次郎  1     2     1     2
# 3  三郎  1     2     3     4     [ 追加 ] 

4-5. 列の追加

未使用のカラムに代入すると列が追加されます。
要素数と DataFrame の行数が一致しない場合はエラーになります。
df['英語'] = [1, 2, 3]

concat() で列(Series)を追加します。
DataFrame に index が指定されている場合は、
Series でも同じ index を指定する必要があります。
index が一致しない場合は、行が追加されるので要注意。
s = pd.Series([5,4,3], index = ['no1', 'no2', 'no3'], name = '英語')
df2 = pd.concat([df, s], axis = 1)
print(df2)

concat() で複数の列(DataFrame)を追加します。
df2 = pd.DataFrame(
    [
        [5, 5],
        [4, 4],
        [3, 3],
    ],
    index   = ['no1', 'no2', 'no3'],
    columns = ['体育', '音楽']
)
df3 = pd.concat([df, df2], axis = 1) #0:行 1:列
print(df3)

#      名前  国語  算数  理科  社会  体育  音楽
# no1  太郎  5     4     4     3     5     5
# no2  花子  4     5     5     4     4     4
# no3  次郎  1     2     1     2     3     3

4-6. 行・列の削除

行の削除
df.drop('no1', axis = 0, inplace = True)
#df.drop(['no1', 'no2'], axis = 0, inplace = True)
print(df)

#      名前  国語  算数  理科  社会
# no2  花子  4     5     5     4
# no3  次郎  1     2     1     2

列の削除
df.drop('社会', axis = 1, inplace = True)
#df.drop(['理科', '社会'], axis = 1, inplace = True)
print(df)

#      名前  国語  算数  理科
# no1  太郎  5     4     4
# no2  花子  4     5     5
# no3  次郎  1     2     1

4-7. サイズ

行数と列数は shape と len() で取得できます。
print(df.shape[0])     # 行数
print(df.shape[1])     # 列数
print(df.size)         # 全体の要素数 = 行数 * 列数
print(len(df))         # 行数
print(len(df.columns)) # 列数

5. CSVファイルの入出力

DataFrame を CSV 出力して、それを読み込んで画面に出力します。
index の列を使う場合は、読み込み時に index_col でインデックスの列を指定します。
import pandas as pd

# 生成
df = pd.DataFrame(
    [
        ['太郎', 5, 4, 4, 3],
        ['花子', 4, 5, 5, 4],
        ['次郎', 1, 2, 1, 2],
    ],
    index   = ['no1', 'no2', 'no3'],
    columns = ['名前', '国語', '算数', '理科', '社会']
)

# CSVファイルの書き込み
df.to_csv('test.csv', encoding="utf-8")

# CSVファイルの読み込み
df2 = pd.read_csv('test.csv', encoding="utf-8", index_col = 0)
print(df2)

#      名前  国語  算数  理科  社会
# no1  太郎   5   4   4   3
# no2  花子   4   5   5   4
# no3  次郎   1   2   1   2

出力された test.csv。行の先頭にインデックスが入っています。
,名前,国語,算数,理科,社会
no1,太郎,5,4,4,3
no2,花子,4,5,5,4
no3,次郎,1,2,1,2

インデックスを使用しない場合は以下のようにします。
# CSVファイルの書き込み
df.to_csv('test.csv', encoding = 'utf-8', index = False)

# CSVファイルの読み込み
df2 = pd.read_csv('test.csv', encoding = 'utf-8')

出力された test.csv
名前,国語,算数,理科,社会
太郎,5,4,4,3
花子,4,5,5,4
次郎,1,2,1,2

また、以下のようにして、簡単にExcelファイルの入出力も行えます。
df.to_excel('test.xlsx')
df3 = pd.read_excel('test.xlsx')

6. グラフ表示

plot() で、簡単にグラフを表示することができます。
凡例に日本語が表示されない場合は、フォント一覧から日本語フォントを探して設定してください。
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt

# フォント一覧の表示
#fonts = matplotlib.font_manager.findSystemFonts()
#for f in fonts:
#    print(matplotlib.font_manager.FontProperties(fname = f).get_name())

# フォントの設定    
plt.rcParams["font.family"] = 'Yu Mincho'

df = pd.DataFrame(
    [
        ['太郎', 5, 4, 4, 3],
        ['花子', 4, 5, 5, 4],
        ['次郎', 1, 2, 1, 2],
    ],
    index   = ['no1', 'no2', 'no3'],
    columns = ['名前', '国語', '算数', '理科', '社会']
)

df.plot(kind = 'bar')
plt.show()

7. numpy.ndarray との相互変換

import pandas as pd

df = pd.DataFrame(
    [
        ['太郎', 5, 4, 4, 3],
        ['花子', 4, 5, 5, 4],
        ['次郎', 1, 2, 1, 2],
    ],
    index   = ['no1', 'no2', 'no3'],
    columns = ['名前', '国語', '算数', '理科', '社会']
)

# pandas -> numpy
nd = df.values
print(type(nd)) # <class 'numpy.ndarray'>
print(nd)
# [['太郎' 5 4 4 3]
#  ['花子' 4 5 5 4]
#  ['次郎' 1 2 1 2]]

# numpy -> pandas
df2 = pd.DataFrame(nd) # <class 'pandas.core.frame.DataFrame'>
print(type(df2))
print(df2)
#    0    1  2  3  4
# 0  太郎  5  4  4  3
# 1  花子  4  5  5  4
# 2  次郎  1  2  1  2