股票中心度因子复现反思总结 和 关于pd和np的效率思考

本文介绍了数据清洗中的注意事项,如保留inf和处理nan,提倡使用向量化和字典存储提高效率,讨论了循环、分组计算、相关系数计算、截面标准化以及df操作的最佳实践。同时强调了函数封装和处理新股问题的方法。
摘要由CSDN通过智能技术生成

一、数据清洗

1.上市天数>30d 或 上市>=252d;

2.筛ST和退市股;

3.用后复权价格;

4.保留inf和nan:

  • 不要在计算的时候把inf换成nan(可以只加上一个检查),有些时候理论上出现不了inf,但是实际计算中出现了的话,可能是因为计算的代码写错了(保留inf便于检查);
  • 至于nan,只需要后续计算的时候注意忽略就ok了。
    • pd(df)自动忽略nan;
    • np必须用np.mean() or np.nanmean() 才行,不然array.mean()就是不会忽略nan(结果为nan);
    • 用码掩:np.ma.masked_invalid() ,但是生成码掩数组就很慢... :<

二、实现方式和习惯--->提高效率

1.循环:

尽量少用循环,能向量化一句话批量处理的就不要循环。(e.g.)

比如qcut 和 自己写循环处理分组收益。

2.循环拼接df的方式:

  1. 用dict先存,最后转df(会快很多)
  2. 用concat直接一次次的拼(越到后面,df越大,concat处理速度就越慢)

对比如下:

s=datetime.now()
ret_group={}
for i in range(1,100):
    x=a.loc[i-1,:]
    group_id=pd.qcut(x, 10,labels=np.arange(1,11)) 
    group_id.name='id'
    ret=pd.concat([b.loc[i,:],group_id],axis=1)
    ret_group[i]=ret.groupby('id').mean().values.reshape(10,)
    
ret_group=pd.DataFrame(ret_group)
e=datetime.now()
print('qcut & dict to df',e-s)

s=datetime.now()
ret_group=pd.DataFrame()
for i in range(1,100):
    x=a.loc[i-1,:]
    group_id=pd.qcut(x, 10,labels=np.arange(1,11)) 
    group_id.name='id'
    ret=pd.concat([b.loc[i,:],group_id],axis=1)
    ret_group=pd.concat([ret_group,ret.groupby('id').mean()],axis=1)
    

e=datetime.now()
print('qcut & concat to df',e-s)

s=datetime.now()
ret_group={}
for i in range(10):
    ret_group['group'+str(i+1)]=[]
for d in range(1,100):
    sort_code= a.loc[d-1,:].sort_values(ascending =True).dropna().index 
    num=int(len(sort_code)/10)   ### 5154\10 = 515
    for i in range(10):
        ret_group['group'+str(i+1)].append(b.loc[d,sort_code[i*num:(i+1)*num]].mean())
ret_group=pd.DataFrame(ret_group)
e=datetime.now()
print('no qcut & dict to df',e-s)

>>> ''' results '''

qcut & dict to df 0:00:00.196086
qcut & concat to df 0:00:00.309246
no qcut & dict to df 0:00:00.360299

再比如:

从stk_kinfo={code: kinfo_df}中抽出close矩阵、chg_1d_df矩阵,也不要concat大表,而是用dict先存后转

print('concat:')
chg=pd.DataFrame()
for code in tqdm(list(stk_aftNAN.keys())):
    c_1d=stk_aftNAN[code]['chg_1d']
    c_1d.name=code
    chg =pd.concat([chg,c_1d],axis=1)


print('dict')
chg={}
for code in tqdm(list(stk_aftNAN.keys())):
    c_1d=stk_aftNAN[code]['chg_1d']
    chg[code]=c_1d
chg=pd.DataFrame(chg)

>>> 
concat:
100%|██████████| 5154/5154 [03:58<00:00, 21.64it/s]
dict
100%|██████████| 5154/5154 [00:00<00:00, 262513.27it/s]

3.计算相关系数:

参考

  1. pd
  2. numpy
  3. bottleneck:
