DataFrame按列内容取不同列的值生成新列

这篇文章的标题有点绕口,但确实是在计算因子时遇到的一种实际场景。例如,我们找到了前期高点的位置,然后想要统计前期高点位置之前的10个交易日内出现涨停的次数。

问题描述

我们以sh.603906龙蟠科技为例,相关数据保存在DataFrame类型的对象df中,以下显示2021年9月9日至2021年9月24日之间10个交易日的数据:

            date   open   high    low  ...  isST     zt     xg  qg_jl
1080  2021-09-09  40.00  40.36  38.06  ...     0  False  False     19
1081  2021-09-10  38.79  40.00  38.30  ...     0  False  False     20
1082  2021-09-13  41.00  43.76  39.91  ...     0   True  False     21
1083  2021-09-14  44.68  46.80  42.92  ...     0  False  False     22
1084  2021-09-15  45.70  49.96  44.00  ...     0   True   True     23
1085  2021-09-16  52.31  53.46  45.87  ...     0  False   True      1
1086  2021-09-17  46.20  46.73  44.00  ...     0  False  False      1
1087  2021-09-22  45.03  46.37  43.50  ...     0  False  False      2
1088  2021-09-23  45.18  48.73  43.92  ...     0   True  False      3
1089  2021-09-24  48.65  53.60  47.10  ...     0  False   True      4

其中,zt列表示当日是否涨停,xg列表示是否为120日来的新高点,qg_jl列表示与前期高点间的距离。

对应看一下K先图:

  • 9月15日,涨停,因此zt列的值为True;形成120日新高,因此xg列的值为True;前高的日期为8月13日,距离为23,因此qg_jl列的值为23。
  • 9月23日,涨停,因此zt列的值为True;未形成120日新高,因此xg列的值为False;前高的日期为9月16日,距离为3,因此qg_jl列的值为3。
  • 9月24日,未涨停,因此zt列的值为False;形成120日新高,因此xg列的值为True;前高的日期为9月16日,距离为4,因此qg_jl列的值为4。

为了统计前期高点前的涨停个数,我们对涨停因子进行移动,得到temp_df,部分列显示如下:

            date  zt_1a  zt_2a  zt_3a  ...  zt_127a  zt_128a  zt_129a  zt_130a
1080  2021-09-09  False  False  False  ...    False    False    False    False
1081  2021-09-10  False  False  False  ...    False    False    False    False
1082  2021-09-13  False  False  False  ...    False    False    False    False
1083  2021-09-14   True  False  False  ...    False    False    False    False
1084  2021-09-15  False   True  False  ...    False    False    False    False
1085  2021-09-16   True  False   True  ...    False    False    False    False
1086  2021-09-17  False   True  False  ...    False    False    False    False
1087  2021-09-22  False  False   True  ...    False    False    False    False
1088  2021-09-23  False  False  False  ...    False    False    False    False
1089  2021-09-24   True  False  False  ...    False    False    False    False

其中zt_1a表示1天前是否涨停,zt_2a表示2天前是否涨停,以此类推。

我们的目标是统计前期高点位置之前的10个交易日内出现涨停的次数,仍以上面三个交易日为例:

  • 9月15日,前高距离qg_jl为23, 统计前期高点之前的10个交易日出现涨停的次数,就等价于统计zt_23a、zt_24a、zt_25a、…、zt_32a中值为True的个数。
  • 9月23日,前高距离qg_jl为3,统计前期高点之前的10个交易日出现涨停的次数,就等价于统计zt_3a、zt_4a、zt_5a、…、zt_12a中值为True的个数。
  • 9月24日,前高距离qg_jl为4,统计前期高点之前的10个交易日出现涨停的次数,就等价于统计zt_4a、zt_5a、zt_6a、…、zt_13a中值为True的个数。

可以看出,我们的问题转化为了根据列qg_jl的值,来获取不同的zt_Xa列的值,也就是本文题目中所描述的问题。

解决方案

先上代码:

