Python pandas 时间序列与层次索引及滚动/移位

1 投票
1 回答
1391 浏览
提问于 2025-04-18 01:34

我在理解pandas中的滚动和移动的概念时遇到了困难。这个论坛里有很多好的建议,但我在我的情况中应用这些建议时失败得很惨。

现在我只能用传统的方法遍历时间序列,但这真是太慢了,处理150,000行数据花了我大约8个小时,这大约是3天的数据量。现在我还有2个月的数据要处理,估计等我从休假回来时都还没处理完,更别提如果停电了我就得重新开始,而这次我可没有休假可以等。

我有以下15分钟的股票价格时间序列(在日期时间(时间戳)和股票代码上有层级索引,唯一的原始列是收盘价):

                                     closePrice
    datetime               ticker
    2014-02-04 09:15:00    AAPL      xxx
                           EQIX      xxx
                           FB        xxx
                           GOOG      xxx
                           MSFT      xxx
    2014-02-04 09:30:00    AAPL      xxx
                           EQIX      xxx
                           FB        xxx
                           GOOG      xxx
                           MSFT      xxx
    2014-02-04 09:45:00    AAPL      xxx
                           EQIX      xxx
                           FB        xxx
                           GOOG      xxx
                           MSFT      xxx

我需要添加两列:

  1. 12sma,12天的移动平均。经过几个小时的搜索,最好的建议是使用rolling_mean,所以我试了一下。但由于我的时间序列结构,它并没有成功。也就是说,它是从上到下计算的,第一个移动平均是基于前12行计算的,而不考虑不同股票代码的值。我该如何让它根据索引来计算,也就是先按日期时间,然后按股票代码,这样我就能得到比如AAPL的移动平均?目前它的计算方式是(AAPL+EQIX+FB+GOOG+MSFT+AAPL...直到第12行)/ 12。
  2. 一旦我得到了12sma列,我还需要12ema列,也就是12天的指数移动平均。计算时,每个股票的时间序列中的第一个值会直接复制同一行的12sma值。之后,我需要同一行的收盘价和上一行的12ema值,也就是过去15分钟的值。我做了很长时间的研究,似乎解决方案是将滚动和移动结合起来,但我不知道该如何把它们组合在一起。

如果有人能帮我,我会非常感激。

谢谢。

编辑:

感谢Jeff的建议,在交换和排序索引层级后,我能够用rolling_mean()正确计算12sma,并且努力在同一时间戳插入了从12sma复制的第一个12ema值:

                                 close  12sma  12ema
    sec_code datetime
    AAPL     2014-02-05 11:45:00 113.0  NaN    NaN
             2014-02-05 12:00:00 113.2  NaN    NaN
             2014-02-05 13:15:00 112.9  NaN    NaN
             2014-02-05 13:30:00 113.2  NaN    NaN
             2014-02-05 13:45:00 113.0  NaN    NaN
             2014-02-05 14:00:00 113.1  NaN    NaN
             2014-02-05 14:15:00 113.3  NaN    NaN
             2014-02-05 14:30:00 113.3  NaN    NaN
             2014-02-05 14:45:00 113.3  NaN    NaN
             2014-02-05 15:00:00 113.2  NaN    NaN
             2014-02-05 15:15:00 113.2  NaN    NaN
             2014-02-05 15:30:00 113.3  113.16 113.16
             2014-02-05 15:45:00 113.3  113.19 NaN
             2014-02-05 16:00:00 113.2  113.19 NaN
             2014-02-06 09:45:00 112.6  113.16 NaN
             2014-02-06 10:00:00 113.5  113.19 NaN
             2014-02-06 10:15:00 113.8  113.25 NaN
             2014-02-06 10:30:00 113.5  113.29 NaN
             2014-02-06 10:45:00 113.7  113.32 NaN
             2014-02-06 11:00:00 113.5  113.34 Nan

我知道pandas有pandas.stats.moments.ewma,但我更喜欢使用一本书中的公式,它需要“此刻”的收盘价和上一行的12ema。

所以,我尝试从2月5日15:45开始填充12ema列。我尝试使用apply()和一个函数,但shift给我报了错:

    def f12ema(x):
        K = 2 / (12 + 1)
        return x['price_nom'] * K + x['12ema'].shift(-1) * (1-K)

    df1.apply(f12ema, axis=1)

    AttributeError: ("'numpy.float64' object has no attribute 'shift'", u'occurred at index 2014-02-05 11:45:00')

我想到的另一个可能性是rolling_apply(),但这超出了我的知识范围。

1 个回答

1

创建一个包含你想要的时间范围的日期

In [47]: rng = date_range('20130102 09:30:00','20130105 16:00:00',freq='15T')

In [48]: rng = rng.take(rng.indexer_between_time('09:30:00','16:00:00'))

