Phi*_*hik 6 python datetime dataframe pandas
我有一个大型数据集,其中每一行代表某个时间间隔(开始和结束之间)的某种类型(想象传感器)的值.它看起来像这样:
start end type value
2015-01-01 2015-01-05 1 3
2015-01-06 2015-01-08 1 2
2015-01-05 2015-01-08 3 3
2015-01-13 2015-01-16 2 1
Run Code Online (Sandbox Code Playgroud)
我想将它变成每日时间索引框架,如下所示:
day type value
2015-01-01 1 3
2015-01-02 1 3
2015-01-03 1 3
2015-01-04 1 3
2015-01-05 1 3
2015-01-06 1 2
2015-01-07 1 2
2015-01-08 1 2
2015-01-05 3 3
2015-01-16 3 3
2015-01-07 3 3
2015-01-08 3 3
2015-01-13 2 1
2015-01-14 2 1
2015-01-15 2 1
2015-01-16 2 1
Run Code Online (Sandbox Code Playgroud)
(请注意,我们不能对间隔做出任何假设:它们应该是连续的且不重叠但我们不能保证)
基于这些Stack Overflow答案[1](日期范围上的DataFrame重新采样)[2](pandas:基于开始/结束日期的聚合),似乎存在两种方法:一种围绕itertuples,一种围绕融合(2使用上面的堆栈)/unstack但它类似于融化).让我们比较它们的性能.
# Creating a big enough dataframe
date_range = pd.date_range(start=dt.datetime(2015,1,1), end=dt.datetime(2019,12,31), freq='4D')
to_concat = []
for val in range(1,50):
frame_tmp = pd.DataFrame()
frame_tmp['start'] = date_range
frame_tmp['end'] = frame_tmp['start']+ dt.timedelta(3)
frame_tmp['type'] = val
frame_tmp['value'] = np.random.randint(1, 6, frame_tmp.shape[0])
to_concat.append(frame_tmp)
df = pd.concat(to_concat, ignore_index=True)
# Method 1
def method_1(df):
df1 = (pd.concat([pd.Series(r.Index,
pd.date_range(r.start,
r.end,
freq='D'))
for r in df.itertuples()])) \
.reset_index()
df1.columns = ['start_2', 'idx']
df2 = df1.set_index('idx').join(df).reset_index(drop=True)
return df2.set_index('start_2')
df_method_1=df.groupby(['type']).apply(method_1)
# Method 2
df_tmp= df.reset_index()
df1 = (df_tmp.melt(df_tmp.columns.difference(['start','end']),
['start', 'end'],
value_name='current_time')
)
df_method_2 = df1.set_index('current_time').groupby('index', group_keys=False)\
.resample('D').ffill()
Run Code Online (Sandbox Code Playgroud)
与%%timeit在Jupyter,方法1采取〜787-8和方法2采取〜25秒为定义为例如数据帧.这太慢了,因为我正在处理的真实数据集比这要大得多.在该数据帧上,方法1需要大约20分钟.
你对如何加快速度有任何想法吗?
这大约比你的快 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']
)
Run Code Online (Sandbox Code Playgroud)
通过创建自己的日期范围而不是调用,您可以获得大约 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']
)
Run Code Online (Sandbox Code Playgroud)
arange或者,使用 numpy 的函数生成日期可以将速度提高 24 倍:
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']
)
Run Code Online (Sandbox Code Playgroud)
我忍不住再添加一个,速度比上一个快两倍多一点。不幸的是,它很难阅读。这根据读数跨越的天数(“dur”)对读数进行分组,然后使用矢量化 numpy 操作在单个批次中扩展每个组。
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)
)
Run Code Online (Sandbox Code Playgroud)
更新:针对您的评论,下面是矢量化方法的简化版本,它更快、更容易阅读。这不是使用groupby步骤,而是使单个矩阵与最长的读数一样宽,然后过滤掉不需要的条目。除非您的读数的最大持续时间比平均值长得多,否则这应该非常有效。使用测试数据帧(所有读数持续 4 天),这比groupby解决方案快约 15 倍,比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']
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
114 次 |
| 最近记录: |