使Pandas的groupby函数与itertools的groupby函数类似

17
假设我有一个Python字典,其中包含列表,如下所示:
{'Grp': ['2'   , '6'   , '6'   , '5'   , '5'   , '6'   , '6'   , '7'   , '7'   , '6'], 
'Nums': ['6.20', '6.30', '6.80', '6.45', '6.55', '6.35', '6.37', '6.36', '6.78', '6.33']}

我可以使用itertools.groupby轻松对数字和关键字进行分组:

from itertools import groupby
for k, l in groupby(zip(di['Grp'], di['Nums']), key=lambda t: t[0]):
    print k, [t[1] for t in l]

输出:

2 ['6.20']
6 ['6.30', '6.80']      # one field, key=6
5 ['6.45', '6.55']
6 ['6.35', '6.37']      # second
7 ['6.36', '6.78']
6 ['6.33']              # third

注意,6键被分为三个单独的组或字段。
现在假设我有与我的字典等效的Pandas DataFrame(相同的数据,相同的列表顺序和相同的键):
  Grp  Nums
0   2  6.20
1   6  6.30
2   6  6.80
3   5  6.45
4   5  6.55
5   6  6.35
6   6  6.37
7   7  6.36
8   7  6.78
9   6  6.33

如果我使用Pandas的groupby,我无法看到如何进行按组迭代。相反,Pandas通过键值分组:
for e in df.groupby('Grp'):
    print e

输出:

('2',   Grp  Nums
0   2  6.20)
('5',   Grp  Nums
3   5  6.45
4   5  6.55)
('6',   Grp  Nums
1   6  6.30            
2   6  6.80                # df['Grp'][1:2] first field
5   6  6.35                # df['Grp'][5:6] second field
6   6  6.37                 
9   6  6.33)               # df['Grp'][9] third field
('7',   Grp  Nums
7   7  6.36
8   7  6.78)

请注意,6组键是聚在一起的,而不是分开的不同组。

我的问题是:是否有一种等效的方法使用Pandas的groupby,使得例如6以与Python的groupby相同的方式分为三组?

我尝试过这样做:

>>> df.reset_index().groupby('Grp')['index'].apply(lambda x: np.array(x))
Grp
2                [0]
5             [3, 4]
6    [1, 2, 5, 6, 9]         # I *could* do a second groupby on this...
7             [7, 8]
Name: index, dtype: object

但是它仍然按照总体的Grp键进行分组,我需要在nd.array上再次进行分组,以将每个键的子组拆分出来。


1
有趣的问题。但在这种情况下应该返回什么对象呢?我的意思是,groupby 被设计为返回一个具有唯一键的对象,但是这在这里是不可能的。您想如何区分重复键(6的不同组)? - Alex Riley
有时候,按键的连续运行是数据的另一个元素。另一个按键的存在表示某些东西,例如时间间隔或数据读数。一旦在系列中出现不同的间隔,那就是一个不同的字段。我想要类似itertools的连续相似键的运行。 - user648852
3
这里有一种方法可以提供解决方案。链接 - Alex Riley
3个回答

22

首先,您可以识别与前一个不同的Grp列中的元素,并进行累加以形成所需的组:

首先,您可以识别Grp列中与前一个不同的元素,并使用累计总和来形成所需的组:

In [9]:
    diff_to_previous = df.Grp != df.Grp.shift(1)
    diff_to_previous.cumsum()
Out[9]:

0    1
1    2
2    2
3    3
4    3
5    4
6    4
7    5
8    5
9    6

那么你随后可以执行

df.groupby(diff_to_previous.cumsum()) 

获取所需的按组分组的对象


6

那么,不想太过鲁莽,为什么不直接在DataFrame上使用Python的groupby函数,并通过iterrows方法迭代?这正是它存在的意义:

>>> df
  Grp  Nums
0   2  6.20
1   6  6.30
2   6  6.80
3   5  6.45
4   5  6.55
5   6  6.35
6   6  6.37
7   7  6.36
8   7  6.78
9   6  6.33

>>> from itertools import groupby
>>> for k, l in groupby(df.iterrows(), key=lambda row: row[1]['Grp']):
        print k, [t[1]['Nums'] for t in l]

输出:

2 ['6.20']
6 ['6.30', '6.80']
5 ['6.45', '6.55']
6 ['6.35', '6.37']
7 ['6.36', '6.78']
6 ['6.33']

尝试让Panda的groupby按照你想要的方式运行,可能需要使用大量嵌套的方法,以至于在将来重新阅读时可能无法理解。

4
回答你的问题,很可能会变慢。 - The Unfun Cat
@TheUnfunCat:在更大的数据框上执行可能会更慢,但其他选择可能对程序员编写来说更慢。 - dawg

2
你基本上想创建一个新列来索引你所需的分组顺序,然后使用它进行分组。在Grp的值改变之前,保持索引号不变。
对于你的数据,你需要像这样:
   Grp  Nums new_group
0    2  6.20         1
1    6  6.30         2
2    6  6.80         2
3    5  6.45         3
4    5  6.55         3
5    6  6.35         4
6    6  6.37         4
7    7  6.36         5
8    7  6.78         5
9    6  6.33         6

现在您可以同时按新组Grp进行分组:

df.groupby(['new_group', 'Grp']).Nums.groups
{(1, 2): [0],
 (2, 6): [1, 2],
 (3, 5): [3, 4],
 (4, 6): [5, 6],
 (5, 7): [7, 8],
 (6, 6): [9]

我使用了这种方法创建新的列:
df['new_group'] = None
for n, grp in enumerate(df.Grp):
if n is 0:
    df.new_group.iat[0] = 1    
elif grp == df.Grp.iat[n - 1]:
    df.new_group.iat[n] = df.new_group.iat[n - 1]
else:
    df.new_group.iat[n] = df.new_group.iat[n - 1] + 1

请注意,这个答案在这里有相同的想法(感谢@ajcr提供链接),但表述更加简洁明了。
>>> df.groupby((df.Grp != df.Grp.shift()).cumsum()).Nums.groups
{1: [0], 2: [1, 2], 3: [3, 4], 4: [5, 6], 5: [7, 8], 6: [9]

该死!我刚在发布答案后才看到这个... 我发誓那是我的;P - JoeCondron
我猜我刚开始学习Pandas,但是如何使用{1:[0],2:[1,2],3:[3,4],4:[5,6],5:[7,8],6:[9]}df获取键和行呢?我知道这些对应关系,但我很难将其转换为Pythonic中我熟悉的切片或其他东西...抱歉... - user648852
df.groupby(['new_group', 'Grp']).Nums.groups.keys() dict_keys([(1, 2), (2, 6), (4, 6), (6, 6), (5, 7), (3, 5)]) - Alexander
[df.loc[(df.new_group == k1) & (df.Grp == k2), :] for k1, k2 in df.groupby(['new_group', 'Grp']).Nums.groups.keys()] 注意,groups是一个没有一致顺序的字典,因此您可能需要重新排序列表。 - Alexander

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