Python入門トップページ


目次

  1. Pandas とは
  2. Pandas を利用するための準備
  3. CSV ファイルを読み込む
  4. DataFrame を表示する
  5. 列や行を抽出する
  6. 列名を抽出する
  7. Python リストに変換する
  8. Python リストから DataFrame に変換する
  9. DataFrame から NumPy 配列に変換する
  10. 基本統計量を出力する
  11. CSV ファイルに書き出す
  12. グループ化とピボットテーブル
    1. データの読み込み
    2. グループ化
    3. ピボットテーブル
    4. 演習課題
  13. データフレームの結合(系列名が等しいとき)
  14. データフレームの結合(系列名が異なるとき)
  15. データフレームの連結

Pandas

グループ化とピボットテーブル

目次に戻る

データの読み込み

まずは準備としてモジュールをインポートした後に 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
pandas-pivot-04

上のデータを確認する.branch(支店または営業所)には Kobe と Osaka のデータがありそうです.staff(営業担当者)は Kobe 支店に Eto,Gondo,Muto など,Osaka 支店には Sato,Ito,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
pandas-pivot-06

上の例では,「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
pandas-pivot-10

目次に戻る

階層インデックスを作る

上で求めた様々な集約関数の結果は 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)
pandas-pivot-15

目次に戻る

時系列データの処理

時系列データを処理することを考えたい.具体的には,年ごとや月ごと,四半期ごとなどの受注総額などを集計したい.このような場合には日付のデータから年,月,四半期などの情報を取得する必要がある.まずは年などの情報を取得する方法を確認する.

まず,「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
pandas-pivot-22

「month」列を追加する

df['month'] = df['date'].dt.strftime('%m')
df
pandas-pivot-23

「year_month」列を追加する.

df['year_month'] = df['date'].dt.strftime('%Y-%m')
df
pandas-pivot-24

四半期「quarter」の列を追加する.

df['quarter'] = df['date'].dt.to_period("Q")
df
pandas-pivot-25

例えば,年ごとにスタッフの受注総額を表示したければ次のようにするとよい.

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
pandas-pivot-28

目次に戻る

ピボットテーブルによる集計

ピボットテーブルの作成には df.pivot_table() 関数を利用します.例えば受注額 (amount) の総計を集計するときに,行 (index) に staff を,列 (columns) に year を設定したいときには次のように記述します.なお使用したい集約関数 (aggregate function) を aggfunc='sum' のように指定します.

tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='sum')
tbl
pandas-pivot-29

目次に戻る

集計方法の変更

総計ではなく件数を集計したい場合には aggfunc='count' を指定します.

tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='count')
tbl
pandas-pivot-30

同じように平均値を求めたい場合には aggfunc='mean' を指定します.

tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='mean')
tbl
pandas-pivot-31

合計と件数を同時に集計することも可能です.

tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc=['sum', 'count'])
tbl
pandas-pivot-32

月ごとに集計することも可能です.

tbl = df.pivot_table('amount', index='staff', columns='month', aggfunc='sum')
tbl
pandas-pivot-33

年・月ごとの集計を行ってみます.

tbl = df.pivot_table('amount', index='staff', columns='year_month', aggfunc='sum')
tbl
pandas-pivot-34

行と列を入れ替えるには indexcolumns に渡す引数を入れ替えるだけでよい.

tbl = df.pivot_table('amount', index='year_month', columns='staff', aggfunc='sum')
tbl
pandas-pivot-35

目次に戻る

割合を求める

count() で集計した結果について,その割合を求めてみたい.まず,上と同じように年ごとの受注件数を集計します.

再掲tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='count')
tbl
pandas-pivot-30

上の結果について割合を求めたければ,lambda 式を使って次のように記述します.横方向での合計をベースに割合を求めています.

tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='count').apply(lambda x:x/sum(x), axis=1)
tbl
pandas-pivot-71

縦方向での合計をベースに割合を求めるには axis=0 とします.

tbl = df.pivot_table('amount', index='staff', columns='year', aggfunc='count').apply(lambda x:x/sum(x), axis=0)
tbl
pandas-pivot-72

目次に戻る

NaN を 0 に置き換える

上の結果にある「NaN」は欠損値である.つまり該当するデータが存在しないことを意味です.NaN を 0 に置き換えるには fill_value=0 オプションを指定します.

tbl = df.pivot_table('amount', index='year_month', columns='staff', aggfunc='sum', fill_value=0)
tbl
pandas-pivot-36

目次に戻る

インデックスの階層化

複数の項目で集計してインデックスを階層化することは難しくありません.例えば branch と staff で集計するには次のように記述します.つまり index=[ ] の中に複数のカラム名を指定するだけです.

tbl = df.pivot_table('amount', index=['branch', 'staff'], columns='year_month', aggfunc='sum', fill_value=0)
tbl
pandas-pivot-37

今度は year と month で集計してみましょう.

tbl = df.pivot_table('amount', index=['year', 'month'], columns='staff', aggfunc='sum', fill_value=0)
tbl
pandas-pivot-38

目次に戻る

総計の計算

次は行ごと,列ごとに総計の集計を行います.次の通り margins=True オプションを指定するだけで総計の集計ができるようになります.

tbl = df.pivot_table('amount', index=['year', 'month'], columns='staff', aggfunc='sum', fill_value = 0, margins=True)
tbl
pandas-pivot-39

目次に戻る

フィルタ

ピボットテーブルの結果が巨大になり見通しが悪いときには,フィルタを使うと良いでしょう.まず,Pandas での行の抽出は次の方法で可能です.例えば「branch」が Kobe の行のみを抽出します.

df[df['branch']=='Kobe']
pandas-pivot-40

この方法がわかれば,ピボットテーブルにフィルタをかけることは難しくありません.例えば Kobe に所属する営業担当者についてのみ受注総額を取得してみます.

tbl = df[df['branch']=='Kobe'].pivot_table('amount', index='year', columns='staff', aggfunc='sum', fill_value=0, margins=True)
tbl
pandas-pivot-41

目次に戻る

グラフの作成

ピボットテーブルからグラフを生成することを考えてみましょう.まずはグラフ化したいピボットテーブルを作成します.例えば branch ごとに年次の受注総額を集計し,その結果を tbl というデータフレームに格納します.

tbl = df.pivot_table('amount', index='year', columns='branch', aggfunc='sum')
tbl
pandas-pivot-42

データフレームのカラム名の一覧は次の方法で取得できます.

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()
pandas-pivot-45

目次に戻る

演習課題

  1. 受注総額の合計はいくらか.
  2. 受注件数の合計は何件か.
  3. 大阪営業所 (Osaka branch),神戸営業所 (Kobe branch) のそれぞれの受注総額の合計はいくらか.
  4. 受注総額の最も大きかった営業担当者はだれか.
  5. 次のようなピボットテーブルを作成しなさい.
    pandas-pivot-55
        
  6. 大阪営業所 (Osaka branch) に所属する営業担当者の情報を抽出し,次のようなグラフを作成しなさい.
    pandas-pivot-62
        

目次に戻る