熊猫:将日期范围解压缩到个别日期

时间:2014-06-05 17:45:54

标签: python pandas time-series

数据集:我有1GB的股票数据集,其日期范围之间的值。日期范围没有重叠,数据集按(ticker,start_date)排序。

>>> df.head()
             start_date    end_date                   val    
ticker         
AAPL         2014-05-01  2014-05-01         10.0000000000
AAPL         2014-06-05  2014-06-10         20.0000000000
GOOG         2014-06-01  2014-06-15         50.0000000000
MSFT         2014-06-16  2014-06-16                  None
TWTR         2014-01-17  2014-05-17         10.0000000000

目标:我想解压缩数据框,以便我有个别日期而不是日期范围。例如,AAPL行将从仅2行变为7行:

>>> AAPL_decompressed.head()
                   val
date                       
2014-05-01         10.0000000000
2014-06-05         20.0000000000
2014-06-06         20.0000000000
2014-06-07         20.0000000000
2014-06-08         20.0000000000

我希望有一个很好的优化方法,例如重新采样的pandas可以在几行中完成。

4 个答案:

答案 0 :(得分:7)

多了几行,但我认为这会导致你提出的问题:

从您的数据框开始:

In [70]: df
Out[70]:
       start_date   end_date  val  row
ticker
AAPL   2014-05-01 2014-05-01   10    0
AAPL   2014-06-05 2014-06-10   20    1
GOOG   2014-06-01 2014-06-15   50    2
MSFT   2014-06-16 2014-06-16  NaN    3
TWTR   2014-01-17 2014-05-17   10    4

首先,我将此数据框重新整理为包含一个date列的数据框(因此,对于start_dateend_date的每个日期,每行重复两次(我添加一个名为{的计数器列{1}}):

row

根据这个新的数据框架,我可以按In [60]: df['row'] = range(len(df)) In [61]: starts = df[['start_date', 'val', 'row']].rename(columns={'start_date': 'date'}) In [62]: ends = df[['end_date', 'val', 'row']].rename(columns={'end_date':'date'}) In [63]: df_decomp = pd.concat([starts, ends]) In [64]: df_decomp = df_decomp.set_index('row', append=True) In [65]: df_decomp.sort_index() Out[65]: date val ticker row AAPL 0 2014-05-01 10 0 2014-05-01 10 1 2014-06-05 20 1 2014-06-10 20 GOOG 2 2014-06-01 50 2 2014-06-15 50 MSFT 3 2014-06-16 NaN 3 2014-06-16 NaN TWTR 4 2014-01-17 10 4 2014-05-17 10 ticker对其进行分组,并在每个组row上应用每日resample(使用方法'垫'向前填充)

fillna

最后一个命令是删除现在多余的In [66]: df_decomp = df_decomp.groupby(level=[0,1]).apply(lambda x: x.set_index('date').resample('D').fillna(method='pad')) In [67]: df_decomp = df_decomp.reset_index(level=1, drop=True) 索引级别 当我们访问AAPL行时,它会提供您想要的输出:

row

答案 1 :(得分:1)

我认为你可以分五步完成:

1)过滤自动收报机列以找到您想要的库存

2)使用pandas.bdate_range建立startend之间的日期范围列表

3)使用reduce

展平此列表

4)重新索引新过滤的数据框

5)使用方法pad

填写nans

以下是代码:

>>> import pandas as pd
>>> import datetime

>>> data = [('AAPL', datetime.date(2014, 4, 28), datetime.date(2014, 5, 2), 90),
            ('AAPL', datetime.date(2014, 5, 5), datetime.date(2014, 5, 9), 80),
            ('MSFT', datetime.date(2014, 5, 5), datetime.date(2014, 5, 9), 150),
            ('AAPL', datetime.date(2014, 5, 12), datetime.date(2014, 5, 16), 85)]
>>> df = pd.DataFrame(data=data, columns=['ticker', 'start', 'end', 'val'])

>>> df_new = df[df['ticker'] == 'AAPL']
>>> df_new.name = 'AAPL'
>>> df_new.index = df_new['start']
>>> df_new.index.name = 'date'
>>> df_new.index = df_new.index.to_datetime()

>>> from functools import reduce #for py3k only
>>> new_index = [pd.bdate_range(**d) for d in df_new[['start','end']].to_dict('record')]
>>> new_index_flat = reduce(pd.tseries.index.DatetimeIndex.append, new_index)

