【问题标题】:Pandas: assign an index to each group identified by groupbyPandas:为 groupby 标识的每个组分配一个索引
【发布时间】:2017-05-26 11:16:33
【问题描述】:

当使用 groupby() 时,如何创建一个 DataFrame,其中包含一个包含组号索引的新列,类似于 R 中的dplyr::group_indices。例如,如果我有

>>> df=pd.DataFrame({'a':[1,1,1,2,2,2],'b':[1,1,2,1,1,2]})
>>> df
   a  b
0  1  1
1  1  1
2  1  2
3  2  1
4  2  1
5  2  2

我怎样才能得到像这样的DataFrame

   a  b  idx
0  1  1  1
1  1  1  1
2  1  2  2
3  2  1  3
4  2  1  3
5  2  2  4

idx 索引的顺序无关紧要)

【问题讨论】:

  • 从 0.20.2 开始,您可以使用 ngroup
  • 是的,ngroup 似乎是 0.20.2 前进的最简单和快速的选择(请注意,@calumyou 在下面添加了它作为答案),尽管其他答案可能仍然很有趣,因为它们给出组 ID 的值不同。
  • ngroup 也用于此答案:stackoverflow.com/questions/46067934/…

标签: python pandas


【解决方案1】:

这是使用来自a comment above by Constantinongroup(自pandas 0.20.2 起可用)的解决方案。

import pandas as pd
df = pd.DataFrame({'a':[1,1,1,2,2,2],'b':[1,1,2,1,1,2]})
df['idx'] = df.groupby(['a', 'b']).ngroup()
df
   a  b  idx
0  1  1    0
1  1  1    0
2  1  2    1
3  2  1    2
4  2  1    2
5  2  2    3

【讨论】:

  • 最好在具有更多组的更大数据帧上进行基准测试,但我认为这仍然是最快的(更不用说最简单的了)。我对 576 个组 (26x26) 和 100,000 行进行了基准测试,ngroup 与其他组一样快或更快。
【解决方案2】:

这是使用drop_duplicatesmerge 获取唯一标识符的简洁方法。

group_vars = ['a','b']
df.merge( df.drop_duplicates( group_vars ).reset_index(), on=group_vars )

   a  b  index
0  1  1      0
1  1  1      0
2  1  2      2
3  2  1      3
4  2  1      3
5  2  2      5

本例中的标识符为 0,2,3,5(只是原始索引的残差),但可以通过附加 reset_index(drop=True) 轻松更改为 0,1,2,3。

更新: 较新版本的 pandas (0.20.2) 提供了一种更简单的方法来执行此操作,如 @Constantino 对上述问题的评论和后续答案中所述的 ngroup 方法通过@CalumYou。我将把它留在这里作为替代方法,但在大多数情况下,ngroup 似乎是更好的方法。

【讨论】:

  • 巧妙且速度极快。如果需要有序索引,则只需在索引列上使用 pandas.factorize 并替换它。这应该是公认的答案!
  • 如果我错了,请纠正我,但组 (a=1, b=1) 现在有多个新索引。 OP 要求为每个组提供一个唯一索引。
【解决方案3】:

一种简单的方法是连接您的分组列(以便它们的值的每个组合代表一个唯一不同的元素),然后将其转换为 pandas Categorical 并仅保留其标签:

df['idx'] = pd.Categorical(df['a'].astype(str) + '_' + df['b'].astype(str)).codes
df

    a   b   idx
0   1   1   0
1   1   1   0
2   1   2   1
3   2   1   2
4   2   1   2
5   2   2   3

编辑:将labels 属性更改为codes,因为前者似乎已被弃用

Edit2:添加了 Authman Apatira 建议的分隔符

【讨论】:

  • 您可以另外添加1 以实现所需的OP,否则+1
  • 完美的解决方案,谢谢。如果我有大量列名,是否有一种巧妙的方法来连接列名,而无需重复 .astype(str) 调用?
  • 使用.codes避免FutureWarning消息.labels
  • @foglerit 谢谢。一个简洁的等价物类似于df[['a','b']].astype(str).apply("".join, 1)
  • 你们要小心以这种方式合并列。 a=11, b=1 将产生与 a=1, b=11 相同的组代码,而实际上它们是不同的。如果您想这样做,请务必在列之间添加某种分隔符。不过,我希望看到这种方法以适当的 groupby 为基准……同时满足内存和处理器要求。
