Скользящее количество окон за интервал дат в пандах
У меня есть история проектов и связанные с ними запланированные времена начала и окончания:
id planned_start planned_end
1 2017-09-12 2017-09-13
2 2017-09-12 2017-09-14
3 2017-09-12 2017-09-13
4 2017-09-13 2017-09-13
5 2017-09-12 2017-09-12
6 2017-09-12 2017-09-20
7 2017-09-14 2017-09-15
8 2017-09-14 2017-09-20
Я хочу посчитать количество одновременных проектов для каждой из дат начала вышеуказанных проектов. Вот моя логика для этого:
for project_id in df['id']:
start_date = df[df['id'] == project_id]['planned_start'].values[0]
concurrent_projects = df[(df['planned_start'] <= start_date) & (df['planned_end'] >= start_date)]
df.ix[df['id'] == project_id, 'concurrent_projects'] = concurrent_projects.shape[0]
Который производит это:
id planned_start planned_end concurrent_projects
0 1 2017-09-12 2017-09-13 5.0
1 2 2017-09-12 2017-09-14 5.0
2 3 2017-09-12 2017-09-13 5.0
3 4 2017-09-13 2017-09-13 5.0
4 5 2017-09-12 2017-09-12 5.0
5 6 2017-09-12 2017-09-20 5.0
6 7 2017-09-14 2017-09-15 4.0
7 8 2017-09-14 2017-09-20 4.0
Тем не менее, я знаю, насколько неоптимальным, с точки зрения времени, выше for
петля есть. На самом деле у меня есть более 500000 проектов, для которых мне нужно сделать эту математику. Может кто-нибудь дать совет, как это ускорить? Я знаю, что должно быть решение для чистых панд или даже тупица, которое убило бы то, что я получил выше.
3 ответа
Векторизованный способ... но взорвет память. Все еще работаем над лучшим векторизованным способом. У меня есть концепция, я просто работаю над деталями, пока собираю ужин.
s = df.planned_start.values
e = df.planned_end.values
s_ = s >= s[:, None]
e_ = s <= e[:, None]
df.assign(concurrent_projects=(e_ & s_).sum(0))
id planned_start planned_end concurrent_projects
0 1 2017-09-12 2017-09-13 5
1 2 2017-09-12 2017-09-14 5
2 3 2017-09-12 2017-09-13 5
3 4 2017-09-13 2017-09-13 5
4 5 2017-09-12 2017-09-12 5
5 6 2017-09-12 2017-09-20 5
6 7 2017-09-14 2017-09-15 4
7 8 2017-09-14 2017-09-20 4
Мои извинения, у меня нет времени объяснять. Но я не хотел оставлять тебя в покое
k = len(df)
d = np.column_stack([df.planned_start.values, df.planned_end.values + 1]).ravel()
i = np.tile([1, -1], k)
a = d.argsort()
f = np.arange(k).repeat(2)
r = np.zeros(k, int)
z = np.zeros(k, int)
m = np.zeros(k, int)
cumsum = 0
for j in range(f.size):
x = f[a[j]]
y = i[a[j]]
r[x] = cumsum
z[x] = (y + 1) // 2
r += y * z
m = np.column_stack([m, r]).max(1)
cumsum += y
m
array([5, 5, 5, 5, 5, 5, 4, 4])
Это мое решение, используя crosstab
в основном использует martix
из четных, чтобы сделать расчет (вход Dataframe
df2
):
df=pd.crosstab(df2.planned_end,df2.planned_start,margins=True)
df=pd.concat([df,pd.DataFrame(columns=list(set(df.index)- set(df.columns)))]).fillna(0)
df2['concurrent_projects']=df2.planned_start.map(df.loc['All',:].cumsum()-df.All.cumsum().shift().fillna(0))
df2
Out[112]:
id planned_start planned_end concurrent_projects
0 1 2017-09-12 2017-09-13 5.0
1 2 2017-09-12 2017-09-14 5.0
2 3 2017-09-12 2017-09-13 5.0
3 4 2017-09-13 2017-09-13 5.0
4 5 2017-09-12 2017-09-12 5.0
5 6 2017-09-12 2017-09-20 5.0
6 7 2017-09-14 2017-09-15 4.0
7 8 2017-09-14 2017-09-20 4.0
С помощью apply
дает примерно 3-кратное ускорение.
Текущий подход:
%%timeit
def concurrent_count_using_loop():
for project_id in df['id']:
start_date = df[df['id'] == project_id]['planned_start'].values[0]
concurrent_projects = df[(df['planned_start'] <= start_date) & (df['planned_end'] >= start_date)]
df.ix[df['id'] == project_id, 'concurrent_projects'] = concurrent_projects.shape[0]
concurrent_count_using_loop()
# 10 loops, best of 3: 21.4 ms per loop
С apply()
:
%%timeit
def concurrent_count(project):
valid_start = df.planned_start <= project["planned_start"]
valid_end = df.planned_end >= project["planned_start"]
return (valid_start & valid_end).sum()
df["concurrent_projects"] = df.apply(concurrent_count, axis=1)
# 100 loops, best of 3: 6.94 ms per loop