Это примерно в 1,7 раза быстрее, чем ваш method_1
, и немного меньше:
df_expand = pd.DataFrame.from_records(
(
(d, r.type, r.value)
for r in df.itertuples()
for d in pd.date_range(start=r.start, end=r.end, freq='D')
),
columns=['day', 'type', 'row']
)
Вы можете получить примерно в 7 раз быстрее, создав собственный диапазон дат вместо вызова pd.date_range()
:
one_day = dt.timedelta(1)
df_expand = pd.DataFrame.from_records(
(
(r.start + i * one_day, r.type, r.value)
for r in df.itertuples()
for i in range(int((r.end-r.start)/one_day)+1)
),
columns=['day', 'type', 'row']
)
Или вы можете получить до 24 раз быстрее, используя функцию arange
numpy для генерации дат:
one_day = dt.timedelta(1)
df_expand = pd.DataFrame.from_records(
(
(d, r.type, r.value)
for r in df.itertuples()
for d in np.arange(r.start.date(), r.end.date()+one_day, dtype='datetime64[D]')
),
columns=['day', 'type', 'row']
)
Я не удержался, добавив еще один, который немного большечем в два раза быстрее, чем последний.К сожалению, это намного сложнее читать.Это группирует показания в зависимости от того, сколько дней они охватывают ('dur'), а затем использует векторизованные операции с числами, чтобы развернуть каждую группу в одном пакете.
def expand_group(g):
dur = g.dur.iloc[0] # how many days for each reading in this group?
return pd.DataFrame({
'day': (g.start.values[:,None] + np.timedelta64(1, 'D') * np.arange(dur)).ravel(),
'type': np.repeat(g.type.values, dur),
'value': np.repeat(g.value.values, dur),
})
# take all readings with the same duration and process them together using vectorized code
df_expand = (
df.assign(dur=(df['end']-df['start']).dt.days + 1)
.groupby('dur').apply(expand_group)
.reset_index('dur', drop=True)
)
Обновление: Ответ наВаш комментарий ниже представляет собой упрощенную версию векторизованного подхода, который быстрее и проще для чтения.Вместо использования шага groupby
это делает одну матрицу шириной самого длинного чтения, а затем отфильтровывает ненужные записи.Это должно быть довольно эффективным, если максимальная продолжительность для ваших показаний не намного больше, чем в среднем.С тестовым фреймом данных (все показания продолжительностью 4 дня) это примерно в 15 раз быстрее, чем решение groupby
, и примерно в 700 раз быстрее, чем method_1
.
dur = (df['end']-df['start']).max().days + 1
df_expand = pd.DataFrame({
'day': (
df['start'].values[:,None] + np.timedelta64(1, 'D') * np.arange(dur)
).ravel(),
'type': np.repeat(df['type'].values, dur),
'value': np.repeat(df['value'].values, dur),
'end': np.repeat(df['end'].values, dur),
})
df_expand = df_expand.loc[df_expand['day']<=df_expand['end'], 'day':'value']