やりたいこと
- 入れ子構造の辞書をアンダーバーで繋いで展開したい。
- Input:
d1 = {'A':{'i':0,'j':1},'B':{'i':2,'j':3}}
のような辞書の要素に辞書が入るようなデータ - Output:
d2 = {'A_i':0, 'A_j':1, 'B_i':2, 'B_j':3}
のように一次元に開かれたデータ
- Input:
関数(Not Smart)
- 入れ子構造の辞書はつまるところ,木構造になる
- イメージとしては木構造を割いて根と末端を直接つなぐ感じの処理になる
- 深さ優先探索っぽく実装するとなると再帰関数が良さそう
ということで書いたのが以下のようなコードになります。
# key 変更
def changekey(dict,k1,k2):
dict[k2] = dict[k1]
del dict[k1]
# すべてのKeyにアンダーバー付きで修飾語をつける
def addkeyheader(dict,key):
ks = list(dict.keys())
for k in ks:
changekey(dict,k,key+'_'+k)
# 本命の関数 深さ優先探索をした後に要素名を結合して返す
def dict_flatten(dict_,depth=2):
newdict = {}
for key in list(dict_.keys()):
# 要素もdictなら再帰的に呼び出す
if isinstance(dict_[key], dict):
if depth > 0:
dic = dict_flatten(dict_[key],depth-1)
addkeyheader(dic,key)
newdict.update(dic)
else:
newdict[key] = dict_[key]
# 要素がdictじゃないならそのまま
else:
newdict[key] = dict_[key]
return newdict
よりスマートな実装
コメントにてよりスマートな実装を教えていただきました。
def _unwrap(dct, prefix):
for key, value in dct.items():
new_key = f'{prefix}_{key}'
if isinstance(value, dict):
yield from _unwrap(value, new_key)
else:
yield new_key[1:], value
def unwrap(dct):
return dict(_unwrap(dct, ''))
d2 = unwrap(d1)
想定していた使い道
- 入れ子構造のdictは以下のように2次元状に配置される(参考)。
- これとは異なり単にデータを単に並べたい場合がある。
Inputを変換すると以下のようになり,
>> pd.DataFrame.from_dict(d1)
A B
i 0 2
j 1 3
Outputを変換すると以下の様になります。
>> pd.DataFrame(d2,index=[""])
A_i A_j B_i B_j
0 1 2 3
これでめでたしと思いきや,pandasの理解が浅かったです。
PandasでMultiIndexを用いてナチュラルに分解が可能である
わざわざ分解するまでもなく実用上はMultiIndexの機能で編集可能です。
https://qiita.com/Morinikiz/items/40faa91e7a83807c0552
- dfをstack()またはunstack()することでまとめられる。
- stackで列側を高レベルに,unstackで行側を高レベルに置ける
- ただ,こちらの解説と挙動が逆なのはなぜ?
>>> df = pd.DataFrame(d1)
>>> df
A B
i 0 2
j 1 3
>>> df.unstack()
A i 0
j 1
B i 2
j 3
dtype: int64
>>> df.stack()
i A 0
B 2
j A 1
B 3
dtype: int64
Data Frameに戻すには最後にto_frame()を使えばいいです。
>>> df.unstack().to_frame("name")
name
A i 0
j 1
B i 2
j 3