Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/292.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python Pandas:尝试在日期范围操作中加速每行每日期_Python_Pandas_Dataframe_Vectorization - Fatal编程技术网

Python Pandas:尝试在日期范围操作中加速每行每日期

Python Pandas:尝试在日期范围操作中加速每行每日期,python,pandas,dataframe,vectorization,Python,Pandas,Dataframe,Vectorization,我有一个以下形式的数据框,其中每一行对应于机器上运行的作业: import pandas as pd df = pd.DataFrame({ 'MachineID': [4, 3, 2, 2, 1, 1, 5, 3], 'JobStartDate': ['2020-01-01', '2020-01-01', '2020-01-01', '2020-01-01', '2020-01-02', '2020-01-03', '2020-01-01', '2020-01-03'],

我有一个以下形式的数据框,其中每一行对应于机器上运行的作业:

import pandas as pd
df = pd.DataFrame({
    'MachineID': [4, 3, 2, 2, 1, 1, 5, 3],
    'JobStartDate': ['2020-01-01', '2020-01-01', '2020-01-01', '2020-01-01', '2020-01-02', '2020-01-03', '2020-01-01', '2020-01-03'], 
    'JobEndDate': ['2020-01-03', '2020-01-03', '2020-01-04', '2020-01-02', '2020-01-04', '2020-01-05', '2020-01-02', '2020-01-04'], 
    'IsTypeAJob': [1, 1, 0, 1, 0, 0, 1, 1]
    })
    
df
>>>    MachineID JobStartDate JobEndDate  IsTypeAJob
0          4      2020-01-01  2020-01-03      1
1          3      2020-01-01  2020-01-03      1
2          2      2020-01-01  2020-01-04      0
3          2      2020-01-01  2020-01-02      1
4          1      2020-01-02  2020-01-04      0
5          1      2020-01-03  2020-01-05      0
6          5      2020-01-01  2020-01-02      1
7          3      2020-01-03  2020-01-04      1

在我的数据中,有两种类型的作业可以在机器上运行,一种是类型
a
,另一种是类型
B
。我的目标是计算每台机器每天的
A
类型和
B
类型作业数。因此,期望的结果看起来像

        MachineID    Date         TypeAJobs  TypeBJobs
0           1     2020-01-02          0          1
1           1     2020-01-03          0          2
2           1     2020-01-04          0          2
3           1     2020-01-05          0          1
4           2     2020-01-01          1          1
5           2     2020-01-02          1          1
6           2     2020-01-03          0          1
7           2     2020-01-04          0          1
8           3     2020-01-01          1          0
9           3     2020-01-02          1          0
10          3     2020-01-03          2          0
11          3     2020-01-04          1          0
12          4     2020-01-01          1          0
13          4     2020-01-02          1          0
14          4     2020-01-03          1          0
15          5     2020-01-01          1          0
16          5     2020-01-02          1          0
我用
resample()
apply()
方法尝试了找到的方法,但是计算时间太慢了。这与以下事实有关:在我的数据集中,某些日期范围跨越多年,这意味着在重新采样期间,一行可以扩展为2000多行(我的数据首先包含大约一百万行)。因此,为某个作业范围内的每个日期创建一个新的机器/日期行的速度太慢(目标是通过(['MachineID','date']).sum()执行
group\u操作)

我目前正在考虑一种新的方法,首先按
MachineID
分组,然后查找该机器的最早作业开始日期和最新作业结束日期。然后,我可以在这两个日期之间创建一个日期范围(按天递增),用于索引新的每台机器数据帧。然后,对于该
MachineID
的每个作业,我可能会在一系列日期内求和,即伪代码:

df['TypeAJobs'][行['JobStartDate']:行['JobEndDate']+=1
如果是类型
a
作业或

df['TypeBJobs'][行['JobStartDate']:行['JobEndDate']]+=1
否则

这似乎可以避免为每个作业创建一堆额外的行,因为现在我们正在为每台机器创建额外的行。此外,加法操作似乎速度很快,因为我们一次将加法添加到一个系列的整个片段中。然而,我不知道这样的事情(按日期索引)在熊猫中是否可行。也许可以先做一些转换?在完成上述操作后,理想情况下,我将拥有许多与所需结果相似的数据帧,但只有一个
MachineID
,然后我将连接这些数据帧以获得结果


我很想听到关于这种方法或其他潜在算法的可行性/有效性的任何建议。非常感谢你的阅读

IIUC,尝试使用
pd.date\u range
explode
创建“每日”行,然后按日期分组,IsTypeAJob和
重命名列:

df_out = df.assign(JobDates=df.apply(lambda x: pd.date_range(x['JobStartDate'], 
                                                             x['JobEndDate'], freq='D'), 
                                     axis=1))\
           .explode('JobDates')

df_out = df_out.groupby([df_out['MachineID'], 
                         df_out['JobDates'].dt.floor('D'), 
                         'IsTypeAJob'])['MachineID'].count()\
               .unstack()\
               .rename(columns={0:'TypeBJobs', 1:'TypeAJobs'})\
               .fillna(0).reset_index()

df_out
输出:

IsTypeAJob  MachineID   JobDates  TypeBJobs  TypeAJobs
0                   1 2020-01-02        1.0        0.0
1                   1 2020-01-03        2.0        0.0
2                   1 2020-01-04        2.0        0.0
3                   1 2020-01-05        1.0        0.0
4                   2 2020-01-01        1.0        1.0
5                   2 2020-01-02        1.0        1.0
6                   2 2020-01-03        1.0        0.0
7                   2 2020-01-04        1.0        0.0
8                   3 2020-01-01        0.0        1.0
9                   3 2020-01-02        0.0        1.0
10                  3 2020-01-03        0.0        2.0
11                  3 2020-01-04        0.0        1.0
12                  4 2020-01-01        0.0        1.0
13                  4 2020-01-02        0.0        1.0
14                  4 2020-01-03        0.0        1.0
15                  5 2020-01-01        0.0        1.0
16                  5 2020-01-02        0.0        1.0


这是另一种方法,其思想类似于在“开始”和“结束”两列上使用
str.get\u dummies
,但使用阵列广播完成。使用
cumsum
在开始和结束之间取一个,否则取0。创建一个数据框,列作为日期,索引作为机器和类型。然后执行与答案类似的操作,以获得预期的输出形状

#get all possible dates
dr = pd.date_range(df['JobStartDate'].min(), 
                   df['JobEndDate'].max()).strftime("%Y-%m-%d").to_numpy()

df_ = (pd.DataFrame(
            np.cumsum((df['JobStartDate'].to_numpy()[:, None] == dr).astype(int) 
                       - np.pad(df['JobEndDate'].to_numpy()[:, None]==dr,((0,0),(1,False)), 
                                mode='constant')[:, :-1],  # pad is equivalent to shift along columns
                      axis=1), 
            index=pd.MultiIndex.from_frame(df[['MachineID', 'IsTypeAJob']]), 
            columns=dr,)
         .sum(level=['MachineID', 'IsTypeAJob']) #equivalent to groupby(['MachineID', 'IsTypeAJob']).sum()
         .replace(0, np.nan) #to remove extra dates per original row during the stack
         .stack()
         .unstack(level='IsTypeAJob', fill_value=0)
         .astype(int)
         .reset_index()
         .rename_axis(columns=None)
         .rename(columns={'level_1':'Date', 0:'TypeBJobs', 1:'TypeAJobs'})
      )
你得到了什么

    MachineID        Date  TypeBJobs  TypeAJobs
0           1  2020-01-02          1          0
1           1  2020-01-03          2          0
2           1  2020-01-04          2          0
3           1  2020-01-05          1          0
4           2  2020-01-01          1          1
5           2  2020-01-02          1          1
6           2  2020-01-03          1          0
7           2  2020-01-04          1          0
8           3  2020-01-01          0          1
9           3  2020-01-02          0          1
10          3  2020-01-03          0          2
11          3  2020-01-04          0          1
12          4  2020-01-01          0          1
13          4  2020-01-02          0          1
14          4  2020-01-03          0          1
15          5  2020-01-01          0          1
16          5  2020-01-02          0          1

非常感谢!现在要尝试运行它。
explode
给了我15%的速度,但看起来慢的一步仍然是通过作业而不是机器来扩展的。你认为有什么办法可以创建每行的日期范围吗?是的,我们可以用不同的方法来创建,并提高速度。然而,我离开我的电脑有几个小时。。。。也许其他人能帮上忙,否则我回到电脑前会再问。写得好的第一个问题,包括输入、预期输出、提问前阅读的内容以及您尝试过的内容!干杯,谢谢你下面的回答!太棒了,谢谢你的回答!抱歉,如果我遗漏了一些明显的内容,那么
dr
在您的代码片段中指的是什么?我知道numpy可以提供一些出色的加速,所以我很高兴尝试一下@帕拉多斯,我的错,它在我笔记本的另一间牢房里。我编辑过这个方法真的很巧妙。这个速度和我在玩具版数据上的方法大致相同(它只包含短日期范围内的工作),但我有一种感觉,当我从事长期工作时,这个方法会很有效。不幸的是,如果我的Docker容器内存不足,我实际上无法在完整集上运行此操作,但我会在安装到更好的计算机上后立即更新。@Parados你是对的,大多数情况下,在小数据样本上,使用pandas方法并不是真正有效的,因为使用它们的开销很大。对于内存pb,您可以尝试对第一个astype使用
astype('int8')
,这可能会节省一些内存,因为默认情况下,astype(int)是int32,在这种情况下,int8就足够了:)难以置信。这种方法将我的运行时间从3小时减少到10分钟。在
astype('int8')
的帮助下,我甚至可以在Netflix运行时观看它。我还选择了在
date\u范围内使用
Freq='MS'
按月分组,这将答案的时间缩短到26秒(而
apply
方法的时间为6分钟)。我对输出结果进行了比较,所有结果都匹配。向你致敬,先生。
    MachineID        Date  TypeBJobs  TypeAJobs
0           1  2020-01-02          1          0
1           1  2020-01-03          2          0
2           1  2020-01-04          2          0
3           1  2020-01-05          1          0
4           2  2020-01-01          1          1
5           2  2020-01-02          1          1
6           2  2020-01-03          1          0
7           2  2020-01-04          1          0
8           3  2020-01-01          0          1
9           3  2020-01-02          0          1
10          3  2020-01-03          0          2
11          3  2020-01-04          0          1
12          4  2020-01-01          0          1
13          4  2020-01-02          0          1
14          4  2020-01-03          0          1
15          5  2020-01-01          0          1
16          5  2020-01-02          0          1