>>> df_new = df_new.reindex(new_index_flat)
>>> df_new = df_new.fillna(method='pad')
>>> df_new
               ticker       start         end  val
    2014-04-28   AAPL  2014-04-28  2014-05-02   90
    2014-04-29   AAPL  2014-04-28  2014-05-02   90
    2014-04-30   AAPL  2014-04-28  2014-05-02   90
    2014-05-01   AAPL  2014-04-28  2014-05-02   90
    2014-05-02   AAPL  2014-04-28  2014-05-02   90
    2014-05-05   AAPL  2014-05-05  2014-05-09   80
    2014-05-06   AAPL  2014-05-05  2014-05-09   80
    2014-05-07   AAPL  2014-05-05  2014-05-09   80
    2014-05-08   AAPL  2014-05-05  2014-05-09   80
    2014-05-09   AAPL  2014-05-05  2014-05-09   80
    2014-05-12   AAPL  2014-05-12  2014-05-16   85
    2014-05-13   AAPL  2014-05-12  2014-05-16   85
    2014-05-14   AAPL  2014-05-12  2014-05-16   85
    2014-05-15   AAPL  2014-05-12  2014-05-16   85
    2014-05-16   AAPL  2014-05-12  2014-05-16   85

    [15 rows x 4 columns]

希望它有所帮助!

答案 2 :(得分:0)

这是一种愚蠢的方式 - 我发布这个糟糕的答案(记住 - 我不能代码:-))因为我是大熊猫的新手并且不介意有人改进它。

这将读取最初发布数据的文件 - 然后创建stock_id和end_date的多索引。下面的get_val函数采用整个帧,例如,一个自动收报机。 ' AAPL',以及日期并使用index.searchsorted,其行为类似于C ++中的map :: upper_bound - 即如果要插入,则查找将插入日期的索引 - 即找到最接近的结束日期,但是在有问题的日期之后 - 这将具有我们想要的值,并使用get_val返回该值。

然后我根据&AAPL'的stock_id,使用此Multiindex从系列中获取横截面。然后我们形成一个空列表,用于使用' AAPL'的密钥从多索引中展平日期元组列表。这些日期成为系列的索引和值。然后我将这个系列映射到get_val以获取所需的股票价格。

我知道这可能是错的......但是......乐于学习。

我不会惊讶地发现有一种简单的方法可以像这样使用一些填充插值方法来扩充数据帧......

stocks=pd.read_csv('stocks2.csv', parse_dates=['start_date', 'end_date'], index_col='ticker')
mi=zip(stocks.index, pd.Series(zip(stocks['start_date'],stocks['end_date'].values)).map(lambda z: tuple(pd.date_range(start=z[0], end=z[1]))).values)
mi=pd.MultiIndex.from_tuples(mi)
ticker='AAPL'
s=pd.Series(index=mi,data=0)
s=list(s.xs(key=ticker).index)
l=[]
map(lambda x: l.extend(x), s)
s=pd.Series(index=l,data=l)
stocks_byticker=stocks[stocks.index==ticker].set_index('end_date')
print(s.map(lambda x: stocks_byticker.ix[stocks_byticker.index.searchsorted(x), 'val']))

2014-05-01    10
2014-06-05    20
2014-06-06    20
2014-06-07    20
2014-06-08    20
2014-06-09    20
2014-06-10    20

答案 3 :(得分:0)

这是一种稍微更通用的方法来扩展joris的良好答案,但允许它与任意数量的其他列一起使用:

| id |  car_name |   service_name |  done_date |
|----|-----------|----------------|------------|
|  1 | porsche_1 |     oil_change | 2017-01-04 |
|  2 | porsche_1 |     oil_change | 2017-04-15 |
|  3 | porsche_1 |     oil_change | 2017-07-12 |
|  4 |    benz_1 |     oil_change | 2017-01-14 |
|  5 |    benz_1 |     oil_change | 2017-05-24 |
|  6 | porsche_1 |     tire_check | 2017-01-04 |
|  7 | porsche_1 |     tire_check | 2017-06-11 |
|  8 | porsche_1 | replace_wipers | 2017-01-04 |
|  9 | porsche_2 |     oil_change | 2017-05-01 |
| 10 | porsche_2 |     oil_change | 2017-08-20 |