まずは準備としてモジュールをインポートした後に GitHub のリポジトリにあるサンプルデータ (machine-sales.csv) を読み込みます.このとき,「date」列は日付形式で読み込むように注意します.この指定をせずに Object 形式(ここでは文字列形式)で読み込んでしまうと後で年や月などの情報を取り出す作業時に問題が起こります.
モジュールをインポートして CSV ファイルを読み込む
# モジュールのインポート
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import set_matplotlib_formats
# from matplotlib_inline.backend_inline import set_matplotlib_formats # バージョンによってはこちらを有効に
set_matplotlib_formats('retina')
# CSV ファイルを読み込む
url = "https://github.com/rinsaka/sample-data-sets/blob/master/machine-sales.csv?raw=true"
df = pd.read_csv(url, parse_dates=["date"])
df
上のデータを確認します.branch(支店または営業所)には Kobe と Osaka のデータがありそうです.staff(営業担当者)は Kobe 支店に Eto,Gondo,Saito など,Osaka 支店には Sato,Kato,Kito, Hyodo などが所属していそうです.ここでは,支店ごとや営業担当者ごとに受注総額や件数を集計することを考えます.Pandas では groupby()
によるグループ化や pivot_table()
によるピボットテーブルを使うことで簡単にこのような集計ができるようになります.
念の為,各列のデータ型表示して,「date」列が日付形式になっていることを確認します.
df.dtypes
id int64
date datetime64[ns]
branch object
staff object
amount int64
dtype: object
まず,branch でグループ化して branch ごとに受注総額を集計してみよう.
branch でグループ化
tbl = df.groupby('branch').sum()
tbl
上の例では,「ID」列までも合計を集計してしまっています.したがって,次の方法で「amount」列のみの集計を行います.
df.groupby('branch')['amount'].sum()
branch Kobe 33128035 Osaka 90554395 Name: amount, dtype: int64
次は合計ではなく,受注件数を集計してみよう.
df.groupby('branch')['amount'].count()
branch Kobe 215 Osaka 584 Name: amount, dtype: int64
さらに平均を集計してみます.
df.groupby('branch')['amount'].mean()
branch Kobe 154083.883721 Osaka 155058.895548 Name: amount, dtype: float64
様々な集約関数の結果を得るには describe()
を使うと良いでしょう.
tbl = df.groupby('branch')['amount'].describe()
tbl
上で求めた様々な集約関数の結果は unstack()
によって次のような階層インデックスの形式になります.
df.groupby('branch')['amount'].describe().unstack()
branch count Kobe 2.150000e+02 Osaka 5.840000e+02 mean Kobe 1.540839e+05 Osaka 1.550589e+05 std Kobe 1.912500e+05 Osaka 1.873390e+05 min Kobe 1.250000e+03 Osaka 1.840000e+03 25% Kobe 4.493700e+04 Osaka 4.677625e+04 50% Kobe 8.480000e+04 Osaka 1.034250e+05 75% Kobe 1.901675e+05 Osaka 1.892438e+05 max Kobe 1.261505e+06 Osaka 1.638750e+06 dtype: float64
branch と staff グループ化して集計することもできます.
df.groupby(['branch', 'staff'])['amount'].sum()
branch staff Kobe Eto 6879225 Gondo 7047707 Goto 7504804 Saito 11696299 Osaka Hyodo 12303267 Kato 20845072 Kito 16250378 Muto 18250009 Sato 22905669 Name: amount, dtype: int64
集計の方法を変更して staff ごとに集計してみよう.
df.groupby('staff')['amount'].sum()
staff Eto 6879225 Gondo 7047707 Goto 7504804 Hyodo 12303267 Kato 20845072 Kito 16250378 Muto 18250009 Saito 11696299 Sato 22905669 Name: amount, dtype: int64
上の結果を受注総額でソートしたい場合,集計したデータフレームを作成してから,そのデータフレームをソートするという2つのステップが必要になります.このとき ascending=[True]
を指定すると昇順ソートになります.
sorted_df = df.groupby('staff')['amount'].sum().reset_index()
sorted_df = sorted_df.sort_values(by=['amount'], ascending=[True])
print(sorted_df)
staff amount 0 Eto 6879225 1 Gondo 7047707 2 Goto 7504804 7 Saito 11696299 3 Hyodo 12303267 5 Kito 16250378 6 Muto 18250009 4 Kato 20845072 8 Sato 22905669
降順ソートを行いたい場合は ascending=[False]
を指定すると良いでしょう.
sorted_df = df.groupby('staff')['amount'].sum().reset_index()
sorted_df = sorted_df.sort_values(by=['amount'], ascending=[False])
print(sorted_df)
staff amount 8 Sato 22905669 4 Kato 20845072 6 Muto 18250009 5 Kito 16250378 3 Hyodo 12303267 7 Saito 11696299 2 Goto 7504804 1 Gondo 7047707 0 Eto 6879225
再び,branch と staff グループ化して集計します.
df.groupby(['branch', 'staff'])['amount'].sum()
branch staff Kobe Eto 6879225 Gondo 7047707 Goto 7504804 Saito 11696299 Osaka Hyodo 12303267 Kato 20845072 Kito 16250378 Muto 18250009 Sato 22905669 Name: amount, dtype: int64
上の結果を branch と staff の複数の項目でソートすることも可能です.例えば,branch は昇順で,staff は降順でソートします.
sorted_df = df.groupby(['branch', 'staff'])['amount'].sum().reset_index()
sorted_df = sorted_df.sort_values(by=['branch', 'amount'], ascending=[True, False])
sorted_df.reset_index(drop=True)
時系列データを処理することを考えたい.具体的には,年ごとや月ごと,四半期ごとなどの受注総額などを集計したい.このような場合には日付のデータから年,月,四半期などの情報を取得する必要があります.まずは年などの情報を取得する方法を確認します.
まず,「data」列のデータ形式が datetime64 になっていることを確認しておきます.
df['date']
0 2018-07-16 1 2018-07-10 2 2018-07-12 3 2018-07-15 4 2018-07-11 ... 794 2020-04-29 795 2020-04-30 796 2020-04-28 797 2020-04-30 798 2020-04-30 Name: date, Length: 799, dtype: datetime64[ns]
まず,次の方法で「date」列のデータから年の情報を取得することができます.なお,次のコードで AttributeError
が表示された場合は,「date」列のデータ形式が「datetime64[ns]」になっていない可能性があります.CSV ファイルの読み込み時にデータ形式を正しく指定してください.
df['date'].dt.strftime('%Y')
0 2018 1 2018 2 2018 3 2018 4 2018 ... 794 2020 795 2020 796 2020 797 2020 798 2020 Name: date, Length: 799, dtype: object
引数に '%m'
を指定すると月の情報を取得できます.
df['date'].dt.strftime('%m')
0 07 1 07 2 07 3 07 4 07 .. 794 04 795 04 796 04 797 04 798 04 Name: date, Length: 799, dtype: object
年月の情報に変換することも可能です.
df['date'].dt.strftime('%Y-%m')
0 2018-07 1 2018-07 2 2018-07 3 2018-07 4 2018-07 ... 794 2020-04 795 2020-04 796 2020-04 797 2020-04 798 2020-04 Name: date, Length: 799, dtype: object
さらに,to_period("Q")
によって四半期のデータを生成できます.
df['date'].dt.to_period("Q")
0 2018Q3 1 2018Q3 2 2018Q3 3 2018Q3 4 2018Q3 ... 794 2020Q2 795 2020Q2 796 2020Q2 797 2020Q2 798 2020Q2 Name: date, Length: 799, dtype: period[Q-DEC]
「data」列を様々なフォーマットに変換する方法がわかったので,データフレームに変換した列を追加していく.まずは「year」列を追加します.
df['year'] = df['date'].dt.strftime('%Y')
df
「month」列を追加する
df['month'] = df['date'].dt.strftime('%m')
df
「year_month」列を追加します.
df['year_month'] = df['date'].dt.strftime('%Y-%m')
df
四半期「quarter」の列を追加します.
df['quarter'] = df['date'].dt.to_period("Q")
df
例えば,年ごとにスタッフの受注総額を表示したければ次のようにすると良いでしょう.
df.groupby(['year', 'staff'])['amount'].sum()
year staff 2018 Eto 1766720 Gondo 1451968 Goto 989451 Hyodo 1916070 Kato 1822396 Kito 1781146 Muto 3086176 Saito 1523216 Sato 4994511 2019 Eto 3143316 Gondo 4082637 Goto 2475689 Hyodo 5695402 Kato 10258507 Kito 7116814 Muto 9585036 Saito 5982719 Sato 12465556 2020 Eto 1969189 Gondo 1513102 Goto 4039664 Hyodo 4691795 Kato 8764169 Kito 7352418 Muto 5578797 Saito 4190364 Sato 5445602 Name: amount, dtype: int64
さらに staff と year で集計することもできます.
df.groupby(['staff', 'year'])['amount'].sum()
staff year Eto 2018 1766720 2019 3143316 2020 1969189 Gondo 2018 1451968 2019 4082637 2020 1513102 Goto 2018 989451 2019 2475689 2020 4039664 Hyodo 2018 1916070 2019 5695402 2020 4691795 Kato 2018 1822396 2019 10258507 2020 8764169 Kito 2018 1781146 2019 7116814 2020 7352418 Muto 2018 3086176 2019 9585036 2020 5578797 Saito 2018 1523216 2019 5982719 2020 4190364 Sato 2018 4994511 2019 12465556 2020 5445602 Name: amount, dtype: int64
しかしながら,今のような縦に長い表は,2次元の表形式にするとより見やすくなります.2次元の表は次のピボットテーブルの機能を使うと作成できます.
ピボットテーブルを使うと2次元の表にグループ化して集計することができます.なお,このピボットテーブルはExcelでも作成可能です(Excelでもぜひ試してください).
まず,年・月・四半期などの列を追加した状態のデータフレームを確認します.
df
ピボットテーブルの作成には df.pivot_table()
関数を利用します.例えば受注額 (amount) の総計を集計するときに,行 (index) に staff を,列 (columns) に year を設定したいときには次のように記述します.なお使用したい集約関数 (aggregate function) を aggfunc='sum'
のように指定します.
tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='sum')
tbl
総計ではなく件数を集計したい場合には aggfunc='count'
を指定します.
tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='count')
tbl
同じように平均値を求めたい場合には aggfunc='mean'
を指定します.
tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='mean')
tbl
合計と件数を同時に集計することも可能です.
tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc=['sum', 'count'])
tbl
月ごとに集計することも可能です.
tbl = df.pivot_table('amount', index='staff', columns='month', aggfunc='sum')
tbl
年・月ごとの集計を行ってみます.
tbl = df.pivot_table('amount', index='staff', columns='year_month', aggfunc='sum')
tbl
行と列を入れ替えるには index
と columns
に渡す引数を入れ替えるだけで良いでしょう.
tbl = df.pivot_table('amount', index='year_month', columns='staff', aggfunc='sum')
tbl
count()
で集計した結果について,その割合を求めてみたい.まず,上と同じように年ごとの受注件数を集計します.
再掲
tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='count')
tbl
上の結果について割合を求めたければ,lambda
式を使って次のように記述します.横方向での合計をベースに割合を求めています.
tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='count').apply(lambda x:x/sum(x), axis=1)
tbl
縦方向での合計をベースに割合を求めるには axis=0
とします.
tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='count').apply(lambda x:x/sum(x), axis=0)
tbl
上の結果にある「NaN」は欠損値です.つまり該当するデータが存在しないことを意味です.NaN を 0 に置き換えるには fill_value=0
オプションを指定します.
tbl = df.pivot_table('amount', index='year_month', columns='staff', aggfunc='sum', fill_value=0)
tbl
複数の項目で集計してインデックスを階層化することは難しくありません.例えば branch と staff で集計するには次のように記述します.つまり index=[ ]
の中に複数のカラム名を指定するだけです.
tbl = df.pivot_table('amount', index=['branch', 'staff'], columns='year_month', aggfunc='sum', fill_value=0)
tbl
今度は year と month で集計してみましょう.
tbl = df.pivot_table('amount', index=['year', 'month'], columns='staff', aggfunc='sum', fill_value=0)
tbl
次は行ごと,列ごとに総計の集計を行います.次の通り margins=True
オプションを指定するだけで総計の集計ができるようになります.
tbl = df.pivot_table('amount', index=['year', 'month'], columns='staff', aggfunc='sum', fill_value = 0, margins=True)
tbl
ピボットテーブルの結果が巨大になり見通しが悪いときには,フィルタを使うと良いでしょう.まず,Pandas での行の抽出は次の方法で可能です.例えば「branch」が Kobe の行のみを抽出します.
df[df['branch']=='Kobe']
この方法がわかれば,ピボットテーブルにフィルタをかけることは難しくありません.例えば Kobe に所属する営業担当者についてのみ受注総額を取得してみます.
tbl = df[df['branch']=='Kobe'].pivot_table('amount', index='year', columns='staff', aggfunc='sum', fill_value=0, margins=True)
tbl
ピボットテーブルからグラフを生成することを考えてみましょう.まずはグラフ化したいピボットテーブルを作成します.例えば branch ごとに年次の受注総額を集計し,その結果を tbl
というデータフレームに格納します.
tbl = df.pivot_table('amount', index='year', columns='branch', aggfunc='sum')
tbl
データフレームのカラム名の一覧は次の方法で取得できます.
tbl.columns
Index(['Kobe', 'Osaka'], dtype='object', name='branch')
これを for
によって繰り返すと,branch ごとの集計結果を取得できます.
for col in tbl.columns:
print(col)
print(tbl[col])
print("")
Kobe year 2018 5731355 2019 15684361 2020 11712319 Name: Kobe, dtype: int64 Osaka year 2018 13600299 2019 45121315 2020 31832781 Name: Osaka, dtype: int64
この要領でピボットテーブルの集計結果からグラフを作成することができます(このデータの場合は本来なら折れ線グラフではなく棒グラフにすべきところですが,複数の棒グラフの作成は若干複雑なので折れ線グラフにしています).
fig, ax = plt.subplots(1, 1, figsize=(6, 4))
for col in tbl.columns:
ax.plot(tbl[col], label=col)
ax.set_xlabel('year')
ax.set_ylabel('total amount')
ax.set_ylim(0, 50000000)
ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, loc: "{:,}".format(int(x))))
ax.legend()
fig.subplots_adjust(left=0.2) # 左端が切れてしまうので余白を微調整
plt.savefig('pivot.png', dpi=300, facecolor='white')
plt.show()