如何在一个分组中将一个列中的行值与另一个列中的所有其他行进行比较?

3
我有一个数据框,包含用户ID、产品ID、创建时间和删除时间等列。我想添加一个布尔型的“is_switch”列,如果对于给定的用户,创建时间戳在时间间隔(比如一秒)内与该用户组中任何其他行的删除时间相同,则为True。如何在不迭代每一行的情况下完成这个操作?或者说,这是适当的方法吗?
我正在尝试编写一个自定义函数,用于在.apply中运行每个用户组,但我不知道如何一次性比较所有其他行与当前行。
# Code to create sample data frame. 
# the below are just timestamps that are within a second of each other.

import datetime

a = datetime.datetime.now()
a2 = a-datetime.timedelta(seconds=1)
b = datetime.datetime.now()-datetime.timedelta(days=4)
b2 = b-datetime.timedelta(seconds=1)
c = datetime.datetime.now()-datetime.timedelta(days=40)
c2 = c - datetime.timedelta(seconds=1)
d = datetime.datetime.now()-datetime.timedelta(days=30)
d2 = d - datetime.timedelta(seconds=1)
e = datetime.datetime.now()-datetime.timedelta(days=60)
e2 = e - datetime.timedelta(seconds=1)
f = datetime.datetime.now()-datetime.timedelta(days=100)
g = datetime.datetime.now()-datetime.timedelta(days=99)

df = pd.DataFrame(
{"user_id" : [0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4],
"product_id" : [100, 101, 102, 101, 102, 104, 105, 106, 107, 105, 106, 107],
"created_at" : [a, a, b, c, d, c, f, f, e2, f, f, d],
"removed_at" : ['NaT', b2, 'NaT', d2, 'NaT', 'NaT', e, g, 'NaT', e2, g, b]},
index = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
df

print(df)

yields this:


        user_id  product_id                 created_at                 removed_at
0         0         100 2019-08-04 09:15:05.200981                        NaT
1         1         101 2019-08-04 09:15:05.200981 2019-07-31 09:15:04.201063
2         1         102 2019-07-31 09:15:05.201063                        NaT
3         2         101 2019-06-25 09:15:05.201121 2019-07-05 09:15:04.201179
4         2         102 2019-07-05 09:15:05.201179                        NaT
5         2         104 2019-06-25 09:15:05.201121                        NaT
6         3         105 2019-04-26 09:15:05.201290 2019-06-05 09:15:05.201235
7         3         106 2019-04-26 09:15:05.201290 2019-04-27 09:15:05.201324
8         3         107 2019-06-05 09:15:04.201235                        NaT
9         4         105 2019-04-26 09:15:05.201290 2019-06-05 09:15:04.201235
10        4         106 2019-04-26 09:15:05.201290 2019-04-27 09:15:05.201324
11        4         107 2019-07-05 09:15:05.201179 2019-07-31 09:15:05.201063

所以我现在有这样的东西:

group_by_user = df.groupby('user_id')

def calculate_is_switch(grp):
    # What goes here? how can i do it without iterating over each row?

# group_by_user.apply(calculate_is_switch)

我想添加“is_switch”列,以便输出结果如下:
    user_id  product_id                 created_at                 removed_at  \
0         0         100 2019-08-04 09:15:05.200981                        NaT   
1         1         101 2019-08-04 09:15:05.200981 2019-07-31 09:15:04.201063   
2         1         102 2019-07-31 09:15:05.201063                        NaT   
3         2         101 2019-06-25 09:15:05.201121 2019-07-05 09:15:04.201179   
4         2         102 2019-07-05 09:15:05.201179                        NaT   
5         2         104 2019-06-25 09:15:05.201121                        NaT   
6         3         105 2019-04-26 09:15:05.201290 2019-06-05 09:15:05.201235   
7         3         106 2019-04-26 09:15:05.201290 2019-04-27 09:15:05.201324   
8         3         107 2019-06-05 09:15:04.201235                        NaT   
9         4         105 2019-04-26 09:15:05.201290 2019-06-05 09:15:04.201235   
10        4         106 2019-04-26 09:15:05.201290 2019-04-27 09:15:05.201324   
11        4         107 2019-07-05 09:15:05.201179 2019-07-31 09:15:05.201063   

    is_switch  
0       False  
1       False  
2        True  
3       False  
4        True  
5       False  
6       False  
7       False  
8        True  
9       False  
10      False  
11      False  

每个组中是否始终存在removed_at的第一个值(如果存在于该组中)? - jezrael
在每个user_id中,removed_at是否可能有多个值? - Erfan
@dryOlive - 答案已被编辑,请核对一下。 - jezrael
1
@jezrael 输出结果与期望的不符,每个用户组中并不一定要至少有一个True。如果用户在1秒窗口之外的时间订阅,则不被视为切换。 - dryOlive
1
@dryOlive - 抱歉,请将 y = np.any((np.abs(y).astype(np.int64) <= val.value), axis=1) 更改为 y = np.any((np.abs(y).astype(np.int64) <= val.value), axis=0) - jezrael
显示剩余5条评论
2个回答

3
使用自定义函数与GroupBy.apply一起使用-首先使用某个默认值datetime替换缺失值,例如Timestamp.min,然后按组比较列与广播-所有值与created_at通过removed_at,获取绝对值,每行至少比较1秒并通过any返回至少一个True
val = pd.Timedelta(1, unit='s')

def f(x):
    y = x['created_at'].values - x['removed_at'].values[:, None]
    y = np.any((np.abs(y).astype(np.int64) <= val.value), axis=0)

    return pd.Series(y, index=x.index)

df['is_switch'] = (df.assign(removed_at = df['removed_at'].fillna(pd.Timestamp.min))
                     .groupby('user_id')
                     .apply(f)
                     .reset_index(level=0, drop=True))

print(df)
    user_id  product_id                 created_at                 removed_at  \
0         0         100 2019-08-04 16:22:39.309093                        NaT   
1         1         101 2019-08-04 16:22:39.309093 2019-07-31 16:22:38.309093   
2         1         102 2019-07-31 16:22:39.309093                        NaT   
3         2         101 2019-06-25 16:22:39.309093 2019-07-05 16:22:38.309093   
4         2         102 2019-07-05 16:22:39.309093                        NaT   
5         2         104 2019-06-25 16:22:39.309093                        NaT   
6         3         105 2019-04-26 16:22:39.309093 2019-06-05 16:22:39.309093   
7         3         106 2019-04-26 16:22:39.309093 2019-04-27 16:22:39.309093   
8         3         107 2019-06-05 16:22:38.309093                        NaT   
9         4         105 2019-04-26 16:22:39.309093 2019-06-05 16:22:38.309093   
10        4         106 2019-04-26 16:22:39.309093 2019-04-27 16:22:39.309093   
11        4         107 2019-07-05 16:22:39.309093 2019-07-31 16:22:39.309093   

    is_switch  
0       False  
1       False  
2        True  
3       False  
4        True  
5       False  
6       False  
7       False  
8        True  
9       False  
10      False  
11      False 

我认为这个问题更加复杂,因为一个用户可能有多个“removed_at”日期。所以在这种情况下,“transform('first')”将不起作用。 - Erfan

0

一行代码就可以了:

print(~df['created_at'].sub(df.groupby('user_id').transform('first')['created_at']).dt.days.between(-1, 1))

输出:

0    False
1    False
2     True
3    False
4     True
5    False
Name: created_at, dtype: bool

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