DataFrame每隔3行进行一次前向填充

时间:2016-05-05 21:51:25

标签: python numpy pandas quantitative-finance

我的DataFrame在索引中有'Date''Id',在列中有'Portfolio'。值是投资组合中的安全权重。在索引的日期级别内,我想将每个第3个日期和前方的安全权重填入下一个“每三个”日期之后的日期。

设置

这是一个通用的DataFrame制作人。 df在最后签名。

import pandas as pd
import numpy as np
from string import uppercase

def generic_portfolio_df(start, end, freq, num_port, num_sec, seed=314):
    np.random.seed(seed)
    portfolios = pd.Index(['Portfolio {}'.format(i) for i in uppercase[:num_port]],
                          name='Portfolio')
    securities = ['s{:02d}'.format(i) for i in range(num_sec)]
    dates = pd.date_range(start, end, freq=freq)
    return pd.DataFrame(np.random.rand(len(dates) * num_sec, num_port),
                        index=pd.MultiIndex.from_product([dates, securities],
                                                         names=['Date', 'Id']),
                        columns=portfolios
                       ).groupby(level=0).apply(lambda x: x / x.sum())    

df = generic_portfolio_df('2014-12-31', '2015-05-30', 'BM', 3, 5)

df看起来像这样:

Portfolio       Portfolio A  Portfolio B  Portfolio C
Date       Id                                        
2014-12-31 s00     0.326164     0.201597     0.085340
           s01     0.278614     0.314448     0.266392
           s02     0.258958     0.089224     0.293570
           s03     0.092760     0.262511     0.084208
           s04     0.043503     0.132221     0.270490
2015-01-30 s00     0.094124     0.041722     0.248013
           s01     0.197860     0.346862     0.265287
           s02     0.232504     0.261939     0.125719
           s03     0.193050     0.286359     0.337316
           s04     0.282462     0.063118     0.023664
2015-02-27 s00     0.266900     0.484163     0.074970
           s01     0.239319     0.083138     0.123289
           s02     0.067958     0.262626     0.262548
           s03     0.181974     0.108668     0.301149
           s04     0.243849     0.061405     0.238044
2015-03-31 s00     0.321438     0.149010     0.125168
           s01     0.217779     0.067209     0.040285
           s02     0.173066     0.293539     0.417372
           s03     0.048929     0.415637     0.216490
           s04     0.238788     0.074605     0.200685
2015-04-30 s00     0.089122     0.135514     0.234565
           s01     0.048235     0.028141     0.327739
           s02     0.026016     0.039664     0.073588
           s03     0.413139     0.397875     0.323671
           s04     0.423487     0.398807     0.040437
2015-05-29 s00     0.135831     0.071604     0.235099
           s01     0.240086     0.242436     0.131698
           s02     0.304451     0.380368     0.101653
           s03     0.213468     0.035276     0.372894
           s04     0.106164     0.270317     0.158656

问题

  

在索引的日期级别内,我想将每个第3个日期转发并将安全权重填入下一个“每三个”日期之后的日期。

我希望它看起来像:

Portfolio       Portfolio A  Portfolio B  Portfolio C
Date       Id                                        
2014-12-31 s00     0.326164     0.201597     0.085340
           s01     0.278614     0.314448     0.266392
           s02     0.258958     0.089224     0.293570
           s03     0.092760     0.262511     0.084208
           s04     0.043503     0.132221     0.270490
2015-01-30 s00     0.326164     0.201597     0.085340
           s01     0.278614     0.314448     0.266392
           s02     0.258958     0.089224     0.293570
           s03     0.092760     0.262511     0.084208
           s04     0.043503     0.132221     0.270490
2015-02-27 s00     0.326164     0.201597     0.085340
           s01     0.278614     0.314448     0.266392
           s02     0.258958     0.089224     0.293570
           s03     0.092760     0.262511     0.084208
           s04     0.043503     0.132221     0.270490
2015-03-31 s00     0.321438     0.149010     0.125168
           s01     0.217779     0.067209     0.040285
           s02     0.173066     0.293539     0.417372
           s03     0.048929     0.415637     0.216490
           s04     0.238788     0.074605     0.200685
