Pandas DataFrame 分组以生成数字多级索引

3

我想在Pandas DataFrame上应用分组操作,而不进行任何聚合。相反,我只想让分层结构在MultiIndex中得到体现。

import pandas as pd

def multi_index_group_by(df, columns):
    # TODO: How to write this? (Hard-coded to give the desired result for the example.)
    if columns == ["b"]:
        df.index = pd.MultiIndex(levels=[[0,1],[0,1,2]], labels=[[0,1,0,1,0],[0,0,1,1,2]])
        return df
    if columns == ["c"]:
        df.index = pd.MultiIndex(levels=[[0,1],[0,1],[0,1]], labels=[[0,1,0,1,0],[0,0,0,1,1],[0,0,1,0,0]])
        return df

if __name__ == '__main__':
    df = pd.DataFrame({
        "a": [0,1,2,3,4],
        "b": ["b0","b1","b0","b1","b0"],
        "c": ["c0","c0","c0","c1","c1"],
    })
    print(df.index.values) # [0,1,2,3,4]


    # Add level of grouping
    df = multi_index_group_by(df, ["b"])
    print(df.index.values) # [(0, 0) (1, 0) (0, 1) (1, 1) (0, 2)]

    # Examples
    print(df.loc[0]) # Group 0
    print(df.loc[1,1]) # Group 1, Item 1


    # Add level of grouping
    df = multi_index_group_by(df, ["c"])
    print(df.index.values) # [(0, 0, 0) (1, 0, 0) (0, 0, 1) (1, 1, 0) (0, 1, 0)]

    # Examples
    print(df.loc[0]) # Group 0
    print(df.loc[0,0]) # Group 0, Sub-Group 0
    print(df.loc[0,0,1]) # Group 0, Sub-Group 0, Item 1

如何最好地实现multi_index_group_by?以下方法几乎可以完成,但生成的索引不是数字:

index_columns = []
# Add level of grouping
index_columns += ["b"]
print(df.set_index(index_columns, drop=False))
# Add level of grouping
index_columns += ["c"]
print(df.set_index(index_columns, drop=False))

编辑: 为了澄清,在这个例子中,最终的索引应该等同于:

[
    [ #b0
        [ #c0
            {"a": 0, "b": "b0", "c": "c0"},
            {"a": 2, "b": "b0", "c": "c0"},
        ],
        [ #c1
            {"a": 4, "b": "b0", "c": "c1"},
        ]
    ],
    [ #b1
        [ #c0
            {"a": 1, "b": "b1", "c": "c0"},
        ],
        [ #c1
            {"a": 3, "b": "b1", "c": "c1"},
        ]
    ]
]

编辑:这是我到目前为止最好的翻译:

def autoincrement(value=0):
    def _autoincrement(*args, **kwargs):
        nonlocal value
        result = value
        value += 1
        return result
    return _autoincrement

def swap_levels(df, i, j):
    order = list(range(len(df.index.levels)))
    order[i], order[j] = order[j], order[i]
    return df.reorder_levels(order)

def multi_index_group_by(df, columns):
    new_index = df.groupby(columns)[columns[0]].aggregate(autoincrement())

    result = df.join(new_index.rename("_new_index"), on=columns)
    result.set_index('_new_index', append=True, drop=True, inplace=True)
    result.index.name = None
    result = swap_levels(result, -2, -1)
    return result

除了最后一个级别没有改变,它给出了正确的结果。仍然觉得有很大的改进空间。

你硬编码的MultiIndex是什么意思? - desiato
@desiato 这是一个稳步增加的 nd 索引(请参见示例)。例如 (0,1,2,...) 表示:第 0 组,第 1 子组,第 2 子子组等。 - kloffy
基本上,我想能够以与索引嵌套列表相同的方式引用行。 - kloffy
你硬编码的列b的MultiIndex正确吗?最后一个元组中的2代表哪个子组? - desiato
@desiato 我相信这是正确的。在这个例子中,只有两层嵌套。最后一个索引选择组中的项。我已经添加了一个列表结构,它将等同于我想要的索引。 - kloffy
2个回答

2

如果您愿意使用sklearn包,您可以使用LabelEncoder

from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()

def multi_index_group_by(df, columns):
    df.index = pd.MultiIndex.from_tuples( zip( *[ le.fit_transform( df[col] ) for  col in columns ] ) )
    return df

它将每一列的标签编码为0到n_classes-1之间的值

调用

multi_index_group_by( ['b','c'] )

给你
     a   b   c
0 0  0  b0  c0
1 0  1  b1  c0
0 0  2  b0  c0
1 1  3  b1  c1
0 1  4  b0  c1

哇,是的,那似乎非常接近我最终得出的结果(请参见我问题的最新编辑)。不确定是否值得依赖于sklearn,但是这是一个有趣的建议,谢谢! - kloffy
我会接受这个答案,因为它基本上做到了我想要的。如果有人需要一种不依赖于sklearn的替代方法,请参阅我问题的编辑。 - kloffy

1
这段代码可以实现你想要的功能:


index_columns = []
replace_values = {}

index_columns += ["b"]
replace_values.update({'b0':0, 'b1':1})

df[['idx_{}'.format(i) for i in index_columns]] = df[index_columns].replace(replace_values)
print(df.set_index(['idx_{}'.format(i) for i in index_columns], drop=True))

index_columns += ["c"]
replace_values.update({'c0':0, 'c1':1})

df[['idx_{}'.format(i) for i in index_columns]] = df[index_columns].replace(replace_values)
print(df.set_index(['idx_{}'.format(i) for i in index_columns], drop=True))

# If you want the 3rd ('c') level MultiIndex:
df['d'] = [0,0,1,0,0]
print(df.set_index(['idx_{}'.format(i) for i in index_columns] + ['d'], drop=True))

是的,我也在尝试类似的东西,但如果我不必手动跟踪索引会更好。谢谢你的建议,如果没有更好的方法出现,我会接受它的。 - kloffy

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