按相似但不匹配的值对数据框进行分组

3
如果我有一个带有以下列的pandas数据框:idnumamount

我想对数据框进行分组,使得每个组中的所有行具有相同的idamount,并且每行的num值的值不超过下一行的num值的值10个以上或10个以下。

对于相同的id,如果从一行到另一行没有相同的amount,或者两个num值之间的绝对差大于10,则会开始一个新的分组。在中间有一个不同的id的行不会打破分组。

我该如何做到这一点?

我还没有成功地进行分组,其中我不需要寻找匹配值(就像这里一样,我需要它接近-但不匹配)。我假设这需要一些自定义分组函数,但我一直很难组合它们。

示例数据框:

编号 金额 数量
aaa-aaa 130 12
aaa-aaa 130 39
bbb-bbb 270 41
ccc-ccc 130 19
bbb-bbb 270 37
aaa-aaa 130 42
aaa-aaa 380 39

预期的分组结果:

Group 1:

编号 金额 数量
aaa-aaa 130 12

第二组:

编号 金额 数量
aaa-aaa 130 39
aaa-aaa 130 42

第三组:

编号 金额 数量
bbb-bbb 270 41
bbb-bbb 270 37

第四组:

id amount num
ccc-ccc 130 19

第五组:

id amount num
aaa-aaa 380 39

如果bbb-bbb的num为29而不是39会发生什么? - itprorh66
@mozway 抱歉,不知道新规则 :S - John Smith
1
@yem,逻辑不明确。如果您添加一个值为29的额外行,它是否应该合并这两个组?此外,您提到按ID分组,但在您的示例中它们都是不同的。 - mozway
抱歉造成困惑。我已经编辑了问题和示例数据框架。希望现在更清楚了。如果预期的逻辑仍然不清楚,请告诉我。 - yem
@mozway 我不确定我理解了。如果你去掉10,那么它不是[15, 20, 26, 30]吗?这仍然会使相邻数字之间的差小于10吧? - yem
显示剩余4条评论
2个回答

4

逻辑不是很清晰,但假设您想在间隔超过10时开始一个新组:

close = (df.sort_values(by=['amount', 'num'])
           .groupby('amount')
           ['num'].diff().abs().gt(10).cumsum()
         )

for _, g in df.groupby(['amount', close]):
    print(g, end='\n\n')

输出:

        id  amount  num
0  aaa-aaa     130   12
3  ddd-ddd     130   19

        id  amount  num
1  bbb-bbb     130   39

        id  amount  num
2  ccc-ccc     270   41
4  eee-eee     270   37
它是如何工作的:
# sort values by amount/sum
df.sort_values(by=['amount', 'num'])

        id  amount  num
0  aaa-aaa     130   12
3  ccc-ccc     130   19
1  aaa-aaa     130   39
5  aaa-aaa     130   42
4  bbb-bbb     270   37
2  bbb-bbb     270   41
6  aaa-aaa     380   39

# get the absolute successive difference in "num"
(df.sort_values(by=['amount', 'num'])
   .groupby('amount')
   ['num'].diff().abs()
)

0     NaN
3     7.0
1    20.0
5     3.0
4     NaN
2     4.0
6     NaN
Name: num, dtype: float64

# check if it's greater than 10 and cumsum
# to create a grouper for groupby

[...].gt(10).cumsum()

0    0
3    0
1    1
5    1
4    1
2    1
6    1
Name: num, dtype: int64

@GoldenLion 当然,我添加了细节。 - mozway

1

通过按数量编号排序,并添加一个辅助标记列difference(适合于连续值之间的阈值):

groups = df.sort_values(['amount', 'num'])\
         .assign(diff_=lambda x: x['num'].diff().abs().fillna(0).le(10))\
         .groupby(['amount', 'diff_'])
for _, g in groups:
    print(g)

         id  amount  num  diff_
1  bbb-bbb      130   39  False
         id  amount  num  diff_
0  aaa-aaa      130   12   True
3  ddd-ddd      130   19   True
         id  amount  num  diff_
4  eee-eee      270   37   True
2  ccc-ccc      270   41   True

这样做行不通。如果你有 [1, 2, 15, 16, 38, 99],按照你的逻辑,这将分为 [1, 2, 16][15, 38, 99]。你需要使用 cumsum 来在通过阈值后分组连续的行(请参见我的答案)。 - mozway
我意识到我忘记使用绝对差值了 ;) - mozway
@mozway,嗯,我已经尝试了[1, 2, 15, 16, 38],它显示你是错的,它发出了4个不同的组。顺便说一句,当你说某种方法肯定是错误的,同时又说“逻辑不是完全清楚”的时候,听起来很奇怪。 - RomanPerekhrest
1
你确定吗?你测试了哪些数据?(顺便说一句,我的评论只是友好的) - mozway
@mozway,从你的评论中提取OP的数据和num值(格式为'num': dict(enumerate([1, 2, 15, 16, 38])))。 - RomanPerekhrest
这里有一个可重现的例子:df = pd.DataFrame({'amount': [130, 130, 270, 130, 270, 130, 130, 130], 'num': [1, 2, 41, 15, 37, 16, 38, 99]}),你可以看到1/2/16被分组在一起,15/38/99也是如此。 - mozway

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