スタートページJavaScript他言語Python 目次>    ←配列の計算

DataFrame のSQLライクな操作

事務処理の分野や大量データからの検索加工の分野では、RDB(関係データベース)が広く用いられており、RDBの検索加工にはSQL言語があります。DataFrameはRDBと似た構造をもっており、Pandas はSQLの SELECT文に類似した機能があります。
ここでは、RDB-SQLの観点から、DataFrame の操作をまとめました。


DataFrameの特徴とSQL

DataFrameの構造とRDB(関係データベース)
  2次元配列(行列)があり、各列に列名があります。
  オプションとして、各行に行名を付けることができます。

           c0    c1    c2    c3 ← columns 列名 ベクトル
     r0     A     1     2     3 ┐
     r1     B    11    12    13 ├ values 要素 行列
     r2     C    21    22    23 ┘
   ↑
  index 行名 ベクトル

  df = pd.DataFrame([['A', 1, 2, 3], # list形式で1行づつ指定
                     ['B',11,12,13],
                     ['C',21,22,23]],
                    columns = ['c0','c1','c2','c3'], # 列名 省略時は 0, 1, 2 となる
                    index = ['r0','r1','r2'])        # 行名 省略時は 0, 1, 2 となる

DataFrameの加工の特徴とSQL
  RDBと同じように、データの追加や削除は行単位で行うのが便利ですが、
  集計やソートなどの加工は、特定の列を縦ベクトルとして扱うのが通常です。
  しかも、列指定では、列番号ではなく、列名で指定するのが便利です。
  個々の要素だけを対象にした加工を行うことはあまりありません。
  これらの理由により、行名を指定することはあまりありません。

このように、DataFrameの加工はSQLでの加工と似ています。

サンプルの実行

Google ColaboratoryでPythonの動作環境を設定して、次の青の部分を「コード」部分にコピーアンドペースト(ペーストは Cntl+V)して実行してください。

import numpy as np import pandas as pd # ========================== 入力データ 売上表 = pd.DataFrame([ [1000, '得意先1', '商品A', 1, 40], [1001, '得意先1', '商品B', 2, 40], [1002, '得意先1', '商品C', 1, 10], [1003, '得意先2', '商品B', 3, 60], [1004, '得意先2', '商品B', 1, 10], [1005, '得意先3', '商品B', 2, 40], [1005, '得意先3', '商品B', 3, 60], [1007, '得意先1', '商品A', 3, 120], [1008, '得意先1', '商品B', 2, 40], [1009, '得意先2', '商品C', 1, 10], [1010, '得意先2', '商品C', 3, 30], [1011, '得意先2', '商品C', 2, 20], [1012, '得意先3', '商品C', 2, 20], [1013, '得意先3', '商品C', 1, 10], [1014, '得意先1', '商品A', 3, 120], [1015, '得意先1', '商品A', 1, 50], [1016, '得意先2', '商品B', 1, 20], [1017, '得意先3', '商品A', 2, 100], [1018, '得意先3', '商品B', 1, 20], [1019, '得意先1', '商品B', 2, 40]], columns = ['番号','得意先', '商品', '数量', '金額']) 得意先表 = pd.DataFrame([ ['得意先1', '東京'], ['得意先2', '東京'], ['得意先3', '大阪']], columns = ['得意先', '府県']) # ========================== 加工処理例 表A = 売上表[['得意先', '金額']] print('表A\n', 表A.head(3).append(表A.tail(3))) # 射影:列の取出し 表B = 売上表[(売上表.得意先 == '得意先1') & (売上表.金額 >= 50)] print('表B\n', 表B) # 選択:行の取出し 表C = 売上表[売上表.得意先 == '得意先1'].loc[:, ['商品', '金額']] print('表C\n', 表C.head(3).append(表C.tail(3))) # 射影と選択の組合せ 表D = 売上表[['番号', '金額']].query("金額 > 100") print('表D\n', 表D) # query による射影と選択 x = 100 条件 = "金額 > @x" 表E = 売上表.query(条件) print('表E\n', 表E) # 条件を外だしquery による射影と選択 表F = pd.merge(売上表, 得意先表, on='得意先') print('表F\n', 表F.head(3).append(表F.tail(3))) #  pd.merge による結合 表G = 売上表.sort_values('金額', ascending=False) print('表G\n', 表G.head(3).append(表G.tail(3))) # ソート ORDER BY 表H = 売上表[['得意先','商品','数量','金額']].groupby(['得意先', '商品'], as_index=False).sum() print('表H\n', 表H) # グループ別集計 表I = 売上表.groupby(['得意先', '商品'], as_index=False).agg({'数量': np.sum, '金額': np.mean}) print('表I\n', 表I) # グループ別集計 集計表 = 売上表.groupby(['得意先'], as_index=False).agg({'数量': np.sum, '金額': np.sum}) 集計表['単価'] = 集計表['金額'] / 集計表['数量'] 表J = 集計表 print('表J\n', 表J) # 集計結果での新列の作成(AS) ワーク表 = 売上表.groupby(['得意先'],as_index=False).agg({'金額': np.sum}) 表K = ワーク表.query('金額 >= 200') print('表K\n', 表K) # 集計した結果を条件に抽出する (HAVING)

