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

11

我有一个数据框:

   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

以及一个参考字典:

replacement_dict = {
    'X1' : ['x1', 'x2', 'x3', 'x4'],
    'Y1' : ['y1', 'y2'],
    'Z1' : ['z1', 'z2', 'z3']
}
我的目标是将所有出现replacement_dict['X1']的地方替换为'X1',然后将这些行合并在一起。例如,任何包含'x1'、'x2'、'x3'或'x4'的实例都将被替换为'X1'等。
我可以通过选择包含任何这些字符串的行并用'X1'替换它们来实现此目标:
keys = replacement_dict.keys()
for key in keys:
    DF.loc[DF['lft'].isin(replacement_dict[key]), 'lft'] = key
    DF.loc[DF['rgt'].isin(replacement_dict[key]), 'rgt'] = key

给予:

    lft rel rgt num
0   t3  r3  Z1  3
1   t1  r3  X1  9
2   X1  r3  t2  8
3   X1  r1  t2  4
4   t1  r1  Z1  1
5   X1  r1  t2  2
6   X1  r2  t4  4
7   Z1  r2  t4  5
8   t4  r3  X1  4
9   Z1  r2  t3  4

现在,如果我选择包含“X1”的所有行并进行合并,最终应该得到:

    lft rel rgt num
0   X1  r3  t2  8
1   X1  r1  t2  6
2   X1  r2  t4  4
3   t1  r3  X1  9
4   t4  r3  X1  4

所以这三列 ['lft', 'rel', 'rgt'] 是唯一的,而 'num' 列是这些行的总和。以上第1行:['X1' 'r1' 't2' 6] 是两行 ['X1' 'r1' 't2' 4] 和 ['X1' 'r1' 't2' 2] 的总和。

对于少量行,我可以轻松地做到这一点,但我正在使用有600万行和60,000个键的替换字典的数据框,使用简单的逐行提取和替换需要很长时间。

如何高效地扩展这个过程(特别是最后一部分)?有人能推荐一些 Pandas 技巧吗?

6个回答

10

反转 replacement_dict 映射,并将此新映射映射到 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()

1: 使用 map()fillna() 可能比使用 replace() 更适合您的用例,因为在内部,map() 实现了一个 Cython 优化的 take_nd() 方法,如果需要替换的值很多,则该方法表现特别好,而 replace() 则实现了使用 Python 循环的 replace_list() 方法。因此,如果 replacement_dict 特别大(在你的情况下是这样的),性能差异将会很大,但如果 replacement_dict 很小,则 replace() 可能会表现更好。


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()

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)

输出:

  lft rel rgt  num
0  X1  r1  t2    6
1  X1  r2  t4    4
2  X1  r3  t2    8
3  t1  r3  X1    9
4  t4  r3  X1    4

3
Pandas内置了一个名为replace的函数,比使用.loc遍历整个数据帧更快。
您还可以在其中传递列表,使我们的字典很适合使用它。
keys = replacement_dict.keys()

# Loop through every value in our dictionary and get the replacements

for key in keys:
  DF = DF.replace(to_replace=replacement_dict[key], value=key)

一旦我完成替换,如何对相似行的“num”字段求和? - vineeth venugopal
我不太明白我们从哪里得到两行相加等于num列。你能具体说明一下吗? - Jimpsoni
1
一旦替换实施,将出现具有相同['lft','rel','rgt']值但不同'num'值的行(上面的第3和第5行)。 这些是具有不同'weights'的相同行。 我想将它们合并。 - vineeth venugopal

3
这里有一种方法可以实现你的问题所要求的功能:
df[['lft','rgt']] = ( df[['lft','rgt']]
    .replace({it:k for k, v in replacement_dict.items() for it in v}) )
df = ( df[(df.lft == 'X1') | (df.rgt == 'X1')]
    .groupby(['lft','rel','rgt']).sum().reset_index() )

输出:

  lft rel rgt  num
0  X1  r1  t2    6
1  X1  r2  t4    4
2  X1  r3  t2    8
3  t1  r3  X1    9
4  t4  r3  X1    4

说明:

  • replace() 使用反转版本的字典将原始字典中列表中的项替换为相关 df 列 lftrgt 中对应的键。
  • 筛选包含 'X1' 的行后,在使用 groupby()sum()reset_index() 对唯一的 lft, rel, rgt 组键的 num 列求和并将组分量从索引级别恢复到列。

作为替代,我们可以使用 query() 仅选择包含 'X1' 的行:

df[['lft','rgt']] = ( df[['lft','rgt']]
    .replace({it:k for k, v in replacement_dict.items() for it in v}) )
df = ( df.query("lft=='X1' or rgt=='X1'")
    .groupby(['lft','rel','rgt']).sum().reset_index() )

1

有很多很好的答案。我避免使用字典,并使用df.apply()来生成新数据,如下所示。

import io
import pandas as pd


# # create the data
x = '''
lft rel rgt num
t3 r3 z2 3
t1 r3 x1 9
x2 r3 t2 8
x4 r1 t2 4
t1 r1 z3 1
x1 r1 t2 2
x2 r2 t4 4
z3 r2 t4 5
t4 r3 x3 4
z1 r2 t3 4
'''


data = io.StringIO(x)
df = pd.read_csv(data, sep=' ')
print(df)

replacement_dict = {
    'X1' : ['x1', 'x2', 'x3', 'x4'],
    'Y1' : ['y1', 'y2'],
    'Z1' : ['z1', 'z2', 'z3']
}


def replace(x):
    # which key to check
    key_check = x[0] + '1'
    key_check = key_check.upper()

    return key_check


df['new'] = df['lft'].apply(replace)
df

返回this:

  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
  lft rel rgt  num new
0  t3  r3  z2    3  T1
1  t1  r3  x1    9  T1
2  x2  r3  t2    8  X1
3  x4  r1  t2    4  X1
4  t1  r1  z3    1  T1
5  x1  r1  t2    2  X1
6  x2  r2  t4    4  X1
7  z3  r2  t4    5  Z1
8  t4  r3  x3    4  T1
9  z1  r2  t3    4  Z1

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