【问题标题】:Python pandas:Fast way to create a unique identifier for groupsPython pandas:为组创建唯一标识符的快速方法
【发布时间】:2016-05-12 12:02:15
【问题描述】:

我的数据看起来像这样

df
Out[10]: 
  ID1 ID2  Price       Date
0  11  21  10.99  3/15/2016
1  11  22  11.99  3/15/2016
2  12  23      5  3/15/2016
3  11  21  10.99  3/16/2016
4  11  22  12.99  3/16/2016
5  11  21  10.99  3/17/2016
6  11  22  11.99  3/17/2016

目标是为每组 ID1 获取一个唯一 ID,并为其每个 ID2 提供特定价格,如下所示:

    # Desired Result
df
Out[14]: 
  ID1 ID2  Price       Date  UID
0  11  21  10.99  3/15/2016    1
1  11  22  11.99  3/15/2016    1
2  12  23      5  3/15/2016    7
3  11  21  10.99  3/16/2016    5
4  11  22  12.99  3/16/2016    5
5  11  21  10.99  3/17/2016    1
6  11  22  11.99  3/17/2016    1

由于数据的大小,速度是一个问题。我能想到的最好方法如下,但它仍然比预期的要慢很多。如果有人有一种他们认为应该自然更快的方式,我很乐意听到。或者也许有一种简单的方法可以并行执行组内操作以加快速度?

我的方法基本上是连接 ID 和价格(在填充零以确保长度相同之后),然后进行排名以简化最终 ID。瓶颈是使用 .transform(np.sum) 完成的组内连接。

# concatenate ID2 and Price
df['ID23'] = df['ID2'] + df['Price']

df
Out[12]: 
  ID1 ID2  Price       Date     ID23
0  11  21  10.99  3/15/2016  2110.99
1  11  22  11.99  3/15/2016  2211.99
2  12  23      5  3/15/2016      235
3  11  21  10.99  3/16/2016  2110.99
4  11  22  12.99  3/16/2016  2212.99
5  11  21  10.99  3/17/2016  2110.99
6  11  22  11.99  3/17/2016  2211.99


# groupby ID1 and Date and then concatenate the ID23's
grouped = df.groupby(['ID1','Date'])
df['summed'] = grouped['ID23'].transform(np.sum)

df
Out[16]: 
  ID1 ID2    Price       Date      ID23            summed                UID
0   6   3  0010.99  3/15/2016  30010.99  30010.9960011.99  630010.9960011.99
1   6   6  0011.99  3/15/2016  60011.99  30010.9960011.99  630010.9960011.99
2   7   7  0000005  3/15/2016  70000005          70000005          770000005
3   6   3  0010.99  3/16/2016  30010.99  30010.9960012.99  630010.9960012.99
4   6   6  0012.99  3/16/2016  60012.99  30010.9960012.99  630010.9960012.99
5   6   3  0010.99  3/17/2016  30010.99  30010.9960011.99  630010.9960011.99
6   6   6  0011.99  3/17/2016  60011.99  30010.9960011.99  630010.9960011.99

# Concatenate ID1 on the front and take rank to get simpler ID's    
df['UID'] = df['ID1'] + df['summed'] 
df['UID'] = df['UID'].rank(method = 'min')

# Drop unnecessary columns
df.drop(['ID23','summed'], axis=1, inplace=True)

更新:

为澄清起见,请考虑按如下方式分组的原始数据:

grouped = df.groupby(['ID1','Date'])
    for name, group in grouped:
    print group

  ID1 ID2  Price       Date
0  11  21  10.99  3/15/2016
1  11  22  11.99  3/15/2016

  ID1 ID2  Price       Date
3  11  21  10.99  3/16/2016
4  11  22  12.99  3/16/2016

  ID1 ID2  Price       Date
5  11  21  10.99  3/17/2016
6  11  22  11.99  3/17/2016

  ID1 ID2 Price       Date
2  12  23     5  3/15/2016

UID 应该在组级别,并且如果关于该组的所有内容都相同,则忽略日期。所以在这种情况下,第一个和第三个打印组是相同的,这意味着第 0、1、5 和 6 行都应该获得相同的 UID。第 3 行和第 4 行属于不同的组,因为价格发生了变化,因此需要不同的 UID。第 2 行也是不同的组。

看待这个问题的一种稍微不同的方式是,我想像我在这里一样进行分组,删除日期列(这对于最初形成组很重要),然后在我删除后在相等的组之间聚合日期。

【问题讨论】:

  • 现在'UID' 对行01 是如何相同的? 'ID1' 两行相同,但 'ID2''Price' 实际上是不同的。
  • 好问题。我应该解释得更好。 ID1 就像 ID2 的父 ID。因此,第 0 行和第 1 行是同一组的一部分:该组由 ID1 = 11 及其当天的所有子项(ID2)组成。我需要该组级别的唯一 ID。这些行的 UID 与最后两行相同,尽管日期不同,因为 ID1、ID2 和价格相同。
  • 唯一标识符必须能够识别任何行,因此您可以命名列,其组合可以保证行的唯一性或使用人工唯一键,如 GUID。否则迟早你的唯一密钥算法会出现问题。只是我的 0.02 美元。
  • 感谢您的 cmets MaxU。我认为我当前的方法确实可以识别任何行。如果我删除存在的排名调用只是为了使最终结果更漂亮,则 1 的 UID 实际上是“11210010.99220011.99”。从中我可以准确读出它对应的行:ID1 = 11,ID2 = 21,Price = 10.99;然后该组中的下一行 ID2 = 22,依此类推。很难方便地命名列,因为组的大小不同。抱歉,我很难解释清楚。
  • 您的数据中的第 0、3 和 5 行有什么区别? (为什么 0 和 5 有相同的“UID”,而不是 3?或者它是一个错字?)

