根据条件替换并聚合 pandas 中的行

vin*_*pal 11 python group-by numpy dataframe pandas

我有一个数据框:

   lft rel rgt num
0   t3  r3  z2  3
1   t1  r3  x1  9
2   x2  r3  t2  8
3   x4  r1  t2  4
4   t1  r1  z3  1
5   x1  r1  t2  2
6   x2  r2  t4  4
7   z3  r2  t4  5
8   t4  r3  x3  4
9   z1  r2  t3  4
Run Code Online (Sandbox Code Playgroud)

以及参考词典:

replacement_dict = {
    'X1' : ['x1', 'x2', 'x3', 'x4'],
    'Y1' : ['y1', 'y2'],
    'Z1' : ['z1', 'z2', 'z3']
}
Run Code Online (Sandbox Code Playgroud)

我的目标是将所有出现的 'X1' 替换replacement_dict['X1'],然后计算行的分组总和num

例如,“x1”、“x2”、“x3”或“x4”的任何实例都将被替换为“X1”等,并且“X1”-“r1”-“t2”组的总和(由上面的重新映射创建的)是 6,等等。

所以我想要的输出是:

replacement_dict = {
    'X1' : ['x1', 'x2', 'x3', 'x4'],
    'Y1' : ['y1', 'y2'],
    'Z1' : ['z1', 'z2', 'z3']
}
Run Code Online (Sandbox Code Playgroud)

我正在使用一个包含 600 万行的数据框和一个包含 60,000 个键的替换字典。使用简单的逐行提取和替换会花费很长时间。

如何有效地扩展这一点(特别是最后一部分)?有人可以推荐熊猫技巧吗?

cot*_*ail 10

反转replacement_dict映射并将map()此新映射到每个 lft 和 rgt 列以替换某些值(例如 x1->X1、y2->Y1 等)。由于 lft 和 rgt 列中的某些值在映射中不存在(例如 t1、t2 等),因此请调用fillna()以填写这些值。1

您还可以对stack()需要替换值的列(lft 和 rgt)调用 map+fillna 并unstack()返回,但因为只有 2 列,对于这种特殊情况可能不值得麻烦。

问题的第二部分可以通过按 lft、rel 和 rgt 列分组后对 num 值求和来回答;所以groupby().sum()应该做到这一点。

# reverse replacement map
reverse_map = {v : k for k, li in replacement_dict.items() for v in li}

# substitute values in lft column using reverse_map
df['lft'] = df['lft'].map(reverse_map).fillna(df['lft'])
# substitute values in rgt column using reverse_map
df['rgt'] = df['rgt'].map(reverse_map).fillna(df['rgt'])

# sum values in num column by groups
result = df.groupby(['lft', 'rel', 'rgt'], as_index=False)['num'].sum()
Run Code Online (Sandbox Code Playgroud)

1 : map()+fillna()可能比您的用例执行得更好,replace()因为在幕后,map()实现了 Cython 优化take_nd()方法,如果有很多值需要替换,则该方法执行得特别好,同时replace()实现replace_list()使用 Python 循环的方法。因此,如果replacement_dict特别大(在您的情况下),性能差异将会很大,但如果replacement_dict很小,replace()可能会优于map()

请参阅此答案,其中包括显示字典大小和数据帧长度之间交互的不同基准,以了解何时使用replace以及何时使用map+ fillna


Cod*_*ent 6

如果你翻转 的键和值replacement_dict,事情就会变得容易得多:

new_replacement_dict = {
    v: key
    for key, values in replacement_dict.items()
    for v in values
}

cols = ["lft", "rel", "rgt"]
df[cols] = df[cols].replace(new_replacement_dict)
df.groupby(cols).sum()
Run Code Online (Sandbox Code Playgroud)


Rab*_*zel 5

试试这个,我评论了步骤

#reverse dict to dissolve the lists as values
reversed_dict = {v:k for k,val in replacement_dict.items() for v in val}

# replace the values
cols = ['lft', 'rel', 'rgt']
df[cols] = df[cols].replace(reversed_dict)

# filter rows where X1 is anywhere in the columns
df = df[df.eq('X1').any(axis=1)]

# sum the duplicate rows
out = df_filtered.groupby(cols).sum().reset_index()
print(out)
Run Code Online (Sandbox Code Playgroud)

输出:

#reverse dict to dissolve the lists as values
reversed_dict = {v:k for k,val in replacement_dict.items() for v in val}

# replace the values
cols = ['lft', 'rel', 'rgt']
df[cols] = df[cols].replace(reversed_dict)

# filter rows where X1 is anywhere in the columns
df = df[df.eq('X1').any(axis=1)]

# sum the duplicate rows
out = df_filtered.groupby(cols).sum().reset_index()
print(out)
Run Code Online (Sandbox Code Playgroud)