qg_day = 10
for i in range(qg_day):
    temp_df['temp_name_zt_{}'.format(i)] = temp_df['qg_jl'].apply(lambda x: 'zt_{}a'.format(int(x + i)))
    temp_df['qg_zt_{}'.format(i)] = None
    for val in temp_df['temp_name_zt_{}'.format(i)].unique():
        temp_df.loc[temp_df['temp_name_zt_{}'.format(i)] == val, 'qg_zt_{}'.format(i)] = temp_df[val]

cols = ['qg_zt_{}'.format(x) for x in range(qg_day)]
df['qg_zt_num'] = temp_df[cols][temp_df[cols]].count(1)
  • 第1行,定义变量qg_day,用于设置访问前期高点前多少天的数据,我们这里统计前期高点之前的10个交易日出现涨停的次数,所以赋值为10。
  • 2~6行,循环构造列,用于后续统计选择时间范围内的涨停次数。
  • 第3行,构造新列temp_name_zt_X,用于存放待统计涨停列的列名,打印结果如下:
            date temp_name_zt_0  ... temp_name_zt_8 temp_name_zt_9
1080  2021-09-09         zt_19a  ...         zt_27a         zt_28a
1081  2021-09-10         zt_20a  ...         zt_28a         zt_29a
1082  2021-09-13         zt_21a  ...         zt_29a         zt_30a
1083  2021-09-14         zt_22a  ...         zt_30a         zt_31a
1084  2021-09-15         zt_23a  ...         zt_31a         zt_32a
1085  2021-09-16          zt_1a  ...          zt_9a         zt_10a
1086  2021-09-17          zt_1a  ...          zt_9a         zt_10a
1087  2021-09-22          zt_2a  ...         zt_10a         zt_11a
1088  2021-09-23          zt_3a  ...         zt_11a         zt_12a
1089  2021-09-24          zt_4a  ...         zt_12a         zt_13a

9月15日,需要访问其zt_23a至zt_31a,9月23日,需要访问zt_3a至zt_12a,9月24日,需要访问zt_4a至zt_13a,与问题描述中分析结果一致。

  • 第4行,构建新列qg_zt_X,值都置为None。

  • 5~6行,循环对qg_zt_X列赋值,以i=0时为例,即使用temp_name_zt_0列给qg_zt_0列赋值。当循环到val=zt_1a时,temp_df.loc[temp_df['temp_name_zt_{}'.format(i)] == val, 'qg_zt_{}'.format(i)]就将temp_name_zt_0列中值为zt_1a的行都筛选出来,上面打印的10个交易日数据来说,9月16日和17日temp_name_zt_0列的值均为zt_1a,这两行就会被筛选出来。然后temp_df[val]会把列zt_1a提取出来,对前面得到的temp_name_zt_0列值为zt_1a的行的qg_zt_0列赋值(有点绕,可以看后面简约示例更清晰些)。

    这里有个问题,就是赋值的等号左右两侧维度不一样,以上面打印的10行数据为例,等号左侧temp_df.loc[temp_df['temp_name_zt_{}'.format(i)] == val, 'qg_zt_{}'.format(i)]取出值为zt_1a的Series只有2个元素,而等号右侧temp_df[val]取出的是1列数据,有10个元素,这里赋值是会根据索引进行对应赋值,即对索引1085和1086对应的元素进行赋值。

    来看一下qg_zt_X的结果:

            date qg_zt_0 qg_zt_1 qg_zt_2  ... qg_zt_6 qg_zt_7 qg_zt_8 qg_zt_9
1080  2021-09-09   False    True   False  ...   False   False   False   False
1081  2021-09-10   False    True   False  ...   False   False   False   False
1082  2021-09-13   False    True   False  ...   False   False   False   False
1083  2021-09-14   False    True   False  ...   False   False   False   False
1084  2021-09-15   False    True   False  ...   False   False   False   False
1085  2021-09-16    True   False    True  ...   False   False   False   False
1086  2021-09-17   False    True   False  ...   False   False   False   False
1087  2021-09-22   False    True   False  ...   False   False   False   False
1088  2021-09-23   False    True   False  ...   False   False   False   False
1089  2021-09-24   False    True   False  ...   False   False   False   False