In [49]: rng
Out[49]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-01-02 09:30:00, ..., 2013-01-05 16:00:00]
Length: 108, Freq: None, Timezone: None

创建一个和你的数据结构相似的框架(2000个股票代码 x 日期)

In [50]: df = DataFrame(np.random.randn(len(rng)*2000,1),columns=['close'],index=MultiIndex.from_product([rng,range(2000)],names=['date','ticker']))

重新排列数据,使其变成股票代码 x 日期的格式,然后对它进行排序!!!!

In [51]: df = df.swaplevel('ticker','date').sortlevel()

In [52]: df
Out[52]: 
                               close
ticker date                         
0      2013-01-02 09:30:00  0.840767
       2013-01-02 09:45:00  1.808101
       2013-01-02 10:00:00 -0.718354
       2013-01-02 10:15:00 -0.484502
       2013-01-02 10:30:00  0.563363
       2013-01-02 10:45:00  0.553920
       2013-01-02 11:00:00  1.266992
       2013-01-02 11:15:00 -0.641117
       2013-01-02 11:30:00 -0.574673
       2013-01-02 11:45:00  0.861825
       2013-01-02 12:00:00 -1.562111
       2013-01-02 12:15:00 -0.072966
       2013-01-02 12:30:00  0.673079
       2013-01-02 12:45:00  0.766105
       2013-01-02 13:00:00  0.086202
       2013-01-02 13:15:00  0.949205
       2013-01-02 13:30:00 -0.381194
       2013-01-02 13:45:00  0.316813
       2013-01-02 14:00:00 -0.620176
       2013-01-02 14:15:00 -0.193126
       2013-01-02 14:30:00 -1.552111
       2013-01-02 14:45:00  1.724429
       2013-01-02 15:00:00 -0.092393
       2013-01-02 15:15:00  0.197763
       2013-01-02 15:30:00  0.064541
       2013-01-02 15:45:00 -1.574853
       2013-01-02 16:00:00 -1.023979
       2013-01-03 09:30:00 -0.079349
       2013-01-03 09:45:00 -0.749285
       2013-01-03 10:00:00  0.652721
       2013-01-03 10:15:00 -0.818152
       2013-01-03 10:30:00  0.674068
       2013-01-03 10:45:00  2.302714
       2013-01-03 11:00:00  0.614686

                                 ...

[216000 rows x 1 columns]

按股票代码分组。为每个股票代码返回一个数据框,这个数据框是应用了滚动平均和指数加权移动平均的结果。注意,这里有很多选项可以控制,比如窗口大小,你可以设置不跨天等等。

In [53]: df.groupby(level='ticker')['close'].apply(lambda x: concat({ 'spma' : pd.rolling_mean(x,3), 'ema' : pd.ewma(x,3) }, axis=1))
Out[53]: 
                                 ema      spma
ticker date                                   
0      2013-01-02 09:30:00  0.840767       NaN
       2013-01-02 09:45:00  1.393529       NaN
       2013-01-02 10:00:00  0.480282  0.643504
       2013-01-02 10:15:00  0.127447  0.201748
       2013-01-02 10:30:00  0.270334 -0.213164
       2013-01-02 10:45:00  0.356580  0.210927
       2013-01-02 11:00:00  0.619245  0.794758
       2013-01-02 11:15:00  0.269100  0.393265
       2013-01-02 11:30:00  0.041032  0.017067
       2013-01-02 11:45:00  0.258476 -0.117988
       2013-01-02 12:00:00 -0.216742 -0.424986
       2013-01-02 12:15:00 -0.179622 -0.257750
       2013-01-02 12:30:00  0.038741 -0.320666
       2013-01-02 12:45:00  0.223881  0.455406
       2013-01-02 13:00:00  0.188995  0.508462
       2013-01-02 13:15:00  0.380972  0.600504
       2013-01-02 13:30:00  0.188987  0.218071
       2013-01-02 13:45:00  0.221125  0.294942
       2013-01-02 14:00:00  0.009907 -0.228185
       2013-01-02 14:15:00 -0.041013 -0.165496
       2013-01-02 14:30:00 -0.419688 -0.788471
       2013-01-02 14:45:00  0.117299 -0.006936

       2013-01-04 10:00:00 -0.060415  0.341013
       2013-01-04 10:15:00  0.074068  0.604611
       2013-01-04 10:30:00 -0.108502  0.440256
       2013-01-04 10:45:00 -0.514229 -0.636702
                                 ...       ...

[216000 rows x 2 columns]

性能相当不错,因为基本上是在遍历这些股票代码。

In [54]: %timeit df.groupby(level='ticker')['close'].apply(lambda x: concat({ 'spma' : pd.rolling_mean(x,3), 'ema' : pd.ewma(x,3) }, axis=1))
1 loops, best of 3: 2.1 s per loop

撰写回答