问题 Pandas:分配具有多个条件和日期阈值的列


编辑:

我在大熊猫数据框df中有一个金融投资组合,其中指数是日期,我每个日期有多个金融股。

例如数据帧:

Date    Stock   Weight  Percentile  Final weight
1/1/2000    Apple   0.010   0.75    0.010
1/1/2000    IBM    0.011    0.4     0
1/1/2000    Google  0.012   0.45    0
1/1/2000    Nokia   0.022   0.81    0.022
2/1/2000    Apple   0.014   0.56    0
2/1/2000    Google  0.015   0.45    0
2/1/2000    Nokia   0.016   0.55    0
3/1/2000    Apple   0.020   0.52    0
3/1/2000    Google  0.030   0.51    0
3/1/2000    Nokia   0.040   0.47    0

我创建 Final_weight 通过分配值来实现 Weight 每当 Percentile 大于 0.7

现在我希望这更复杂,我仍然想要 Weight 被分配到 Final_weight 什么时候 Percentile is > 0.7然而,在此日期之后(在未来的任何时候),而不是在成为股票时变为0 Percentile 不是 >0.7,我们仍然可以获得与股票一样多的权重 Percentile 在上面 0.5 (即持有头寸不超过一天)。

然后,如果股票低于 0.5 (在不久的将来)然后 Final_weight would become 0

例如,从上面修改的数据帧:

Date    Stock   Weight  Percentile  Final weight
1/1/2000    Apple   0.010   0.75    0.010
1/1/2000    IBM     0.011   0.4     0
1/1/2000    Google  0.012   0.45    0
1/1/2000    Nokia   0.022   0.81    0.022
2/1/2000    Apple   0.014   0.56    0.014
2/1/2000    Google  0.015   0.45    0
2/1/2000    Nokia   0.016   0.55    0.016
3/1/2000    Apple   0.020   0.52    0.020
3/1/2000    Google  0.030   0.51    0
3/1/2000    Nokia   0.040   0.47    0

每天投资组合都不同,并不总是从前一天开始拥有相同的股票。


6271
2018-05-04 20:08


起源

您是否有任何尝试过的代码向我们展示? - Mad Physicist
我写的代码如果与下面的PiRSquaredes回答非常相似,但是这只看了一天前,我想要一个很好的熊猫方式来做它最好没有循环,因为数据集很大 - MysterioProgrammer91


答案:


这个解决方案更明确,更少熊猫式,但它只涉及一次遍历所有行而不创建大量的临时列,因此可能更快。它需要一个额外的状态变量,我把它包装成一个闭包,不需要创建一个类。

def closure():
    cur_weight = {}
    def func(x):
        if x["Percentile"] > 0.7:
            next_weight = x["Weight"]
        elif x["Percentile"] < 0.5 :
            next_weight = 0
        else:
            next_weight = x["Weight"] if cur_weight.get(x["Stock"], 0) > 0 else 0
        cur_weight[x["Stock"]] = next_weight
        return next_weight
    return func

df["FinalWeight"] = df.apply(closure(), axis=1)

5
2018-05-12 08:05



很棒的答案....这么快! - MysterioProgrammer91
@MysterioProgrammer91整个数据集的速度有多快? (对此你说其他答案需要3天左右)。 - Andy Hayden
@cronos除非这是我提交的相同问题的牺牲品,它可能会改变 closure 只是给出指标变量,然后添加 df['Final Weight'] = df['Final Weight']*df['Weight'] 应用后它应该更快,在我测试时对我来说大约10%。 - EFT


  • 我先放 'Stock' 进入索引
  • 然后 unstack 将它们放入列中
  • 我然后分手了 w 对于重量和 p 百分位数
  • 然后操纵一系列 where

d1 = df.set_index('Stock', append=True)

d2 = d1.unstack()

w, p = d2.Weight, d2.Percentile

d1.join(w.where(p > .7, w.where((p.shift() > .7) & (p > .5), 0)).stack().rename('Final Weight'))

                   Weight  Percentile  Final Weight
Date       Stock                                   
2000-01-01 Apple    0.010        0.75         0.010
           IBM      0.011        0.40         0.000
           Google   0.012        0.45         0.000
           Nokia    0.022        0.81         0.022