import numpy as np
from bottleneck import move_block
data = np.concatenate((A, B), axis=1)  ### 要算A,B数据集的相关系数

'''block_size指定了每个块的大小,num_blocks是数据集中块的数量'''

result = np.zeros((num_blocks,))
for i in range(num_blocks):
    start = i * block_size
    end = start + block_size
    block_data = data[:, start:end]
    block_data = block_data - np.mean(block_data, axis=1, keepdims=True)
    result[i] = np.mean(np.dot(block_data, block_data.T))
corr = np.mean(result)、

'''这仅适用于较大的数据集,因为它需要将数据集分成多个块来处理'''

4.做截面标准化:

  1. 手写:先算日mean和std序列,再遍历每只股票,序列相减、除,再concat(或用dict先存后转)
  2. 直接pd计算。涉及不同shape的pd直接用df.add/sub/mul/div,注明方向,axis=0是列运算。
  3. 转成np计算,reshape后,才能broadcast(注意np.std()默认ddof=0,而df.std()默认ddof=1,要改一下。

先说结论:直接用pd自带函数df.add/sub/mul/div算会快一点,pd是np的两倍速度

测试:

a=pd.DataFrame(np.random.randint(0,100,(10000,5000)))

print("测试样本大小:",a.shape)
print("=====================")


s=datetime.now()
d1={}
mean=a.mean(axis=1)
std=a.std(axis=1,ddof=0)
for i in range(5000):
    d1[i]=(a.iloc[:,i]-mean)/std
d1=pd.DataFrame(d1)
e=datetime.now()
print("循环",e-s)
t1=e-s

s=datetime.now()
mean=a.mean(axis=1)
std=a.std(axis=1,ddof=0)
d2=a.sub(mean,axis=0).div(std,axis=0)
e=datetime.now()
print("pd  ",e-s)
t2=e-s

s=datetime.now()
mean=np.array(a).mean(axis=1).reshape(10000,1)
std=np.array(a).std(axis=1,ddof=0).reshape(10000,1)
d3=pd.DataFrame( (np.array(a)-mean)/std)
e=datetime.now()
print("np  ",e-s)
t3=e-s

print("速度比较(以“手动循环”速度为 1 单位)\n{:.2f} , {:.2f} , {:.2f}".format(t1/t1,t1/t2,t1/t3))

 测试结果:

>>>
测试样本大小: (1000, 5000)
=====================
循环 0:00:02.268984
pd   0:00:00.051131
np   0:00:00.091769

>>>
测试样本大小: (10000, 5000)
=====================
循环 0:00:04.028592
pd   0:00:00.420793
np   0:00:00.889271

>>>
速度比较(以“手动循环”速度为 1 单位)
1.00 , 10.67 , 4.79

一个衍生的问题: 

df1.mul(df2) 和 df1*df2 区别?

先上结论:

  • df.mul()可以不同shape的相运算,更灵活
  • 速度:df.mul()在小样本的时候更快,* 在大样本更快

测试:

a=pd.DataFrame(np.random.randint(0,100,(1000,5000)))
b=pd.DataFrame(np.random.randint(0,100,(1000,5000)))
print("测试样本大小:",a.shape)
print("=====================")

s=datetime.now()
c=a*b
e=datetime.now()
print("*  ",e-s)

s=datetime.now()
c=a.mul(b)
e=datetime.now()
print("mul",e-s)

结果:

>>>
测试样本大小: (100000, 5000)
=====================
*   0:00:00.342597
mul 0:00:00.426536

>>>
测试样本大小: (1000, 5000)
=====================
*   0:00:00.109975
mul 0:00:00.012219

三、框架搭建习惯

尽量封装成函数,把入参做灵活一点,方便后续调用和兼容。

四、问题

1.没明白什么叫做y的处理?

2.处理新股时,无法获取历史每一天的新股,只能获得当天实时的新股列表。

解决方法:

获取不到当日的上市日期,解决方法: 在所有因子值计算完了之后,选股、因子评估时,把所有股票的前30天的因子值设为np.nan  ?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值