这是我的数据的非常简化的版本:
+----+---------+---------------------+
| | user_id | seconds_since_start |
+----+---------+---------------------+
| 0 | 1 | 10 |
| 1 | 1 | 12 |
| 2 | 1 | 15 |
| 3 | 1 | 52 |
| 4 | 1 | 60 |
| 5 | 1 | 67 |
| 6 | 1 | 120 |
| 7 | 2 | 55 |
| 8 | 2 | 62 |
| 9 | 2 | 105 |
| 10 | 3 | 200 |
| 11 | 3 | 206 |
+----+---------+---------------------+
这是我想产生的数据:
+----+---------+---------------------+-----------------+------------------+
| | user_id | seconds_since_start | session_ordinal | session_duration |
+----+---------+---------------------+-----------------+------------------+
| 0 | 1 | 10 | 1 | 5 |
| 1 | 1 | 12 | 1 | 5 |
| 2 | 1 | 15 | 1 | 5 |
| 3 | 1 | 52 | 2 | 15 |
| 4 | 1 | 60 | 2 | 15 |
| 5 | 1 | 67 | 2 | 15 |
| 6 | 1 | 120 | 3 | 0 |
| 7 | 2 | 55 | 1 | 7 |
| 8 | 2 | 62 | 1 | 7 |
| 9 | 2 | 105 | 2 | 0 |
| 10 | 3 | 200 | 1 | 6 |
| 11 | 3 | 206 | 1 | 6 |
+----+---------+---------------------+-----------------+------------------+
我的会话概念是一组来自单个用户的事件,它们之间的间隔不超过10秒,会话的持续时间定义为会话中第一个事件与最后一个事件之间的差(以秒为单位)
我已经编写了可以实现我想要的功能的Python。
import pandas as pd
events_data = [[1, 10], [1, 12], [1, 15], [1, 52], [1, 60], [1, 67], [1, 120],
[2, 55], [2, 62], [2, 105],
[3, 200], [3, 206]]
events = pd.DataFrame(data=events_data, columns=['user_id', 'seconds_since_start'])
def record_session(index_range, ordinal, duration):
for i in index_range:
events.at[i, 'session_ordinal'] = ordinal
events.at[i, 'session_duration'] = duration
session_indexes = []
current_user = previous_time = session_start = -1
session_num = 0
for i, row in events.iterrows():
if row['user_id'] != current_user or (row['seconds_since_start'] - previous_time) > 10:
record_session(session_indexes, session_num, previous_time - session_start)
session_indexes = [i]
session_num += 1
session_start = row['seconds_since_start']
if row['user_id'] != current_user:
current_user = row['user_id']
session_num = 1
previous_time = row['seconds_since_start']
session_indexes.append(i)
record_session(session_indexes, session_num, previous_time - session_start)
我的问题是运行所需的时间。如我所说,这是我的数据的非常简化的版本,我的实际数据有70,000,000行。是否有一种方法可以矢量化(从而加快)像这样的算法,这些算法根据变长预见来制定其他列?
答案 0 :(得分:1)
您可以尝试:
# Create a helper boolean Series
s = df.groupby('user_id')['seconds_since_start'].diff().gt(10)
df['session_ordinal'] = s.groupby(df['user_id']).cumsum().add(1).astype(int)
df['session_duration'] = (df.groupby(['user_id', 'session_ordinal'])['seconds_since_start']
.transform(lambda x: x.max() - x.min()))
[输出]
user_id seconds_since_start session_ordinal session_duration
0 1 10 1 5
1 1 12 1 5
2 1 15 1 5
3 1 52 2 15
4 1 60 2 15
5 1 67 2 15
6 1 120 3 0
7 2 55 1 7
8 2 62 1 7
9 2 105 2 0
10 3 200 1 6
11 3 206 1 6
答案 1 :(得分:0)
Chris A的答案here很不错。它包含一些我不熟悉的技术或电话。该答案将复制他的内容并添加大量注释。
我们首先建立一个辅助布尔系列。本系列记录了哪些事件为任何用户启动了附加会话。作为布尔序列,这是可以的,因为在数字上下文中,它们的行为类似于整数0和1 (引自here)。让我们将系列一点一点地放在一起。
starts_session = events.groupby('user_id')['seconds_since_start'].diff().gt(10)
首先,我们将事件按user_id(documentation)分组,然后选择“ seconds_since_start”列,并在其上调用diff(documentation)。 events.groupby('user_id')['seconds_since_start'].diff()
的结果是
+----+----------------------+
| | seconds_since_start |
+----+----------------------+
| 0 | NaN |
| 1 | 2.0 |
| 2 | 3.0 |
| 3 | 37.0 |
| 4 | 8.0 |
| 5 | 7.0 |
| 6 | 53.0 |
| 7 | NaN |
| 8 | 7.0 |
| 9 | 43.0 |
| 10 | NaN |
| 11 | 6.0 |
+----+----------------------+
我可以看到每个组的开始都已经获得了正确的NaN差异,因为该用户没有先前的事件可以提供差值。
然后使用大于gt(10)
(documentation)的元素,得出
+----+----------------------+
| | seconds_since_start |
+----+----------------------+
| 0 | False |
| 1 | False |
| 2 | False |
| 3 | True |
| 4 | False |
| 5 | False |
| 6 | True |
| 7 | False |
| 8 | False |
| 9 | True |
| 10 | False |
| 11 | False |
+----+----------------------+
(注:列标题为奇数,但未使用,因此无所谓。)
events['session_ordinal'] = starts_session.groupby(events['user_id']).cumsum().add(1).astype(int)
然后,我们根据事件中的user_id将starts_session重新分组,然后对每个组进行累积总和cumsum
(documentation)。分组在这里为我们完成了工作,确保每个用户的事件均从零重新开始。我们需要会话序数从1开始而不是零,因此我们只需添加一个add(1)
(documentation)并将其强制转换为int,因为它们都不是NaN astype(int)
({{3} }。这给出了我想要的派生session_ordinal列。
events['session_duration'] = events.groupby(['user_id', 'session_ordinal'])['seconds_since_start'].transform(lambda x: x.max() - x.min())
为了得出每个会话的持续时间,我们首先通过user_id和新的session_ordinal对事件进行分组,即,将事件分组为会话。使用transform
(documentation),我们可以找到每个组(即每个会话)的seconds_since_start的最小值和最大值,它们之间的差是会话的持续时间。将transform
应用于分组数据的这种模式在documentation流程中得到广泛使用。
谢谢克里斯。