2000-02-01 Apple    0.014        0.56         0.014
           Google   0.015        0.45         0.000
           Nokia    0.016        0.55         0.016

3
2018-05-04 20:29



嗨非常感谢你的回答。我修改了问题中的示例数据帧。我实际上不只是看一个班次,也就是说,在我们购买股票之后因为它在前30个百分点,我们将拥有它,只要它保持超过0.5,例如,甚至可以持续10天。我仍然会指定一个重量,但是一旦百分位数低于0.5,它就不会给最终重量分配权重,并且会等到它再次超过0.7个百分点。 - MysterioProgrammer91


建立

Dataframe:

             Stock  Weight  Percentile  Finalweight
Date                                               
2000-01-01   Apple   0.010        0.75            0
2000-01-01     IBM   0.011        0.40            0
2000-01-01  Google   0.012        0.45            0
2000-01-01   Nokia   0.022        0.81            0
2000-02-01   Apple   0.014        0.56            0
2000-02-01  Google   0.015        0.45            0
2000-02-01   Nokia   0.016        0.55            0
2000-03-01   Apple   0.020        0.52            0
2000-03-01  Google   0.030        0.51            0
2000-03-01   Nokia   0.040        0.57            0

df = df.reset_index()
#find historical max percentile for a Stock
df['max_percentile'] = df.apply(lambda x: df[df.Stock==x.Stock].iloc[:x.name].Percentile.max() if x.name>0 else x.Percentile, axis=1)
#set weight according to max_percentile and the current percentile
df['Finalweight'] = df.apply(lambda x: x.Weight if (x.Percentile>0.7) or (x.Percentile>0.5 and x.max_percentile>0.7) else 0, axis=1)

Out[1041]: 
        Date   Stock  Weight  Percentile  Finalweight  max_percentile
0 2000-01-01   Apple   0.010        0.75        0.010            0.75
1 2000-01-01     IBM   0.011        0.40        0.000            0.40
2 2000-01-01  Google   0.012        0.45        0.000            0.45
3 2000-01-01   Nokia   0.022        0.81        0.022            0.81
4 2000-02-01   Apple   0.014        0.56        0.014            0.75
5 2000-02-01  Google   0.015        0.45        0.000            0.51
6 2000-02-01   Nokia   0.016        0.55        0.016            0.81
7 2000-03-01   Apple   0.020        0.52        0.020            0.75
8 2000-03-01  Google   0.030        0.51        0.000            0.51
9 2000-03-01   Nokia   0.040        0.57        0.040            0.81

注意

在示例数据的最后一行中,诺基亚的Percentile为0.57,而在您的结果中,它变为0.47。在这个例子中,我使用了0.57,因此输出与最后一行的输出略有不同。


2
2018-05-11 07:42





一种方法,避免循环和有限的回顾期。

使用你的例子:

import pandas as pd
import numpy as np


>>>df = pd.DataFrame([['1/1/2000',    'Apple',   0.010,   0.75],
                      ['1/1/2000',    'IBM',     0.011,    0.4],
                      ['1/1/2000',    'Google',  0.012,   0.45],
                      ['1/1/2000',    'Nokia',   0.022,   0.81],
                      ['2/1/2000',    'Apple',   0.014,   0.56],
                      ['2/1/2000',    'Google',  0.015,   0.45],
                      ['2/1/2000',    'Nokia',   0.016,   0.55],
                      ['3/1/2000',    'Apple',   0.020,   0.52],
                      ['3/1/2000',    'Google',  0.030,   0.51],
                      ['3/1/2000',    'Nokia',   0.040,   0.47]],
                     columns=['Date', 'Stock', 'Weight', 'Percentile'])

首先,确定何时开始或停止跟踪最终重量的股票:

>>>df['bought'] = np.where(df['Percentile'] >= 0.7, 1, np.nan)
>>>df['bought or sold'] = np.where(df['Percentile'] < 0.5, 0, df['bought'])

“1”表示要购买的股票,“0”表示要卖出,如果拥有。