【解决方案4】:

我认为比当前接受的答案快大约一个数量级的方法(时间结果如下):

def create_index_usingduplicated(df, grouping_cols=['a', 'b']):
    df.sort_values(grouping_cols, inplace=True)
    # You could do the following three lines in one, I just thought 
    # this would be clearer as an explanation of what's going on:
    duplicated = df.duplicated(subset=grouping_cols, keep='first')
    new_group = ~duplicated
    return new_group.cumsum()

计时结果:

a = np.random.randint(0, 1000, size=int(1e5))
b = np.random.randint(0, 1000, size=int(1e5))
df = pd.DataFrame({'a': a, 'b': b})

In [6]: %timeit df['idx'] = pd.Categorical(df['a'].astype(str) + df['b'].astype(str)).codes
1 loop, best of 3: 375 ms per loop

In [7]: %timeit df['idx'] = create_index_usingduplicated(df, grouping_cols=['a', 'b'])
100 loops, best of 3: 17.7 ms per loop

【讨论】:

  • 根据我的数据,我得到 group = pd.Categorical(data['day'])new_group = ~data.sort_values('day').duplicated(subset='day', keep='first'); group = new_group.cumsum() 快​​大约 5 倍。
【解决方案5】:

绝对不是最直接的解决方案,但这是我要做的(代码中的 cmets):

df=pd.DataFrame({'a':[1,1,1,2,2,2],'b':[1,1,2,1,1,2]})

#create a dummy grouper id by just joining desired rows
df["idx"] = df[["a","b"]].astype(str).apply(lambda x: "".join(x),axis=1)

print df

这将为ab 的每个组合生成一个唯一的idx。

   a  b idx
0  1  1  11
1  1  1  11
2  1  2  12
3  2  1  21
4  2  1  21
5  2  2  22

但这仍然是一个相当愚蠢的索引(想想ab 列中的一些更复杂的值。所以让我们清除索引:

# create a dictionary of dummy group_ids and their index-wise representation
dict_idx = dict(enumerate(set(df["idx"])))

# switch keys and values, so you can use dict in .replace method
dict_idx = {y:x for x,y in dict_idx.iteritems()}

#replace values with the generated dict
df["idx"].replace(dict_idx,inplace=True)

print df

这将产生所需的输出:

   a  b  idx
0  1  1    0
1  1  1    0
2  1  2    1
3  2  1    2
4  2  1    2
5  2  2    3

【讨论】:

    【解决方案6】:

    我不确定这是一个微不足道的问题。这是一个有点复杂的解决方案,它首先对分组列进行排序,然后检查每一行是否与前一行不同,如果是,则累加 1。进一步检查下面的字符串数据答案。

    df.sort_values(['a', 'b']).diff().fillna(0).ne(0).any(1).cumsum().add(1)
    

    输出

    0    1
    1    1
    2    2
    3    3
    4    3
    5    4
    dtype: int64
    

    所以把它分解成几个步骤,让我们看看df.sort_values(['a', 'b']).diff().fillna(0) 的输出,它检查每一行是否与前一行不同。任何非零条目都表示一个新组。

         a    b
    0  0.0  0.0
    1  0.0  0.0
    2  0.0  1.0
    3  1.0 -1.0
    4  0.0  0.0
    5  0.0  1.0
    

    一个新组只需要有一个不同的列,因此这是.ne(0).any(1) 检查的内容 - 对于任何列都不等于 0。然后只是一个累积总和来跟踪组。

    以字符串形式回答列

    #create fake data and sort it
    df=pd.DataFrame({'a':list('aabbaccdc'),'b':list('aabaacddd')})
    df1 = df.sort_values(['a', 'b'])
    

    df1的输出

       a  b
    0  a  a
    1  a  a
    4  a  a
    3  b  a
    2  b  b
    5  c  c
    6  c  d
    8  c  d
    7  d  d
    

    通过检查组是否已更改来采取类似的方法

    df1.ne(df1.shift().bfill()).any(1).cumsum().add(1)
    
    0    1
    1    1
    4    1
    3    2
    2    3
    5    4
    6    5
    8    5
    7    6
    

    【讨论】:

      猜你喜欢
      • 2017-06-11
      • 2017-12-18
      • 2019-06-12
      • 2020-10-16
      • 2018-05-22
      • 1970-01-01
      • 2022-01-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多