実行の解説

上の「売上表」と「得意作表」は、DataFrame 形式ではありますが、RDBともほぼ同じ構造になっています。
ここでは、両表の加工を、Pandas とSQLの対比を示します。以下では「表A」や「表B」を赤字で示し、対応するSQLを青字で示しました。

例えば「売上表」の「得意先」属性を示すのに、対象表において「得意先」ない場合、SQLでは 売上表.得意先 を単に「得意先」と指定できますが、DataFrame は配列として、売上表['得意先'] あるいは 売上表.得意先 と明示する必要があります。

SQLと異なり、結合するキーの列名は2つの表で同じ名称でなければなりません。

RDBの基本操作

RDBを対象にした基本操作には、次の3操作があります。
  射影:列の取出し
  選択:行の取出し
  結合:による二つの表を特定のキーで結合する

射影:列の取出し

指定した列名だけを取り出します。

列名列挙による指定
  2つの形式があります。
  df[[列名リスト]]
  df.loc[:, [列名リスト]]   #  : は「すべての行」を指す

 # ■表A
  売上表[['得意先', '金額']]      # SQL: SELECT 得意先,金額 FROM 売上表
  売上表.loc[:, ['得意先', '金額']] 
        #      得意先    金額
        # 0    得意先1    40
        # 1    得意先1    40
        # 2    得意先1    10
        # 3    得意先2    60
        # 4    得意先2    10
          :
範囲指定による射影
  df.loc[:, [列名リスト]] の列名リストは、「先頭: 末尾」 のような記述ができます。

  売上表.loc[:, '得意先': '数量']      # SQL: 機能なし
      #      得意先      商品    数量  # 得意先と数量の間にある商品も対象になる
      # 0    得意先1    商品A     1
      # 1    得意先1    商品B     2
      # 2    得意先1    商品C     1
      # 3    得意先2    商品B     3
    # 4    得意先2    商品B     1
        ;

列番号による範囲指定での射影
  列名ではなく列番号で与えるには、loc を iloc にします。ただし kmax に1を加える必要があります。

  売上表.iloc[:, 1:4]
      #      得意先      商品    数量
      # 0    得意先1    商品A     1
      # 1    得意先1    商品B     2
      # 2    得意先1    商品C     1
      # 3    得意先2    商品B     3
      # 4    得意先2    商品B     1
        ;

選択:行の取出し

行番号指定による選択
  一般に df は大きな行数になるので、確認などには部分的に出力するのが適切です。
    最初のn行:、 df.head(n) 省略時:n=5
    最後のn行:、 df.tail(n)
    スライスを用いて、次の表示もできます。
    df[imin: imax; di]

  売上高.head(5)             # SQL: SELECT * FROM 売上表 LIMIT 5;
     #     番号  得意先    商品  数量 金額
      #  0  1000  得意先1  商品A    1     40
      #  1  1001  得意先1  商品B    2     40
      #  2  1002  得意先1  商品C    1     10
      #  3  1003  得意先2  商品B    3     60
      #  4  1004  得意先2  商品B    1     10

  売上表[0: 20: 5]           # SQL: 機能なし 
      #     番号  得意先    商品  数量 金額
      #  0  1000  得意先1  商品A    1     40
      #  5  1005  得意先3  商品B    2     40
      # 10  1010  得意先2  商品C    3     30
      # 15  1015  得意先1  商品A    1     50

条件合致による選択
  df[列名の条件]

  売上表[売上表.得意先 == '得意先1']
           # SQL:  SELECT 得意先,数量 FROM 売上表 WHERE 得意先 = '得意先1';
      #     番号  得意先    商品  数量 金額
      #  0  1000  得意先1  商品A    1     40
      #  1  1001  得意先1  商品B    2     40
      #  2  1002  得意先1  商品C    1     10
      #  7  1007  得意先1  商品A    3    120
      #  8  1008  得意先1  商品B    2     40
      # 14  1014  得意先1  商品A    3    120
      # 15  1015  得意先1  商品A    1     50
      # 19  1019  得意先1  商品B    2     40

