たまたま自分のまわりだけかもしれませんが、次項に示した例のように、1対Nのデータを結合してネストオブジェクトのJSON Lines出力をざっくりで良い精度でやってしまいたいという例にしばしば遭遇します。
ブームの時に手グセになっていたり、その案件でガチで必要ならそれなりの関数にしてしまうので良いのです。
ただ、大抵はそこまでいかないのと、忘れたころに、あれ?そんなに難しくないんだけど、どんな感じだったけ、めんどくさいなぁとなって、自分の書き捨てのコードなど頼ることになります。
おそらく程度としては中途半端なので、世のイディオムやTIPSにはあがってこないのと、ググラビリティが低そうなので、やりたいことのイメージ残しを含めて、この記事でメモってみます。
やりたいこと概要
こんなデータ(LEFT)と
名前 | 所属 | |
---|---|---|
0 | 山田 | 営業1 |
1 | 佐藤 | 営業2 |
2 | 田中 | 営業2 |
こんなデータ(RIGHT)があって、
名前 | 月 | 値 | |
---|---|---|---|
0 | 山田 | 1 | 100 |
1 | 山田 | 4 | 60 |
2 | 山田 | 2 | 33 |
3 | 佐藤 | 6 | 9 |
4 | 田中 | 4 | 209 |
こんなデータ(出力)を
名前 | 所属 | 関連データ | |
---|---|---|---|
0 | 山田 | 営業1 | [{'名前': '山田', '月': 1, '値': 100}, {'名前': '山田', '月': 2, '値': 33}, {'名前': '山田', '月': 4, '値': 60}] |
1 | 佐藤 | 営業2 | [{'名前': '佐藤', '月': 6, '値': 9}] |
2 | 田中 | 営業2 | [{'名前': '田中', '月': 4, '値': 209}] |
出力したい。
なお、リストにしている部分は、「月」順にソートしておきたい。
Pythonので例
import pandas as pd import io debug = False left = pd.read_csv(io.StringIO( """名前,所属 山田,営業1 佐藤,営業2 田中,営業2""" )) if debug: print(left.to_markdown(),'\n\n') right = pd.read_csv(io.StringIO( """名前,月,値 山田,1,100 山田,4,60 山田,2,33 佐藤,6,9 田中,4,209""" )) if debug: print(right.to_markdown(),'\n\n') keycols = ['名前', '月'] on = ['名前'] grpcols = on sortcol = '月' dictcol = '関連データ' _renamecols = {i: j for i, j in zip(range(len(keycols)), keycols)} 関連データカラム名称変更 = {len(keycols): dictcol} _func = lambda s,keycols: pd.Series([s[k] for k in keycols] + [ {k:v for k,v in s.items()}]) rightdf = right.apply(_func, axis=1, keycols=keycols).rename(columns=_renamecols).rename(columns=関連データカラム名称変更) rightdf_gr = rightdf.groupby(grpcols)[dictcol] _listsorted = lambda s: sorted(list(s), key=lambda el: el[sortcol]) 出力 = pd.merge(left,rightdf_gr.apply(_listsorted).reset_index(),on=on) if debug: print(出力.to_markdown(),'\n\n') print(出力.to_json(orient='records',lines=True,force_ascii=False))
自分の中でのポイント
- _funcのあたりで、全てのカラムを取り込んだdictを生成する。ここでは元のDataFrameをさわりたくなかったので、rightdfに代入している。
- 一度rightdfのようなDataFrameを作成してから、それをgroupbyする。*1
- groupbyしたのち、dictcolで指定のカラムをグループ単位に配列にする。
補足
- そもそも pandasを使用。今回の用途であれば、collectionsあたりを使いこなした方がスマートかもしれない。
- 利用 pandas のバージョンは 1.0.1。この記事のやりたいことのためには本来必須ではないが、pandasバージョンアップ記念に、ver 1で導入された(らしい) to_markdown() を使ってみている。
- 見てのとおりpandasが入っていれば動作するレベルだが、to_markdown()で、tabulateが古いと怒られるので、pipでアップデートが必要かもしれない。
*1:groupbyのグループ化に「'名前','月'」を使うつもりだったが、ここでは「名前」だけにしたので、事例としてはkeycolsを定義した意味が薄れているが書き直すのも手間なのでこのままとしている。