在Python Pandas中,基于其他列对列进行分组

3

假设我有以下的 pd.DataFrame

Name | Color
------------------------------
John | Blue
Greg | Red
John | Yellow
Greg | Red
Greg | Blue

我想要得到每个名称不同颜色的表格 - 包括数量和值。例如,如下所示:
Name | Distinct | Values
--------------------------------------
John |   2      | Blue, Yellow
Greg |   2      | Red, Blue

有什么想法如何做到这一点吗?

尝试使用agg了吗? - cs95
@sshashank124 循环遍历所有 Name 的值,对该值过滤数据帧并应用一个lambda函数。 - shakedzy
1
这与https://dev59.com/uI7da4cB1Zd3GeqP_Elz非常相似,唯一的区别是您使用逗号字符而不是空格进行连接。 - EdChum
2
@shakedzy,能请教您一个问题吗?如果您的需求在未来发生变化,您认为您将能够适应这三种解决方案中的哪一种?我很感兴趣听到您的想法,因为虽然我们都乐意提供解决方案和帮助,但我认为了解什么是可持续的对社区有利。 - jpp
1
@shakedzy,好的,这很有帮助 - 谢谢!就我个人而言,我不认为我会使用nuinque而不是map(len),但可能是因为我在学习Python之前就已经接触了Pandas。 - jpp
显示剩余3条评论
3个回答

5
使用groupby + agg,传递自定义的聚合函数list
f = [
        ('Distinct', 'nunique'), 
        ('Values', lambda x: ', '.join(x.unique()))
]

df.groupby('Name').Color.agg(f).reset_index()

   Name  Distinct        Values
0  Greg         2     Red, Blue
1  John         2  Blue, Yellow

时间

首先,进行设置 -


df = pd.DataFrame(
      np.random.randint(0, 1000, (10000, 2)).astype(str), columns=['Name', 'Color']
)

接下来是时间问题。看起来pd.Series.unique速度过慢(慢了4倍)。为了提高性能,我将使用np.unique代替:

# in this answer

%%timeit
f = [
        ('Distinct', 'nunique'), 
        ('Values', lambda x: ', '.join(np.unique(x.values).tolist()))
]

df.groupby('Name').Color.agg(f).reset_index()

122 ms ± 1.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

使用lambda x: ', '.join(x.unique())会导致速度减慢4倍。在这些数据上,使用set会快一些,但这真的取决于具体情况。

# @jpp

%%timeit
v = df.groupby('Name')['Color'].apply(set).reset_index()
v['Distinct'] = v['Color'].map(len)
v['Color'] = v['Color'].map(', '.join)

219 ms ± 1.83 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# @jezrael

%%timeit
(df.groupby('Name')['Color'].agg(['nunique', lambda x: ', '.join(set(x))])
        .rename(columns={'nunique':'Distinct', '<lambda>':'Values'})
        .reset_index())

118 ms ± 4.29 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

在处理数据时,性能会因数据的不同而差异极大,在决定使用何种解决方案之前,建议对自己的数据进行时间测试。


@COLDSPEED,加上时间统计怎么样?你的看起来是2.74毫秒±232微秒每次循环(平均值±7次运行的标准偏差,每次100个循环),而@jezrael的时间是2.94毫秒±513微秒每次循环(平均值±7次运行的标准偏差,每次100个循环),@jpp的时间是3.18毫秒±263微秒每次循环(平均值±7次运行的标准偏差,每次100个循环) - Vivek Kalyanarangan
@VivekKalyanarangan 我有点惊讶它们不完全相同...它们应该是 :) - cs95
@VivekKalyanarangan,我在我的帖子中添加了一些时间。 - jpp
2
@shakedzy 嗯,我是通过观察其他用户回答类似问题来学习的 :) 经验法则是大多数常用函数都被支持:mean、sum、max、min、size、count、nunique...等等。这并不是一个详尽的列表。 - cs95
@jpp 哈哈,我也是 :p - cs95
显示剩余2条评论

4

使用 groupbyagg,最后通过rename重命名列和reset_index进行操作:

df = (df.groupby('Name')['Color'].agg(['nunique', lambda x: ', '.join(set(x))])
        .rename(columns={'nunique':'Distinct', '<lambda>':'Values'})
        .reset_index())
print (df)
   Name  Distinct        Values
0  Greg         2     Blue, Red
1  John         2  Blue, Yellow

请问您能解释一下 '<lambda>':'Values' 这部分的含义吗? - Vivek Kalyanarangan
2
@Vivek Kalyanarangan - 这只是重命名默认列 <lambda> :) - jezrael

3

避免低效的 lambda 的一种方法:

df = df.groupby('Name')['Color'].apply(set).reset_index()

df['Distinct'] = df['Color'].map(len)
df['Color'] = df['Color'].map(', '.join)

#    Name         Color  Distinct
# 0  Greg     Red, Blue         2
# 1  John  Yellow, Blue         2

顺带一提,我注意到了一种趋势,即方法链接和/或单行计算。如果性能不是问题,建议使用您认为更有用/可读的方法。就个人而言,我更喜欢将计算分为三部分。

性能基准测试

import pandas as pd
import numpy as np
from random import choice
from string import ascii_uppercase

df = pd.DataFrame({'Name': np.random.randint(0, 100, 10000),
                   'Color': [''.join(choice(ascii_uppercase) for _ in range(2)) for k in range(10000)]})

def jpp(df):
    df = df.groupby('Name')['Color'].apply(set).reset_index()

    df['Distinct'] = df['Color'].map(len)
    df['Color'] = df['Color'].map(', '.join)
    return df

def jez(df):
    return df.groupby('Name')['Color'].agg(['nunique', lambda x: ', '.join(set(x))])\
            .rename(columns={'nunique':'Distinct', '<lambda>':'Values'})\
            .reset_index()

def cs(df):
    f = [
            ('Distinct', 'nunique'), 
            ('Values', lambda x: ', '.join(x.unique()))
    ]

    return df.groupby('Name').Color.agg(f).reset_index()

%timeit jpp(df)  # 100 loops, best of 3: 15.7 ms per loop
%timeit jez(df)  # 10 loops, best of 3: 22.9 ms per loop
%timeit cs(df)   # 10 loops, best of 3: 27.1 ms per loop

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