标签: python pandas


【解决方案1】:

编辑: 下面的代码实际上比 OP 的解决方案要慢。我暂时保留它,以防有人使用它来编写更好的解决方案。


为了可视化,我将使用以下数据:

df
Out[421]: 
    ID1  ID2  Price       Date
0    11   21  10.99  3/15/2016
1    11   22  11.99  3/15/2016
2    12   23   5.00  3/15/2016
3    11   21  10.99  3/16/2016
4    11   22  12.99  3/16/2016
5    11   21  10.99  3/17/2016
6    11   22  11.99  3/17/2016
7    11   22  11.99  3/18/2016
8    11   21  10.99  3/18/2016
9    12   22  11.99  3/18/2016
10   12   21  10.99  3/18/2016
11   12   23   5.00  3/19/2016
12   12   23   5.00  3/19/2016

首先,让我们按“ID1”和“日期”对其进行分组,并将结果聚合为元组(已排序)。我还重置了索引,所以有一个名为“索引”的新列。

gr = df.reset_index().groupby(['ID1','Date'], as_index = False)
df1 = gr.agg(lambda x : tuple(sorted(x)))
df1
Out[425]: 
   ID1       Date     index       ID2           Price
0   11  3/15/2016    (0, 1)  (21, 22)  (10.99, 11.99)
1   11  3/16/2016    (3, 4)  (21, 22)  (10.99, 12.99)
2   11  3/17/2016    (5, 6)  (21, 22)  (10.99, 11.99)
3   11  3/18/2016    (7, 8)  (21, 22)  (10.99, 11.99)
4   12  3/15/2016      (2,)     (23,)          (5.0,)
5   12  3/18/2016   (9, 10)  (21, 22)  (10.99, 11.99)
6   12  3/19/2016  (11, 12)  (23, 23)      (5.0, 5.0)

完成所有分组后,我将使用列 'index' 中的索引来访问来自 df 的行(它们最好是唯一的)。 (还要注意df1.indexdf1['index'] 是完全不同的东西。)

现在,让我们将'index' 分组(跳过日期):

df2 = df1.groupby(['ID1','ID2','Price'], as_index = False)['index'].sum()
df2
Out[427]: 
   ID1       ID2           Price               index
0   11  (21, 22)  (10.99, 11.99)  (0, 1, 5, 6, 7, 8)
1   11  (21, 22)  (10.99, 12.99)              (3, 4)
2   12  (21, 22)  (10.99, 11.99)             (9, 10)
3   12     (23,)          (5.0,)                (2,)
4   12  (23, 23)      (5.0, 5.0)            (11, 12)

我相信这是解决问题所需的分组,因此我们现在可以将标签添加到 df。比如这样:

df['GID'] = -1
for i, t in enumerate(df2['index']):
    df.loc[t,'GID'] = i

df
Out[430]: 
    ID1  ID2  Price       Date  GID
0    11   21  10.99  3/15/2016    0
1    11   22  11.99  3/15/2016    0
2    12   23   5.00  3/15/2016    3
3    11   21  10.99  3/16/2016    1
4    11   22  12.99  3/16/2016    1
5    11   21  10.99  3/17/2016    0
6    11   22  11.99  3/17/2016    0
7    11   22  11.99  3/18/2016    0
8    11   21  10.99  3/18/2016    0
9    12   22  11.99  3/18/2016    2
10   12   21  10.99  3/18/2016    2
11   12   23   5.00  3/19/2016    4
12   12   23   5.00  3/19/2016    4

或者以一种可能更快但更棘手的方式:

# EXPERIMENTAL CODE!
df3 = df2['index'].apply(pd.Series).stack().reset_index()
df3.index = df3[0].astype(int)
df['GID'] = df3['level_0']

【讨论】:

  • 我喜欢你的解决方案 ptrj。使用元组比像我以前那样连接一堆字符串要灵活得多。我很惊讶你的方法最终变得有点慢。我猜这是因为对 groupby 求和是 cythonized (?) 所以即使使用更流畅的代码,gr.agg() 步骤也会更慢。
  • @Archimedes 我没有测试性能。 tuple(sorted(x)) 的 groupby 是这么慢还是整个代码?
  • 是的,大部分时间都是 gr.agg(lambda x : tuple(sorted(x))) 行。
  • @Archimedes 罪魁祸首可能是sorted。如果是这样(即如果gr.agg(lambda x : tuple(x)) 运行得更快),那么您可以尝试一些解决方法,例如预先按“ID2”和“价格”对 df 进行排序(groupby 应该保留顺序)。您也可以尝试将sort = False 传递给每个 groupby。
  • 不幸的是,删除排序似乎没什么区别。值得注意的是,在我的数据中,在大多数情况下,给定的 ID1 只有一个 ID2。所以在大多数情况下,排序没有做任何事情。
猜你喜欢
  • 1970-01-01
  • 2020-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多