由此,您可以确定股票是否归属。请注意,这需要按时间顺序对数据帧进行排序,如果您在没有日期索引的数据帧上使用它的话:

>>>df['own'] = df.groupby('Stock')['bought or sold'].fillna(method='ffill').fillna(0)

'ffill' 是向前填充,从买卖日期向前传播所有权状态。 .fillna(0) 捕获整个数据帧中保持在.5和.7之间的任何股票。 然后,计算最终重量

>>>df['Final Weight'] = df['own']*df['Weight']

乘法,用 df['own'] 作为身份或零,比另一个np.where快一点,并给出相同的结果。

编辑:

由于速度是一个问题,按照@cronos的建议,在一列中完成所有操作确实提供了速度提升,在我的测试中,在20行中提高了约37%,在2,000,000时提高了18%。如果存储中间列是为了跨越某种内存使用阈值,或者还有其他涉及我没有遇到的系统细节,我可以想象后者更大。

这看起来像:

>>>df['Final Weight'] = np.where(df['Percentile'] >= 0.7, 1, np.nan)
>>>df['Final Weight'] = np.where(df['Percentile'] < 0.5, 0, df['Final Weight'])
>>>df['Final Weight'] = df.groupby('Stock')['Final Weight'].fillna(method='ffill').fillna(0)
>>>df['Final Weight'] = df['Final Weight']*df['Weight']

使用此方法或删除中间字段将给出结果:

>>>df 
       Date   Stock  Weight  Percentile  Final Weight
0  1/1/2000   Apple   0.010        0.75         0.010
1  1/1/2000     IBM   0.011        0.40         0.000
2  1/1/2000  Google   0.012        0.45         0.000
3  1/1/2000   Nokia   0.022        0.81         0.022
4  2/1/2000   Apple   0.014        0.56         0.014
5  2/1/2000  Google   0.015        0.45         0.000
6  2/1/2000   Nokia   0.016        0.55         0.016
7  3/1/2000   Apple   0.020        0.52         0.020
8  3/1/2000  Google   0.030        0.51         0.000
9  3/1/2000   Nokia   0.040        0.47         0.000

为了进一步改进,我将考虑添加一种方法来设置一个拥有股票的初始条件,然后将数据框向下打破以查看较小的时间范围。这可以通过为这些较小的数据帧之一所覆盖的时间段添加初始条件,然后进行更改来完成

>>>df['Final Weight'] = np.where(df['Percentile'] >= 0.7, 1, np.nan)

喜欢的东西

>>>df['Final Weight'] = np.where((df['Percentile'] >= 0.7) | (df['Final Weight'] != 0), 1, np.nan)

允许它被识别和传播。


2
2018-05-09 23:21



这可行,但由于我的投资组合中的股票数量和大数据性质,它需要大约3天才能运行。有什么办法让它更快? - MysterioProgrammer91
很好的答案。但是你可以通过单一使用来加快速度 FinalWeight 专栏从一开始就开始研究。无需3个临时列。 - cronos
@MysterioProgrammer91你能描述数据集的内部结构(#rows,#distinct stocks)以及你运行它的环境吗?我在构建一个场景时遇到了一些麻烦,在这个场景中,我的操作数据帧在内存中占用了很长时间,而且这些信息对于确定我目前使用的技术在何处以及如何失败都有很大帮助。规模。 - EFT


我想你可能想要使用pandas.Series 压延 窗口方法。

也许是这样的:

import pandas as pd

grouped = df.groupby('Stock')

df['MaxPercentileToDate'] = np.NaN
df.index = df['Date']

for name, group in grouped:
    df.loc[df.Stock==name, 'MaxPercentileToDate'] = group['Percentile'].rolling(min_periods=0, window=4).max()

# Mask selects rows that have ever been greater than 0.75 (including current row in max)
# and are currently greater than 0.5
mask = ((df['MaxPercentileToDate'] > 0.75) & (df['Percentile'] > 0.5))
df.loc[mask, 'Finalweight'] = df.loc[mask, 'Weight']

我相信这假设值按日期排序(您的初始数据集似乎有),您还必须调整 min_periods 参数是每个股票的最大条目数。


1
2018-05-09 20:01