pandas - 基于列值合并几乎重复的行

66

我有一个包含多行数据的 pandas 数据框,其中有一些行基本相同,仅有一个值不同。我的目标是将这些行合并或“合并”,转化为一行数据,但不要对数值进行求和。

以下是我处理的一个示例:

Name   Sid   Use_Case  Revenue
A      xx01  Voice     $10.00
A      xx01  SMS       $10.00
B      xx02  Voice     $5.00
C      xx03  Voice     $15.00
C      xx03  SMS       $15.00
C      xx03  Video     $15.00

这是我想要的:

Name   Sid   Use_Case            Revenue
A      xx01  Voice, SMS          $10.00
B      xx02  Voice               $5.00
C      xx03  Voice, SMS, Video   $15.00

我不想对“Revenue”列求和的原因是,我的表是在多个时间段内进行数据透视后得出的,其中“Revenue”仅仅被列出了多次,而不是每个“Use_Case”拥有不同的值。

如何解决这个问题?我已经研究过groupby()函数,但我仍然不太理解它。

4个回答

100

我认为你可以使用 groupbyaggregate,结合 first 和自定义函数 ', '.join

df = df.groupby('Name').agg({'Sid':'first', 
                             'Use_Case': ', '.join, 
                             'Revenue':'first' }).reset_index()

#change column order                           
print df[['Name','Sid','Use_Case','Revenue']]                              
  Name   Sid           Use_Case Revenue
0    A  xx01         Voice, SMS  $10.00
1    B  xx02              Voice   $5.00
2    C  xx03  Voice, SMS, Video  $15.00

评论中提出了不错的想法,感谢 Goyo:

df = df.groupby(['Name','Sid','Revenue'])['Use_Case'].apply(', '.join).reset_index()

#change column order                           
print df[['Name','Sid','Use_Case','Revenue']]                              
  Name   Sid           Use_Case Revenue
0    A  xx01         Voice, SMS  $10.00
1    B  xx02              Voice   $5.00
2    C  xx03  Voice, SMS, Video  $15.00

5
为了保险起见,我会按照除了“Use_Case”以外的所有内容进行分组。另外,聚合函数可以只用, .join,不需要使用lambda - Stop harming Monica
1
是的,或者使用.astype(str),它是用于转换为字符串的函数。 - jezrael
1
@jezrael 如何仅加入“Use_Case”的唯一值:', '.join。 - panda
1
@jezrael 请检查聊天框。 - panda
1
@jezrael 非常感谢,这个Panda代码中相当于MYSQL的是什么? df.groupby('Name').agg({'Sid':'first', 'Use_Case': ', '.join, 'Revenue':'sum' }).reset_index() - user1862965
显示剩余9条评论

24
你可以使用`groupby`和`apply`函数,并应用`list`函数:

你可以groupby并且apply`list`函数:

>>> df['Use_Case'].groupby([df.Name, df.Sid, df.Revenue]).apply(list).reset_index()
    Name    Sid     Revenue     0
0   A   xx01    $10.00  [Voice, SMS]
1   B   xx02    $5.00   [Voice]
2   C   xx03    $15.00  [Voice, SMS, Video]

(如果您担心重复项,请使用 set 而不是 list。)


1
无法感谢你在这里提供的答案! - seizethedata
1
这是我认为最性感的解决方案 :) - mrGott
如果我想在多个“Use_Case”列上执行此操作,语法应该是什么?我在寻找这个方面遇到了困难。 - undefined

4

我曾使用一些代码,但并不认为它们是最佳的。后来我发现了jezrael的回答。但在使用它并运行timeit测试后,我实际上又回到了我原先做的事情:

cmnts = {}
for i, row in df.iterrows():
    while True:
        try:
            if row['Use_Case']:
                cmnts[row['Name']].append(row['Use_Case'])

            else:
                cmnts[row['Name']].append('n/a')

            break

        except KeyError:
            cmnts[row['Name']] = []

df.drop_duplicates('Name', inplace=True)
df['Use_Case'] = ['; '.join(v) for v in cmnts.values()]

根据我的 100 次运行 timeit 测试,迭代和替换方法比 groupby 方法快一个数量级。
import pandas as pd
from my_stuff import time_something

df = pd.DataFrame({'a': [i / (i % 4 + 1) for i in range(1, 10001)],
                   'b': [i for i in range(1, 10001)]})

runs = 100

interim_dict = 'txt = {}\n' \
               'for i, row in df.iterrows():\n' \
               '    try:\n' \
               "        txt[row['a']].append(row['b'])\n\n" \
               '    except KeyError:\n' \
               "        txt[row['a']] = []\n" \
               "df.drop_duplicates('a', inplace=True)\n" \
               "df['b'] = ['; '.join(v) for v in txt.values()]"

grouping = "new_df = df.groupby('a')['b'].apply(str).apply('; '.join).reset_index()"

print(time_something(interim_dict, runs, beg_string='Interim Dict', glbls=globals()))
print(time_something(grouping, runs, beg_string='Group By', glbls=globals()))

产量:

Interim Dict
  Total: 59.1164s
  Avg: 591163748.5887ns

Group By
  Total: 430.6203s
  Avg: 4306203366.1827ns

这里的time_something是一个函数,它使用timeit计时代码片段,并以上述格式返回结果。


1

在关注 @jezrael 和 @leoschet 的回答之后,我想提供一个更通用的示例,以防数据框中有更多列,这是我最近必须处理的情况。

明确地说,我的数据框总共有184个列

REF应该用作groupby的参考列,而其他182个剩余的列中只有另外一个名为IDS的列与它不同,我希望将其元素折叠成列表id1,id2,id3...

所以:

# Create a dictionary {df_all_columns_name : 'first', 'IDS': join} for agg
# Also avoid REF column in dictionary (inserted after aggregation)
columns_collapse = {c: 'first' if c != 'IDS' else ', '.join for c in my_df.columns.tolist() if c != 'REF'}
my_df = my_df.groupby('REF').agg(columns_collapse).reset_index()

希望这对某人也有用!

敬礼!


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