将活动数据拆分为小时并获得每小时的持续时间

时间:2018-04-10 09:32:47

标签: python pandas datetime

我有关于以下活动的数据:

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

1 个答案:

答案 0 :(得分:0)

答案概述:

  1. 制作样本数据集
  2. 添加包含持续时间,小时和日期信息的列
  3. 处理需要拆分的行
  4. 合并具有非拆分行的拆分行以生成最终结果
  5. 1。制作样本数据集

    我将起始日期设置为与原始数据相同,并使用随机数生成器生成其他数据。这在我的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
    

    2。添加包含持续时间,小时,日期信息的列

    这是相对简单的。没有必要进行纯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
    

    3。处理需要拆分的行

    这是困难的部分。在这里,我使用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
    

    4。合并具有非拆分行的拆分行

    最后,只需将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提供。