我有关于以下活动的数据:
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之后的第二天,这是另外一个问题。
我使用以下代码完成它并且它可以工作,但是当它遍历行时,它非常慢。 我操作的文件超过1百万行,因此欢迎任何提高效率的线索。
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
答案 0 :(得分:0)
我将起始日期设置为与原始数据相同,并使用随机数生成器生成其他数据。这在我的Macbook上大约需要 40 ms 。
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')})
数据集有100k条记录。约82%不需要分割,约17%需要一次分割,> 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 ms :
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笔记本here提供。