複合条件での選択
  次の2点が NumPy と異なります。
  ・各条件は ( ) でくくります。
  ・and or not ではなく、&、|、~ を使います。

  # ■表B
  売上表[(売上表.得意先 == '得意先1') & (売上表.金額 >= 50)]
                     # SQL:  SELECT * FROM 売上表 WHERE 得意先 = '得意先1' AND 金額 >= 50;
        #     番号  得意先    商品  数量 金額
      #  7  1007  得意先1  商品A    3    120
      # 14  1014  得意先1  商品A    3    120
      # 15  1015  得意先1  商品A    1     50

射影と選択の組合せ

  まず選択操作をして 売上表[売上表.得意先 == '得意先1']
  次に射影操作をする 売上表.loc[:, ['得意先', '金額']] 
  というように2ステップにしてもよいのですが、次のように1ステップにすることもできます。

  # ■表C
  売上表[売上表.得意先 == '得意先1'].loc[:, ['商品', '金額']]
                    # SQL:  SELECT 商品, 金額 FROM 売上表 WHERE 得意先 = '得意先1';
      #       商品    金額
      #  0    商品A    40
      #  1    商品B    40
      #  2    商品C    10
      #  7    商品A   120
         ;

query による射影と選択

  SQL: SELECT * FROM df WHERE ~ の ~ の部分を文字列として df.query("~") と記述できます。
  選択操作の記述が簡単にできます。通常の場合の選択操作には query を用いるのが適切です。

基本形式
  条件式は、全体を文字列として " " で囲みます。

  売上表.query("金額 > 100")     # SQL: SELECT * FROM 売上表 WHERE 金額 > 100
      #     番号  得意先    商品   数量   金額
      #  7  1007  得意先1  商品A    3    120
      # 14  1014  得意先1  商品A    3    120

射影と組み合わせることができます。
  条件に用いた列の列名は射影のリストに加える必要があります。
  (射影した結果のdf に対して query を行うという考え方です。)

  # ■表D
  売上表[['番号', '金額']].query("金額 > 100")
                        # SQL: SELECT 番号, 金額 FROM 売上表 WHERE 金額 > 100
      #     番号   金額
      #  7  1007    120
      # 14  1014    120

次のように、条件を外だしできます。
  条件 = "金額 > 100"
  売上表.query(条件)

複雑な条件も与えられます。 演算子: and or & | not、  ( ) は任意
  条件 = "(得意先=='得意先1') & (金額/数量 > 30)" 
  条件 = "35 < 金額 < 45"
  条件 = "得意先 in ['得意先1', '得意先3']"
  条件 = "index % 5 == 0"                 # 行番号 0, 5, 10, … 

  条件文字列の中で変数を使用するには変数名の前に@をつけます。
 # ■表E
  x = 100
  条件 = "金額 > @x"                      # 条件 = "金額 > 100" と同じです。

結合

  結合とは、売上表の得意先と得意先表の得意先で2つの表を結合して一つの表にするような操作です。
  ここでは pd.merge how='inner' の内部結合を対象にします。
  一般に結合とは、このタイプを指します。他のタイプを使う機会は稀ですので省略します。

  SQLと異なり、結合するキーの列名は2つの表で同じ名称でなければなりません。
  ここでは、売上表にも得意先表にも列名「得意先」があり、これが結合キーになります。

  売上表では、「得意先1」は複数行にありますが、得意先表には「得意先1」は一つあるだけです。
  このとき、得意先表の得意先を主キーまたはユニークキーといい、売上表の得意先を外部キーといいます。
  売上表の各行の外部キーで得意先表の主キーを探します。
  一致したら得意先表にある他の列名の要素を売上表のその行に加えます。
  一致するものがなかったら、売上表にその行がなかったことにします。
  逆に、得意先表の主キーのうち、外部キーと一致するものがないときは、得意先表にその行がなかったことにします。

