来自数据框的任意深度的嵌套字典

3
我可以从pandas dataframe的分类列中创建多层字典,最多三层 - 参见代码。 但是我的解决方案太过硬编码... 如果我想要按10个分类列进行'split'呢?
我正在寻找一些能够执行以下伪代码操作的东西:
d = {'A': ['a1','a1','a2'], 'B': ['b1','b2','b3'], 'C': ['c1','c2','c2'], 'v': [0,5,1]}
df = pd.DataFrame(data=d)

dA = tree(df=d, cols=['A'])
#it gives dictionary of two dataframes
# "tree" should be some standard implementation
#a1 
#a2

dB = tree(df=d, cols=['A', 'B'])
#it give dictionary of three dataframes at lowest level
#a1_b1
#a1_b2
#a2_b3
#"tree" should be ready for any number of cols

#acces operations
dA['a1'], dB['a1'], dB['a1]['b1],...

#iteration operation (transpose is just for example)
dA = dA.iter.T #transposes every dataframe
dB = dB.iter.T #transposes every dataframe on lowest level i.e. dB['a1]['b1].T, dB['a1]['b2].T, ...

#some operations will require access to dictionary keys to make sense or to have enough flexibility:
dA.iter.to_csv(str(key)+'csv')
#produces a1.csv, a2.csv
dB.iter.to_csv(str(key)+'csv')
#produces a1_b1.csv, a1_b2.csv, a2_b3.csv

基本上:为了轻松地从数据框创建任意深度的嵌套字典,创建可操作于任何深度的“键级别”函数,并在整个字典上迭代,而无需为每个级别编写代码。
我的代码:
import pandas as pd
from collections import defaultdict

# sample dataframe
d = {'A': ['a1','a1','a2'], 'B': ['b1','b2','b3'], 'C': ['c1','c2','c2'], 'v': [0,5,1]}
df = pd.DataFrame(data=d)

# make dictionary of dataframes based on categorical column, every categroy is a key to dataframe
def dict_dfs_based_on_cat(df, col):
    Cat = df[col].unique()
    dictDFbasedOnCat = {elem: pd.DataFrame for elem in Cat}
    for key in dictDFbasedOnCat.keys(): 
        dictDFbasedOnCat[key] = df[:][df[col]==key]
    return dictDFbasedOnCat

#1st level 
di_A = dict_dfs_based_on_cat(df, 'A')

#2nd level
di_A_B= {}
for a in di_A:
    di_A_B[a] = dict_dfs_based_on_cat(di_A[a], 'B')

#3rd level
di_A_B_C = defaultdict(dict)
for a in di_A:
    for b in di_A_B[a]:
        di_A_B_C[a][b] = dict_dfs_based_on_cat(di_A_B[a][b],'C')

#operations on 3rd level
def iter_di(msg, func, di):
    print(msg)
    for a in di:
        for b in di[a]:
            for c in di[a][b]:
                func(a, b, c, di)

def save(a, b, c, di):
    di[a][b][c].to_csv(str(a)+'_'+str(b)+'_'+str(c)+'.csv', index=False)

#sample operation
iter_di('saving', save, di_A_B_C)

#a1_b1_c1.csv
#a1_b2_c2.csv
#a2_b3_c2.csv

这里的解决方案可能会有所帮助。与其在字符串中循环遍历行,不如通过循环遍历数据框并检索必要的列来实现。这里提供了具体实现。 - dROOOze
3个回答

1
您发布的代码可能存在一些问题:
  • d = {'A': ['a1','a1','a2'], 'B': ['b1','b2','b3'], 'C': 'c1','c2','c2'], 'v': [0,5,1]} 缺少右括号(很明显需要修复)
  • return dictDFbasedOnCat 可能缩进有误。
无论如何,在假设代码应该是什么并运行后,di_A_B_C 返回
>>> di_A_B_C
defaultdict(<type 'dict'>, {'a1': {'b1': {'c1':     A   B   C  v
0  a1  b1  c1  0}, 'b2': {'c2':     A   B   C  v
1  a1  b2  c2  5}}, 'a2': {'b3': {'c2':     A   B   C  v
2  a2  b3  c2  1}}})

这个结果可以与递归函数匹配:

def update_nested_dict(d, vars, frame):
    if len(vars) > 2:
        try:
            d[vars[0]] = update_nested_dict(d[vars[0]], vars[1:], frame)
        except KeyError:
            d[vars[0]] = update_nested_dict({}, vars[1:], frame)
    else:
        try:
            d[vars[0]].update({vars[1]: frame})
        except KeyError:
            d[vars[0]] = {vars[1]: frame}
    return d

