合并多个具有非唯一索引的数据框。

3

我有一堆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 对系列进行 reindex() ,因为每个系列的索引不唯一。

我可以通过缓慢的 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个回答

3

这几乎是一个连接操作:

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

然而,concat函数无法处理重复的索引(它们合并起来会有歧义,并且在你的情况下,你不想以“普通”的方式合并它们 - 即组合)...

我认为你需要使用groupby函数:

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的int类型。

我们想要为每个项目添加一个cumcounts的多级索引,这样我们就可以欺骗索引变成唯一的:

In [23]: s0.groupby(level=0).cumcount()
Out[23]:
0    0
0    1
1    0
dtype: int64

注意:我似乎无法将列附加到索引而不成为DataFrame。
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

现在你可以使用ffill来获得所需的结果...(如果你担心不同日期时间的向前填充,可以通过对索引进行分组和填充来解决)。
如果每个组中重复次数的上限是合理的(我选择了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之前你就没办法了 - 没有累计计数。


我认为昂贵的部分是重置索引...如果每个组中的数字有一个上限,您可以将(cumcounts / X)添加到索引中,这可能会更快。实际上,这可能更容易。 - Andy Hayden
@Alok-- 请查看附加的解决方案,它也更加简单和快速。 - Andy Hayden
@Alok 注意:在你的示例中,你需要将此包装为view/astype int,并在之后使用DatetimeIndex。 - Andy Hayden
我正在使用0.13.1版本,该方法存在但需要一个必需的“arr”参数。这是回溯信息:TypeError: _cumcount_array() takes exactly 2 arguments (1 given)。我需要执行.grouper._max_groupsize, dtype='int64'),然后它似乎可以工作了。现在正在尝试在真实数据上运行。 - Alok--
@Alok-- 好的,我猜我在0.14版本中使传递输出数组成为可选项,因此传递诸如np.arange(len(s0))之类的东西... - Andy Hayden
显示剩余3条评论

1
如何这样做-首先将数据转换为带有标记列的数据框,然后使用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

我不相信这样做可以实现我的需求。它似乎是将单独的序列顺序连接起来,而我想要进行合并。请查看我编辑后的帖子以获取更好的示例。 - Alok--
因此,在您编辑的示例中,“final_df”将有4行,其中“4/5/14”的值为“12.2”和“14.2”。而“4/8/14”的值将是“12.2”和“3.0”(“向前填充”)。 - Alok--
谢谢!就快完成了。在你的例子中,如果d1d2都有两个值对应于4/5/14,我希望final_df也有这些日期的两个值(就像Python中的zip()函数一样)。上述代码将会给我2*2=4行结果。例如,请参考我问题中2014-01-01 01:00:01的数值。 - Alok--

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接