pd.merge による結合

  一般形 pd.merge(A表, B表, on='結合キー', how='inner')   # how は省略できます。
  SQL では結合キーがA表とB表で異なる列名でもよいのですが、Pandas では同名の必要があります。
  通常は、外部キーをもつほうをA表、主キーをもつほうをB表にします。

  # ■表F
  pd.merge(売上表, 得意先表, on='得意先')
                        # SQL: SELECT * FROM 売上表, 得意先表 WHERE 売上表.得意先 = 得意先表.得意先;
      # 	  番号  得意先    商品   数量    金額    府県
      #  0  1000	得意先1  商品A    1      40    東京
      #  1  1001	得意先1  商品B    2      40    東京
      #  2  1002	得意先1  商品C    1      10    東京
      #     :
      # 17  1013	得意先3  商品C    1      10    大阪
      # 17  1009	得意先3  商品A    2     100    大阪
      # 18  1009	得意先3  商品B    1      20    大阪

 A表とB表を逆にしたとき、列の順序は異なるが、内容は同じになります。

  pd.merge(得意先表, 売上表, on='得意先')
      #     得意先    府県  番号  商品   数量  金額
      #  0  得意先1  東京  1001  商品A    1    40
      #  1  得意先1  東京  1001  商品B    2    40
      #  2  得意先1  東京  1001  商品C    1    10
      #     :
      # 17  得意先3  大阪  1006  商品C    1    10
      # 18  得意先3  大阪  1009  商品A    2   100
      # 19  得意先3  大阪  1009  商品B    1    20

 ここで、得意先表に得意先1がなく、売上表にない得意先4があるとします。
 結合キーが一方の表だけにしかないときは、その行がなかったことになります。

  得意先表 = pd.DataFrame([
                    ['得意先4', '東京'],
                    ['得意先2', '東京'],
                    ['得意先3', '大阪']],
          columns = ['得意先',   '府県'])

  pd.merge(売上表, 得意先表, on='得意先', how='inner')
      # 	  得意先    商品   数量  金額    府県
      #  0  得意先2  商品B    3    60    東京
      #  1  得意先2  商品B    1    10    東京
      #  2  得意先2  商品C    1    10    東京
      #      :
      #  9  得意先3  商品C    1    10    大阪
      # 10  得意先3  商品A    2   100    大阪
      # 11  得意先3  商品B    1    20    大阪

 このように、売上表で得意先1をもつ行は無視されたので、行数が12になりました。
 行番号が崩れることに留意してください。

付帯機能

SQLの ORDER BY や GROOP BY などに相当する機能があります。

ソート ORDER BY sort_values

 一般形
  df.sort_values(キー列名, ascending=True)
  df.sort_values([キー1, キー2], ascending=[True. False])

  # ■表G
  売上表.sort_values('金額', ascending=False)  # 金額の大きい順
                          # SQL: SELECT * FROM 売上表 ORDER BY 金額 DESC;
      #     番号  得意先    商品   数量   金額
      # 14  1014  得意先1  商品A    3    120
      #  7  1007	得意先1  商品A    3    120
      # 17  1017	得意先3  商品A    2    100
      #  3  1003	得意先2  商品B    3     60
      #  :
      #  4  1004	得意先2  商品B    1     10
      #  2  1002	得意先1  商品C    1     10

  行方向にソートしたい場合は引数に axis=1 を指定しますが、通常は稀でしょう。
グループ別集計 GroupByとAggregation
  「得意先別・商品別売上集計表」を作るような操作です。
      グループ分け groupby
      集計     agg, sum, mean などを用いたいくつかの形式があります。

形式1 一般形 df.groupby([キー列名]).集計方法

  売上表.groupby('得意先').sum()               # SQL: 全列指定の機能なし
      #          番号  数量   金額
      # 得意先			
      # 得意先1  8036    15    460
      # 得意先2  6030    11    150
      # 得意先3  6036    11    250

  売上表.groupby(['得意先', '商品']).sum()      # SQL: 全列指定の機能なし
      #                  番号  数量  金額
      # 得意先	商品			
      # 得意先1  商品A  4020    8    330
      #           商品B  3015    6    120
      #           商品C  1001    1     10
      # 得意先2  商品B  3013    5     90
      #           商品C  3017    6     60
      # 得意先3  商品A  1009    2    100
      #           商品B  3015    6    120
      #           商品C  2012    3     30

  キー列名(グループラベル)がindex (行名)になってしまいました。
  その後の操作では、これらを列名にしたほうが便利です。→形式2

  集計列を指定していないので、番号まで集計してしまった(本来は削除列)
  表示する列名を指定する(射影)ことができます。→形式3

  集計がすべて合計(sum)になっています。
  個々の列に集計方法を変える必要があるでしょう。→形式4

