我有一个带有多索引的数据框。
01.01 02.01 03.01 04.01
bar total1 40 52 18 11
total2 36 85 5 92
baz total1 23 39 45 70
total2 50 49 51 65
foo total1 23 97 17 97
total2 64 56 94 45
qux total1 13 73 38 4
total2 80 8 61 50
df.index.values
结果:
array([('bar', 'total1'), ('bar', 'total2'), ('baz', 'total1'),
('baz', 'total2'), ('foo', 'total1'), ('foo', 'total2'),
('qux', 'total1'), ('qux', 'total2')], dtype=object)
我最终希望将数据帧转换为字典字典,以便第一个字典键是
['bar','baz', 'foo','qux']
之一,值是日期,内部字典由“total1”和“totals2”作为键和值组成是 df 的整数。
另一种解释是,例如,如果 dict1
是字典,那么调用 dict1['bar']
将导致输出:
{'bar':{'01.01':{'total1':40,'total2':36},'02.01':{'total1':52,'total2':85},'03.01':{'total1':18,'total2':5},'04.01':{'total1':11,'total2':92} } }
为了实现这一目标,我需要如何改变以及改变什么?这是索引问题吗?
将整个数据帧转换为字典尝试:
df.groupby(level=0).apply(lambda df: df.xs(df.name).to_dict()).to_dict()
{'bar': {'01.01': {'total1': 40, 'total2': 36},
'02.01': {'total1': 52, 'total2': 85},
'03.01': {'total1': 18, 'total2': 5},
'04.01': {'total1': 11, 'total2': 92}},
'baz': {'01.01': {'total1': 23, 'total2': 50},
'02.01': {'total1': 39, 'total2': 49},
'03.01': {'total1': 45, 'total2': 51},
'04.01': {'total1': 70, 'total2': 65}},
'foo': {'01.01': {'total1': 23, 'total2': 64},
'02.01': {'total1': 97, 'total2': 56},
'03.01': {'total1': 17, 'total2': 94},
'04.01': {'total1': 97, 'total2': 45}},
'qux': {'01.01': {'total1': 13, 'total2': 80},
'02.01': {'total1': 73, 'total2': 8},
'03.01': {'total1': 38, 'total2': 61},
'04.01': {'total1': 4, 'total2': 50}}}
要转换某一特定列,请在将其转换为字典之前进行选择,即
df.groupby(level=0).apply(lambda df: df.xs(df.name)[colname].to_dict()).to_dict()
piRSquared 的答案很好,但如果组很多,则速度非常慢,因为
groupby.apply
必须多次调用 Python 函数。1
如果有很多组,将数据帧转换为嵌套字典的更快方法是使用
itertuples
简单地循环数据帧并动态构建字典。因为我们需要一个嵌套字典,所以 dict.setdefault()
或 collections.defaultdict
会很有帮助。
dict2 = {}
for (idx1, idx2), *vals in df.itertuples():
for col, val in zip(df.columns, vals):
dict2.setdefault(idx1, {}).setdefault(col, {})[idx2] = val
dict.setdefault()
这里帮助我们插入一个带有空字典的键作为值(如果它不存在)。它产生与以下更详细的代码相同的结果。
dict2 = {}
for (idx1, idx2), *vals in df.itertuples():
for col, val in zip(df.columns, vals):
if idx1 in dict2:
if col in dict2[idx1]:
dict2[idx1][col][idx2] = val
else:
dict2[idx1][col] = {idx2: val}
else:
dict2[idx1] = {col: {idx2: val}}
1 支持上述主张的基准(在 Python 3.11.5、pandas 2.2.0 上测试):
idx = pd.MultiIndex.from_product((range(1000), range(10)))
df = pd.DataFrame({c: range(10000) for c in 'ABCD'}, index=idx)
%%timeit
dict1 = df.groupby(level=0).apply(lambda df: df.xs(df.name).to_dict()).to_dict()
# 849 ms ± 11.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit
dict2 = {}
for (idx1, idx2), *vals in df.itertuples():
for col, val in zip(df.columns, vals):
dict2.setdefault(idx1, {}).setdefault(col, {})[idx2] = val
# 50.3 ms ± 4.59 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
从上面的基准测试中可以看出,如果有 1000 个组,那么直接循环比
groupby.apply
解决方案快 16 倍。随着团体数量的增加,这种差距会扩大。
另一方面,如果组数很少(<10) then both approaches are very fast so performance probably isn't an issue anymore.
关于这一点的更多解释可以在将 Pandas Dataframe 转换为嵌套 JSON 找到。