合并多个具有非唯一索引的数据框
我有一堆 pandas 时间序列。这里有一个例子来说明情况(真实数据每个序列大约有 100 万条记录):
>>> for s in series:
print s.head()
print
2014-01-01 01:00:00 -0.546404
2014-01-01 01:00:00 -0.791217
2014-01-01 01:00:01 0.117944
2014-01-01 01:00:01 -1.033161
2014-01-01 01:00:02 0.013415
2014-01-01 01:00:02 0.368853
2014-01-01 01:00:02 0.380515
2014-01-01 01:00:02 0.976505
2014-01-01 01:00:02 0.881654
dtype: float64
2014-01-01 01:00:00 -0.111314
2014-01-01 01:00:01 0.792093
2014-01-01 01:00:01 -1.367650
2014-01-01 01:00:02 -0.469194
2014-01-01 01:00:02 0.569606
2014-01-01 01:00:02 -1.777805
dtype: float64
2014-01-01 01:00:00 -0.108123
2014-01-01 01:00:00 -1.518526
2014-01-01 01:00:00 -1.395465
2014-01-01 01:00:01 0.045677
2014-01-01 01:00:01 1.614789
2014-01-01 01:00:01 1.141460
2014-01-01 01:00:02 1.365290
dtype: float64
每个序列中的时间并不是唯一的。例如,最后一个序列在 2014-01-01 01:00:00
这个时间点有 3 个值。第二个序列在这个时间点只有一个值。另外,并不是所有的时间在所有序列中都需要出现。
我的目标是创建一个合并后的 DataFrame
,其中的时间是所有单独时间序列中时间的并集。每个时间戳应该根据需要重复出现。所以,如果一个时间戳在上面的序列中出现了 (2, 0, 3, 4)
次,那么在最终的 DataFrame
中,这个时间戳应该重复 4 次(出现次数的最大值)。每一列的值应该是“向前填充”的。
例如,合并上述序列的结果应该是:
c0 c1 c2
2014-01-01 01:00:00 -0.546404 -0.111314 -0.108123
2014-01-01 01:00:00 -0.791217 -0.111314 -1.518526
2014-01-01 01:00:00 -0.791217 -0.111314 -1.395465
2014-01-01 01:00:01 0.117944 0.792093 0.045677
2014-01-01 01:00:01 -1.033161 -1.367650 1.614789
2014-01-01 01:00:01 -1.033161 -1.367650 1.141460
2014-01-01 01:00:02 0.013415 -0.469194 1.365290
2014-01-01 01:00:02 0.368853 0.569606 1.365290
2014-01-01 01:00:02 0.380515 -1.777805 1.365290
2014-01-01 01:00:02 0.976505 -1.777805 1.365290
2014-01-01 01:00:02 0.881654 -1.777805 1.365290
为了让你了解我真实数据的大小和“唯一性”:
>>> [len(s.index.unique()) for s in series]
[48617, 48635, 48720, 48620]
>>> len(times)
51043
>>> [len(s) for s in series]
[1143409, 1143758, 1233646, 1242864]
这是我尝试过的:
我可以创建一个所有唯一时间的并集:
uniques = [s.index.unique() for s in series]
times = uniques[0].union_many(uniques[1:])
现在我可以用 times
来索引每个序列:
series[0].loc[times]
但这样似乎会对 times
中的每个项目重复值,这并不是我想要的。
我不能用 times
来重新索引这些序列,因为每个序列的索引并不唯一。
我可以通过一个慢速的 Python 循环来实现,或者用 Cython,但有没有一种“仅用 pandas”的方法来实现我想做的事情呢?
我用以下代码创建了我的示例序列:
def make_series(n=3, rep=(0,5)):
times = pandas.date_range('2014/01/01 01:00:00', periods=n, freq='S')
reps = [random.randint(*rep) for _ in xrange(n)]
dates = []
values = numpy.random.randn(numpy.sum(reps))
for date, rep in zip(times, reps):
dates.extend([date]*rep)
return pandas.Series(data=values, index=dates)
series = [make_series() for _ in xrange(3)]
2 个回答
这样做怎么样呢 - 先把数据转换成带有标签的表格(dataframe),然后再用concat()合并。
s1 = pd.Series(index=['4/4/14','4/4/14','4/5/14'],
data=[12.2,0.0,12.2])
s2 = pd.Series(index=['4/5/14','4/8/14'],
data=[14.2,3.0])
d1 = pd.DataFrame(a,columns=['a'])
d2 = pd.DataFrame(b,columns=['b'])
final_df = pd.merge(d1, d2, left_index=True, right_index=True, how='outer')
这样我得到了
a b
4/4/14 12.2 NaN
4/4/14 0.0 NaN
4/5/14 12.2 14.2
4/8/14 NaN 3.0
这几乎是一个连接操作:
In [11]: s0 = pd.Series([1, 2, 3], name='s0')
In [12]: s1 = pd.Series([1, 4, 5], name='s1')
In [13]: pd.concat([s0, s1], axis=1)
Out[13]:
s0 s1
0 1 1
1 2 4
2 3 5
不过,连接操作无法处理重复的索引(因为不清楚它们该怎么合并,而在你的情况下,你不想以“普通”的方式合并它们——也就是组合的方式)...
我觉得你需要使用分组操作:
In [21]: s0 = pd.Series([1, 2, 3], [0, 0, 1], name='s0')
In [22]: s1 = pd.Series([1, 4, 5], [0, 1, 1], name='s1')
注意:我添加了一种更快的方法,适用于类似整数的类型(比如 datetime64)。
我们想要为每个项目添加一个多级索引的 累积计数,这样我们就能让索引变得唯一:
In [23]: s0.groupby(level=0).cumcount()
Out[23]:
0 0
0 1
1 0
dtype: int64
注意:我似乎不能在没有数据框的情况下将列添加到索引中...
In [24]: df0 = pd.DataFrame(s0).set_index(s0.groupby(level=0).cumcount(), append=True)
In [25]: df1 = pd.DataFrame(s1).set_index(s1.groupby(level=0).cumcount(), append=True)
In [26]: df0
Out[26]:
s0
0 0 1
1 2
1 0 3
现在我们可以继续进行连接操作:
In [27]: res = pd.concat([df0, df1], axis=1)
In [28]: res
Out[28]:
s0 s1
0 0 1 1
1 2 NaN
1 0 3 4
1 NaN 5
如果你想去掉累积计数这一层:
In [29]: res.index = res.index.droplevel(1)
In [30]: res
Out[30]:
s0 s1
0 1 1
0 2 NaN
1 3 4
1 NaN 5
现在你可以用前向填充来得到想要的结果...(如果你担心不同时间的前向填充,可以先按索引分组再进行前向填充)。
如果每组的重复次数上限合理(我这里选择1000,但更高的数字仍然是“合理”的!),你可以使用 Float64Index,如下所示(这样看起来更优雅):
s0.index = s0.index + (s0.groupby(level=0)._cumcount_array() / 1000.)
s1.index = s1.index + (s1.groupby(level=0)._cumcount_array() / 1000.)
res = pd.concat([s0, s1], axis=1)
res.index = res.index.values.astype('int64')
注意:我这里调皮地使用了一个私有方法,它返回累积计数作为一个 numpy 数组...
注意2:这是 pandas 0.14 版本,在 0.13 版本中,你必须传递一个 numpy 数组给 _cumcount_array
,例如 np.arange(len(s0))
),在 0.13 之前的版本就没办法了——没有累积计数。