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