形式2(キー列名を列名にする場合)as_index オプション

  売上表.groupby(['得意先', '商品'], as_index=False).sum()
      #    得意先  商品  番号 数量   金額
      # 0  得意先1  商品A  4020    8    330
      # 1  得意先1  商品B  3015    6    120
      # 2  得意先1  商品C  1001    1     10
      # 3  得意先2  商品B  3013    5     90
      # 4  得意先2  商品C  3017    6     60
      # 5  得意先3  商品A  1009    2    100
      # 6  得意先3  商品B  3015    6    120
      # 7  得意先3  商品C  2012    3     30

形式3(番号を削除、集計がすべて同一)

  射影での df[[列名リスト]] と組み合わせる

  # ■表H
  import numpy as np
  売上表[['得意先','商品','数量','金額']].groupby(['得意先', '商品'], as_index=False).sum()
               # SQL: SELECT 得意先, 商品, sum(数量), sum(金額) FROM 売上表 GROUP BY 得意先, 商品;
      #    得意先  商品  数量   金額
      # 0  得意先1  商品A     8    330
      # 1  得意先1  商品B     6    120
      # 2  得意先1  商品C     1     10
      # 3  得意先2  商品B     5     90
      # 4  得意先2  商品C     6     60
      # 5  得意先3  商品A     2    100
      # 6  得意先3  商品B     6    120
      # 7  得意先3  商品C     3     30

形式4(集計方法が列により異なる)

  一般形 df.groupby(キー列名).agg({集計列名:集計方法})
                 # SQL: SELECT 得意先, 商品, sum(数量), sum(金額) FROM 売上表 GROUP BY 得意先, 商品;

  売上表.groupby(['得意先', '商品'], as_index=False).agg({'数量': np.sum, '金額': np.sum})
                 # SQL: SELECT 得意先, 商品, sum(数量), sum(金額) FROM 売上表 GROUP BY 得意先, 商品;
      # 結果は形式3と同じ

  集計方法は NumPy の np.sum を指定し、() を付けない。
  NumPy を使うので import numpy as np が必要。

(意味はないが)金額を合計()ではなく平均()にしてみます。

  # ■表I
  売上表.groupby(['得意先', '商品'], as_index=False).agg({'数量': np.sum, '金額': np.mean})
                 # SQL: SELECT 得意先, 商品, sum(数量), mean(金額) FROM 売上表 GROUP BY 得意先, 商品;
      #    得意先    商品    数量   金額
      # 0  得意先1  商品A     8    82.5
      # 1  得意先1  商品B     6    40.0
      #      :

2ステップで実現する機能

SQLでは1ステップで記述できるのに Pandas ではその機能がない(私が知らない、複雑になりそうだ)ものについて掲げます。2ステップにすれば簡単ですので、あまり気にかけていません。

集計操作での列名変更(AS)

  得意先別合計金額を算出し、その列名を金額ではなく金額合計としたい。
  SQL: SELECT 得意先, SUM(金額) AS 金額合計 FROM 売上表 GROUP BY 得意先;

  ワーク表 = 売上表.groupby(['得意先'],as_index=False).agg({'金額': np.sum}) # 集計だけ
  集計表 = ワーク表.rename(columns={'金額':'金額合計'})                      # 列名変更
      #    得意先   金額合計
      # 0  得意先1      460
      # 1  得意先2      150
      # 2  得意先3      250

集計結果での新列の作成(AS)

  得意先別に数量と金額を合計し、得意先別単価=金額合計/数量合計を新列に加えたい。
  SQL: SELECT 得意先, SUM(金額), SUM(数量), SUM(金額)/SUM(数量) AS 単価 FROM 売上表 GROUP BY 得意先;

  # ■表J
  集計表 = 売上表.groupby(['得意先'], as_index=False).agg({'数量': np.sum, '金額': np.sum})
  集計表['単価'] = 集計表['金額'] / 集計表['数量']
      #    得意先    数量   金額    単価
      # 0  得意先1    15    460    30.7
      # 1  得意先2    11    150    13.6
      # 2  得意先3    11    250    22.7

集計した結果を条件に抽出する (HAVING)

  得意先金額合計が200以上の表を作成したい
  SQLでは HAVING 句で記述できる。
  SQL: SELECT 得意先, SUM(金額) FROM 売上表 GROUP BY 得意先 HAVING  SUM(金額) >= 200;

  # ■表K
  ワーク表 = 売上表.groupby(['得意先'],as_index=False).agg({'金額': np.sum})
  集計表 = ワーク表.query('金額 >= 200')
      #    得意先       金額
      # 0  得意先1      460
      # 2  得意先3      250