您可以定义一个函数,该函数接受一个 DataFrame 对象和您想要排序的列的确切顺序,然后输出一个 defaultdict 对象:
def dataframe_dict(df, cols=None):

    if cols is None:
        cols = df.keys()

    di = {}
    df_col_inds = dict(zip(df.keys(), range(len(df.keys()))))
    df_col_inds = [df_col_inds[c] for c in cols]
    for v in df.values:
        _ = update_nested_dict(di, v[df_col_inds], pd.DataFrame(dict(zip(df.keys(), v[:,None]))))

    return defaultdict(dict, di)

例如,匹配您的di_A_B_C:
>>> dataframe_dict(df, ['A', 'B', 'C'])
defaultdict(<type 'dict'>, {'a1': {'b1': {'c1':     A   B   C  v
0  a1  b1  c1  0}, 'b2': {'c2':     A   B   C  v
0  a1  b2  c2  5}}, 'a2': {'b3': {'c2':     A   B   C  v
0  a2  b3  c2  1}}})

使用所有列:

>>> dataframe_dict(df) # Same as dataframe_dict(df, df.keys()) = dataframe_dict(df, ['A', 'B', 'C', 'v'])
defaultdict(<type 'dict'>, {'a1': {'b1': {'c1': {0L:     A   B   C  v
0  a1  b1  c1  0}}, 'b2': {'c2': {5L:     A   B   C  v
0  a1  b2  c2  5}}}, 'a2': {'b3': {'c2': {1L:     A   B   C  v
0  a2  b3  c2  1}}}})

列的随机顺序:

>>> dataframe_dict(df, ['v', 'C', 'A'])
defaultdict(<type 'dict'>, {0L: {'c1': {'a1':     A   B   C  v
0  a1  b1  c1  0}}, 1L: {'c2': {'a2':     A   B   C  v
0  a2  b3  c2  1}}, 5L: {'c2': {'a1':     A   B   C  v
0  a1  b2  c2  5}}})

@drooze 非常感谢您的帮助和代码纠正(由于我在复制粘贴时出现了一些错误)。我会检查这个递归方法。 - Quant Christo
@drooze 我在思考这个问题,基本上我需要动态构建一棵树。叶子节点中将包含特定的数据框。为了执行我的操作,我需要迭代叶子节点。 - Quant Christo
@QuantChristo,请在问题中提供一个非常简洁的示例,说明您的操作是什么,这样您将获得更好的答案。 - dROOOze
@drooze,希望现在我的伪代码更清晰了。 - Quant Christo
1
@QuantChristo 希望您不要试图在'pandas'中实现新方法,因为这就是syntax pd.tree的意思。也许您想定义一个名为'tree'的函数?此外,目前不清楚为什么dB = dA = pd.tree(df=d, cols=['A', 'B'])会希望dB.iter.T将低级别的转置操作应用于比dA.iter.T更低的级别,因为它们都指向同一引用-那是多重赋值操作。也许您的意思是tree()应该返回两个数据对象,因为您在['A','B']中指定了两个对象? - dROOOze
@drooze 当然,我不想实现任何新功能,因此我将“pd.tree”更改为“tree”,但如果pandas有这样的功能,那就太好了。 dB = dA 我的错误,应该是 dB = tree(df=d, cols=['A', 'B']) - Quant Christo

0

如果你把字典的字典转换成列表的列表,可能会在某些程度上有所帮助。

import itertools

d = [['attrs',['a1','a1','a2']], ['B',['b1','b2','b3']], ['C',['c1','c2','c2']], ['v',[0,5,1]]]

list_of_items = [k[1] for k in d]
z = list(itertools.product(*list_of_items))

很遗憾,我有一个数据框架,而 d 只是为了让例子更具体化。 - Quant Christo

0

虽然并没有完全回答我的问题,但它解决了我的问题。 我不需要嵌套的数据框字典,因为我不需要存储中间结果 - 只需对数据框的切片执行一些操作并将其存储在 CSV 中。

import pandas as pd

d = {'A': ['a1','a1','a2'], 'B': ['b1','b2','b3'], 'C': ['c1','c2','c2'], 'v': [0,5,1]}
df = pd.DataFrame(data=d)

df = df.set_index(['A','B','C'])

for i in set(df.index):
    df_sel = df.loc[i].reset_index()
    df_sel = df_sel.T #or any other operation on dataframe
    df_sel.to_csv(str(i) + '.csv')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接