对应的qg_zt_X均已根据列名转化为对应的布尔值,表示前期高点前的各天是否是涨停。

  • 第8行,列名列表,用于统计涨停的次数。
  • 第9行,统计涨停的次数,结果保存在qg_zt_num列中。

看一下计算结果:

            date   open   high    low  ...     zt     xg  qg_jl  qg_zt_num
1080  2021-09-09  40.00  40.36  38.06  ...  False  False     19          1
1081  2021-09-10  38.79  40.00  38.30  ...  False  False     20          1
1082  2021-09-13  41.00  43.76  39.91  ...   True  False     21          1
1083  2021-09-14  44.68  46.80  42.92  ...  False  False     22          1
1084  2021-09-15  45.70  49.96  44.00  ...   True   True     23          1
1085  2021-09-16  52.31  53.46  45.87  ...  False   True      1          2
1086  2021-09-17  46.20  46.73  44.00  ...  False  False      1          2
1087  2021-09-22  45.03  46.37  43.50  ...  False  False      2          2
1088  2021-09-23  45.18  48.73  43.92  ...   True  False      3          2
1089  2021-09-24  48.65  53.60  47.10  ...  False   True      4          2

对照上文中的K线图:

  • 9月15日,前高在23个交易日前,即8月13日,8月13日(包含)的前10个交易日内,有1次涨停(8月12日)。
  • 9月24日,前高在4个交易日前,即9月16日,9月16日(包含)的前10个交易日内,有2次涨停(9月15日、9月13日)。

至此完成了DataFrame按列内容取不同列的值生成新列。

简约示例

上文的例子过于复杂,下面用一个简单的例子进行说明。

首先构造一个简单的DataFrame:

df = pd.DataFrame([[1, 2, 3, 'A'],
                   [4, 5, 6, 'C'],
                   [7, 8, 9, 'B']], columns=['A', 'B', 'C', 'D'])
print(df)
   A  B  C  D
0  1  2  3  A
1  4  5  6  C
2  7  8  9  B

其中,D列存放了不同的列名,我们要求新建一列E,来保存各行按D列内容获取对应列中的内容。例如,第0行第D列的值为A,那么我们希望第0行第E列的值等于第0行第A列的值(1);第1行第D列的值为C,那么我们希望第1行第列E的值等于第1行第C列的值(6);第2行第D列的值为B,那么我们希望第2行第E列的值等于第2行第B列的值(8);也就是说,我们的目标是生成下面的数据:

   A  B  C  D  E
0  1  2  3  A  1
1  4  5  6  C  6
2  7  8  9  B  8

实现代码如下:

df['E'] = None
for val in df['D'].unique():
    df.loc[val == df['D'], 'E'] = df[val]
  • 第1行,新建列E,赋值为None。如果没有这一步,会导致由于局部赋值,新列结果都为浮点型的情况。

  • 2~3行,循环对E列赋值。等号左侧df.loc[val == df['D'], 'E']类型为Series,取出的是所有D列值为val的行的E列;等号右侧df[val]类型也是Series,提取的是列名为val的列。

    以val=B为例,df.loc[val == df['D'], 'E']取出的是有D列值为B的行的E列,打印结果如下:

  print(df.loc[val == df['D'], 'E'])
  2    None
  Name: E, dtype: object

df[val]取出的是B列:

  print(df[val])
  B
  0    2
  1    5
  2    8
  Name: B, dtype: int64

可以看到等号两侧Series所包含的元素个数不同,但并不影响赋值操作,赋值会按照索引进行对应赋值,即把等号右侧索引为2的值8,赋值给等号左侧索引为2的元素。

经过测试,生成的新列E符合预期。

小结

本文结合股票因子计算的实例以及一个简约示例,展示了DataFrame按列内容取不同列的值生成新列的过程。这是目前笔者找到一种解决方案,如果读者有更好更快的方案,也请留言交流。

《DataFrame按列内容取不同列的值生成新列》有1条评论

发表评论

京公网安备 11010802036642号

京ICP备2021028699号