要約
区切り文字を抜いて全紐づけパターンを出力する処理を書いた!動いた!
とは言え、辞書型変換を雑スクラッチしてしまった感が強く残った。
その後にもっと良いやり方(諸説あり)を知ったので修正。
学びの備忘録として残す。
一応公開しています。
https://github.com/gmoriki/rm_delimiter
実装の背景
Scivalから提供されるExcelデータに区切り文字が使用されていることが多い。
例えば一つの論文に紐づく著者3名の固有IDが、以下のような形式の値で格納されている。
例:"10000 | 20000 | 300000"
この状態で固有IDと別の情報をマージすることは困難であり、部署内の利用者はうまいこと区切り文字を処理してきた。今後同じような事態(車輪の再生産)を避けるために、今回は区切り文字を排除した上でExcelデータに再出力する、比較的汎用的なプログラムをPythonで書こう、と思い至った。
読み込みテーブル
key
,target
という列名を含むExcelデータを用意する。
key | target |
---|---|
ronbun1 | 10000 | 20000 | 300000 |
ronbun2 | 6543 | 53 | 45323 |
出力テーブル
全パターン、冗長な形で出力したい。
key | target |
---|---|
ronbun1 | 10000 |
ronbun1 | 20000 |
ronbun1 | 300000 |
ronbun2 | 6543 |
ronbun2 | 53 |
ronbun2 | 45323 |
最初に書いたコード
最初にとりあえず書いてみたもの
# データの読み込み
df = pd.read_excel(excelpath,dtype=str,usecols=['key','target'])
datalist = []
# dfのレコードに対する繰り返し処理:区切り文字を取ってdatalistに格納する
for record in df.to_dict(orient='records'):
# 区切り文字の処理
target_list = [_.strip() for _ in record['target'].split(delimiter)]
# DataFarmeの元データを作成
for target in target_list:
datalist.append({'key':record['key'],'target':target})
# 出力
pd.DataFrame(datalist).to_excel(output_filename,index=False)
1. 読み込んだDataFrameを行単位で辞書データに変換
2. 区切り文字を無くしたリストから要素を取り出し、辞書としてdatalistに追加
3. できあがった辞書をDataFrameに突っ込んで出力
動く!けどなんだか無駄が多い(特に# DataFarmeの元データを作成
)。
冗長な形式の縦持ちテーブルを作るには辞書を逐次appendするしかないと考えていた。
当初は辞書の値をリスト型にした出力を考えていたが、
想定と違うDataFrameを返したので非採用。
d = {'key1': [1,2,3], 'key2': [4,5,6,7], 'key3': [8,9]}
pd.DataFrame([d])
key1 key2 key3
0 [1, 2, 3] [4, 5, 6, 7] [8, 9]
うーむ知識の抜け感がぬぐえない。
修正の軌跡
pd.DataFrame.from_dict
辞書の操作を調べてたところ、いくつか参考になる記事を見つけた。
d = {'key1': [1,2,3], 'key2': [4,5,6,7], 'key3': [8,9]}
df = pd.DataFrame.from_dict(d, orient='index').T
# もしくは
df = pd.DataFrame(d.values(), index=d.keys()).T
print(df)
key1 key2 key3
0 1.0 4.0 8.0
1 2.0 5.0 9.0
2 3.0 6.0 NaN
3 NaN 7.0 NaN
ちゃんとfrom_dict
を使えば要素がテーブルの値に乗ってくれることを知る。
df.unstack()
from_dictから作ったDataFrameをunstack
すれば、欲しい形が手に入りそう。
unstack
はcolumnsをmulit indexに持たせるメソッド。
https://note.nkmk.me/python-pandas-stack-unstack-pivot/
print(df.unstack())
key1 0 1.0
1 2.0
2 3.0
3 NaN
key2 0 4.0
1 5.0
2 6.0
3 7.0
key3 0 8.0
1 9.0
2 NaN
3 NaN
dtype: float64
あとは不要なindex,columnを落とせばOK。
# columnsをmulti indexに持たせる
# リストの長さは可変なのでNanを落としている
df_unstack = df.unstack() \
.dropna() \
.reset_index()
# 列名の定義・整理
df_unstack.drop(df_unstack = df_.columns[1],inplace=True)
df_unstack.columns=['key','target']
print(df_unstack)
key target
0 key1 1.0
1 key1 2.0
2 key1 3.0
3 key2 4.0
4 key2 5.0
5 key2 6.0
6 key2 7.0
7 key3 8.0
8 key3 9.0
欲しい形までたどり着いた。
実際に実装してみる。ついでに可能な限りapplyで対応。
修正後のコード
# update対応の箱
tmp_dict = {}
# 区切り文字を排除したリストを辞書に格納する関数
def format_dict_data(row):
target_list = [_.strip() for _ in row['target'].split(delimiter)]
tmp_dict.update({row['key']:target_list})
df.apply(format_dict_data,axis=1) # dfにformat_dict_data適用
# 得られた辞書をDataFrameとして再定義
df_from_dict = pd.DataFrame.from_dict(tmp_dict, orient='index').T
# DataFrameを取り回す関数
def preprocess_df(df_):
df_ = df_.unstack() \
.dropna() \
.reset_index()
df_.drop(columns = df_.columns[1],inplace=True)
df_.columns=['key','target']
return df_
df_key_target = df_from_dict.pipe(preprocess_df) # df_from_dictにpreprocess_df適用
1. 区切り文字の排除し辞書データを作成(formmat_dict_data)
2. 得られた辞書データをDataFrameとして再定義
3. DataFrameに対する整形処理(preprocess_df)
結論
思ったより長いコードになった!が、似た情報を何度もappendするよりかは幾分マシだと思う。
改良されたのか諸説あるが道草食って納得感のある実装ができたので及第点。
参考文献
https://qiita.com/ShoheiKojima/items/30ee0925472b7b3e5d5c
https://note.nkmk.me/python-pandas-stack-unstack-pivot/