Pandas数据框中区间的高级切片
我需要从一个以120分钟为间隔的数据表中切出几个时间段。每个想要的时间段的开始时间是由另一个没有特定频率的数据表提供的。我的想法是,拿每个开始时间,加上一个时间和若干个时间段,来构建每个时间段。这个时间和时间段的数量在所有时间段中都是一样的。
我们通过一个例子来看看这个问题。
假设我们的开始时间是'18:00:00',而时间段的数量是3。要切割的数据表是df1,而包含开始时间的数据表是df2。
df1
A B
DateTime
2005-09-06 16:00:00 1 5
2005-09-06 18:00:00 2 6
2005-09-06 20:00:00 3 7
2005-09-06 22:00:00 4 8
2005-12-07 16:00:00 9 8
2005-12-07 18:00:00 7 6
2005-12-07 20:00:00 5 4
2005-12-07 22:00:00 3 2
<class 'pandas.tseries.index.DatetimeIndex'>
[2005-09-06 16:00:00, ..., 2005-12-07 22:00:00]
Length: 8, Freq: 120T, Timezone: None
df2
Num
DateTime
2005-09-07 1
2005-12-07 2
<class 'pandas.tseries.index.DatetimeIndex'>
[2005-09-07, 2005-12-07]
Length: 2, Freq: None, Timezone: None
期望的输出结果:
df3 = func(source=df1['B'], start_dates=df2.index, time_start='18:00:00', periods=3)
1 2
18:00:00 6 6
20:00:00 7 4
22:00:00 8 2
我做了什么以及需要考虑的事项:
其中一个难点是,df1中的数据是以120分钟为间隔,但只包含工作日。考虑到这一点,我会这样做:
start = df2.index[0] ## And somehow add to this formula the fact that we want to start at
'18:00'
df3 = df1['B'][(df1.index > start) & (df1.index < start + 3)] ## Somehow iterate this over the
dates in the df2 index
我很感激任何建议。
提前谢谢你。
2 个回答
2
你需要使用pivot,不过前提是你得先提取出你感兴趣的行。
对于那些在同一天的行,可以使用normalize
和isin
:
In [11]: res = df.loc[df.index.normalize().isin(df2.index), 'B']
In [12]: res
Out[12]:
2005-09-06 16:00:00 5
2005-09-06 18:00:00 6
2005-09-06 20:00:00 7
2005-09-06 22:00:00 8
2005-12-07 16:00:00 8
2005-12-07 18:00:00 6
2005-12-07 20:00:00 4
2005-12-07 22:00:00 2
Name: B, dtype: int64
一旦数据整理成这种形式,就可以进行透视(如果可能会有缺失数据,你可能需要使用pivot_table
,这个更灵活一些)!
In [14]: pd.pivot(res.index.time, res.index.normalize(), res.values)
Out[14]:
2005-09-06 2005-12-07
16:00:00 5 8
18:00:00 6 6
20:00:00 7 4
22:00:00 8 2
这里的关键是使用isin来选择行,检查时间是否在df2.index中,并且时间要归一化到午夜。
df.index.normalize().isin(df2.index)
如果我们还关心具体的时间,可以使用indexer_between_time
:
In [15]: df.ix[df.index.indexer_between_time('18:00', '00:00'), 'B']
Out[15]:
2005-09-06 18:00:00 6
2005-09-06 20:00:00 7
2005-09-06 22:00:00 8
2005-12-07 18:00:00 6
2005-12-07 20:00:00 4
2005-12-07 22:00:00 2
Name: B, dtype: int64
好的,在这个例子中,这两个条件是一样的(因为只有我们想要的日期!),但一般来说,你确实需要同时满足这两个条件(也就是要“与”它们结合起来)……
# I had tried to make this a one-liner but utterly failed!
in_time = np.zeros(len(df), dtype=bool)
in_time[df.index.indexer_between_time('18:00', '00:00')] = True
res = df.loc[df.index.normalize().isin(df2.index) & in_time, 'B']
In [17]: res
Out[17]:
2005-09-06 16:00:00 5
2005-09-06 18:00:00 6
2005-09-06 20:00:00 7
2005-09-06 22:00:00 8
2005-12-07 16:00:00 8
2005-12-07 18:00:00 6
Name: B, dtype: int64
你可以对透视结果的列进行映射:
In [21]: pv = pd.pivot(res.index.time, res.index.normalize(), res.values)
In [22]: pv
Out[22]:
2005-09-06 2005-12-07
18:00:00 6 6
20:00:00 7 4
22:00:00 8 2
In [23]: pv.columns = pv.columns.map(df2.Num.get)
In [24]: pv
Out[24]:
1 2
18:00:00 6 6
20:00:00 7 4
22:00:00 8 2
瞧。
1
一种完全不同的方法:
def next_n_asof(x, t, n):
"""The next n rows after time t in x
"""
i = np.argmax(df.index >= t)
return x[i:i + n]
In [11]: next_n_asof(df.B, pd.Timestamp('2005-09-06 18:00:00'), 3)
Out[11]:
2005-09-06 18:00:00 6
2005-09-06 20:00:00 7
2005-09-06 22:00:00 8
Name: B, dtype: int64
我们可以在索引中为每一天使用这个方法:
In [12]: pd.concat(next_n_asof(df.B, t, 3)
for t in df2.index + pd.tseries.timedeltas.to_timedelta(18, unit='h'))
Out[12]:
2005-09-06 18:00:00 6
2005-09-06 20:00:00 7
2005-09-06 22:00:00 8
2005-12-07 18:00:00 6
2005-12-07 20:00:00 4
2005-12-07 22:00:00 2
Name: B, dtype: int64
我们需要在df2.index中的日期上添加时间:
In [13]: df2.index + pd.tseries.timedeltas.to_timedelta(18, unit='h')
Out[13]:
<class 'pandas.tseries.index.DatetimeIndex'>
[2005-09-06 18:00:00, 2005-12-07 18:00:00]
Length: 2, Freq: None, Timezone: None
注意:我没能让这个方法在asof中顺利工作……这可能会更有效。