我的活动数据如下:
login_time logout_time a b c
2018-03-01 08:15:20 2018-03-01 08:16:01 0.000000 0.000000 62
2018-03-01 08:16:28 2018-03-01 08:19:38 52.199083 21.000718 62
2018-03-01 08:57:10 2018-03-01 09:46:26 52.199083 21.000590 62
2018-03-01 10:05:43 2018-03-01 10:08:51 0.000000 0.000000 62
2018-03-02 09:45:40 2018-03-02 09:47:16 52.239281 21.010551 62
我需要计算按日期和小时划分的会话持续时间(以秒为单位),因此结果应与此类似:
a b c duration hour date
0.000000 0.000000 62.0 41.0 8.0 2018-03-01
52.199083 21.000718 62.0 190.0 8.0 2018-03-01
52.199083 21.000590 62.0 170.0 8.0 2018-03-01
52.199083 21.000590 62.0 2786.0 9.0 2018-03-01
0.000000 0.000000 62.0 188.0 10.0 2018-03-01
52.239281 21.010551 62.0 96.0 9.0 2018-03-02
如您所见,源df中的第三行被划分为结果df中的两行。有时logout_time可能是login_time之后的第二天,这是另一个问题。
我使用下面的代码完成了该工作,但是它在行中进行迭代时非常慢。我处理的文件超过100万行,因此欢迎提供任何提示以提高效率。
def SplitAvail(df):
new_split=pd.DataFrame()
for i in np.arange(df.shape[0]):
row=df.iloc[i,:]
if (row.login_time.day==row.logout_time.day):
new_split=new_split.append(MakeSplitAvail(row))
else:
row1=row.copy()
row1.logout_time=datetime(row.login_time.year,row.login_time.month,
row.login_time.day, 23,59,59)
new_split=new_split.append(MakeSplitAvail(row1))
row2=row.copy()
row2.login_time=datetime(row.logout_time.year,row.logout_time.month,
row.logout_time.day, 0,0,0)
new_split=new_split.append(MakeSplitAvail(row2))
return new_split
def MakeSplitAvail(row):
split=pd.DataFrame()
for j in np.arange(row.login_time.hour, row.logout_time.hour+1,1):
row_t=row.copy()
h1=datetime(row.login_time.year,row.login_time.month,
row.login_time.day, j,0,0)
h2=h1+ dt.timedelta(hours=1)
row_t['hour']=j
row_t['duration']=(min(row_t.logout_time, h2)-max(row_t.login_time, h1))\
.total_seconds()
split=split.append(row_t)
return split
我将开始日期设置为与原始数据相同,并使用随机数生成器生成其他数据。在Macbook上,这大约需要40毫秒。
start = pd.Timestamp('2018-03-01 08:15:20').value
login_time = start + np.random.randint(10, 1000, size=100000).cumsum() * 10 ** 9
logout_time = login_time + np.random.lognormal(mean=6, size=100000) * 10 ** 9
df = pd.DataFrame({'login_time': pd.to_datetime(login_time),
'logout_time': pd.to_datetime(logout_time).round(freq='s')})
数据集有10万条记录。大约82%的用户不需要拆分,大约17%的用户需要1个拆分,> 1%的用户需要2个以上的拆分。可以通过更改使用的参数/分布类型来更改
df['hour_diff'].value_counts()
0 82309
1 17117
2 467
3 76
4 16
5 8
6 3
8 2
16 1
10 1
Name: hour_diff, dtype: int64
这是相对简单的。无需进行纯Python迭代。%
当日期更改时,模运算符用于修复负时差。在Macbook上,这大约需要1.4 s。
df['duration'] = (df['logout_time'] - df['login_time']).apply(lambda x: x.total_seconds())
df['date'] = df['login_time'].apply(lambda x: x.date())
df['hour'] = df['login_time'].apply(lambda x: x.hour)
df['hour_diff'] = (df['logout_time'].apply(lambda x: x.hour) - df['hour']) % 24
这是困难的部分。在这里,我使用itertuples
了相对快速的数据帧迭代。我将所有记录元组放在一个列表中,并从该列表中构建一个新的数据框。在新手中这是一个非常常见的错误,但是Pandas在迭代数据框架构建方面非常糟糕,因此我建议您避免这种情况。创建记录列表,然后从中建立新的数据框,速度更快。process_record
被实现为生成器功能,使事情变得更加优雅/高效。在Macbook上,这大约需要1.5 s。
def process_record(t):
cumtime = 0
r = t._asdict()
for i in range(t.hour_diff + 1):
pseudo_logout = min(t.logout_time, pd.Timestamp(t.date) + pd.Timedelta(hours=t.hour + i + 1))
duration = (pseudo_logout - t.login_time).total_seconds() - cumtime
cumtime += duration
r['duration'] = duration
yield tuple(r.values())
records = []
for t in df[df['hour_diff'] > 0].itertuples():
for r in process_record(t):
records.append(r)
split_df = pd.DataFrame(records)
split_df = split_df.drop(0, axis=1)
split_df.columns = df.columns
最后,只需将split_df
与中未更改的记录连接起来df
。在我的Macbook上,这大约需要30毫秒:
merged_df = pd.concat([split_df, df[df['hour_diff'] == 0]])
merged_df = merged_df.sort_values(by='login_time').reset_index(drop=True)
最终结果如下所示:
login_time logout_time duration date hour hour_diff
0 2018-03-01 08:21:29 2018-03-01 08:30:12 523.0 2018-03-01 8 0
1 2018-03-01 08:28:17 2018-03-01 08:42:47 870.0 2018-03-01 8 0
2 2018-03-01 08:33:17 2018-03-01 08:35:29 132.0 2018-03-01 8 0
3 2018-03-01 08:40:13 2018-03-01 08:45:50 337.0 2018-03-01 8 0
4 2018-03-01 08:45:12 2018-03-01 08:49:54 282.0 2018-03-01 8 0
5 2018-03-01 08:54:28 2018-03-01 09:01:19 332.0 2018-03-01 8 1
6 2018-03-01 08:54:28 2018-03-01 09:01:19 79.0 2018-03-01 8 1
7 2018-03-01 09:01:30 2018-03-01 09:03:06 96.0 2018-03-01 9 0
8 2018-03-01 09:04:01 2018-03-01 09:05:44 103.0 2018-03-01 9 0
9 2018-03-01 09:17:30 2018-03-01 09:46:40 1750.0 2018-03-01 9 0
10 2018-03-01 09:21:40 2018-03-01 09:22:31 51.0 2018-03-01 9 0
总的来说,单个内核上的100k记录(30 us /记录)大约需要3s。结果可能会有所不同,具体取决于需要拆分的记录数量,但是我想您应该能够轻松地每分钟处理1m +条记录。
我还做了这个可以作为一个Jupyter笔记本电脑在这里。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句