2015-04-30 s00     0.321438     0.149010     0.125168
           s01     0.217779     0.067209     0.040285
           s02     0.173066     0.293539     0.417372
           s03     0.048929     0.415637     0.216490
           s04     0.238788     0.074605     0.200685
2015-05-29 s00     0.321438     0.149010     0.125168
           s01     0.217779     0.067209     0.040285
           s02     0.173066     0.293539     0.417372
           s03     0.048929     0.415637     0.216490
           s04     0.238788     0.074605     0.200685

结论

虽然我仍然对其他人的答案感兴趣。由于以下原因,我选择亚历山大的答案:

%%timeit
    df = generic_portfolio_df('2014-12-31', '2015-05-30', 'BM', 3, 5)
    df = df.unstack()
    df.iloc[3:] = np.nan
    df = df.ffill(limit=3).stack()

100 loops, best of 3: 11.6 ms per loop

%%timeit
    df = generic_portfolio_df('2014-12-31', '2015-05-30', 'BM', 3, 5)
    df0 = df.loc[pd.IndexSlice[::3, :], :]
    diff = df.index.difference(df0.index)
    df.ix[diff] = np.nan
    df.groupby(level=1).ffill(limit=3)

100 loops, best of 3: 21 ms per loop

显然,使用stackunstack会更有效率。

3 个答案:

答案 0 :(得分:3)

# Create Boolean index of rows to delete (every third row is marked as False).
idx = len(df.unstack())
idx = [i % 3 > 0 for i in range(idx)]
>>> idx
[False, True, True, False, True, True]

# Unstack the dataframe so you just have a column of dates 
df = df.unstack()

# Delete those in the `idx` index.
df.loc[idx, :] = np.nan

# Forward fill the retained dates, and then restack your dataframe.
df = df.ffill(limit=3).stack()

>>> df.tail()
Portfolio       Portfolio A  Portfolio B  Portfolio C
Date       Id                                        
2015-05-29 s00     0.321438     0.149010     0.125168
           s01     0.217779     0.067209     0.040285
           s02     0.173066     0.293539     0.417372
           s03     0.048929     0.415637     0.216490
           s04     0.238788     0.074605     0.200685

答案 1 :(得分:2)

我认为在这种情况下(使用' BM'作为频率)单行将会:

df2 = df.unstack().resample('3BM').first().resample('1BM').ffill(limit=3).stack()

当然,对于其他频率字符串freq,您可以分别使用'3'+freq'1'+freq

<强>更新

我刚刚注意到上面的代码可能会在索引中添加一天(resample('3BM'),因此我们必须另外控制数据框的长度。

至于一般情况,它仍然可以在一行中完成。为了更具可读性,我将其拆分为两个。首先,我创建了一个我们想要保留的未堆叠数据框中的行索引:

idx = np.arange(np.ceil(len(df.unstack())/3), dtype = int)*3
df2 = df.unstack().iloc[idx].loc[df_t.index].fillna(method = 'ffill').stack()

添加不需要的行并没有问题,而且更不等于Alexander的回答。无论如何,我认为亚历山大的答案更清晰,更优雅。

答案 2 :(得分:0)

解决方案

Exception in thread "Thread-28" java.lang.NumberFormatException: For input string: "boom"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.parseInt(Integer.java:527)
    at ...

这几乎与亚历山大的答案相同。这就是我用来制作样品的原因。

亮点

  • df0 = df.loc[pd.IndexSlice[::3, :], :] diff = df.index.difference(df0.index) df.ix[diff] = np.nan df.groupby(level=1).ffill(limit=3) 我喜欢这个工具。前两行代码定义了要设置为pd.IndexSlice的索引,并且不需要np.nan
  • 再次
  • unstack(),无需在groupby(level=1).ffill(limit=3)模式下操作
  • unstacked()是必需的,虽然我给出的例子并不明显。情况可能是limit=3可能早期存在并且不属于投资组合。如果发生这种情况,则列的其余部分将为'Id'并受'NaN'限制。 ffill阻止了这一点。