一、数据清洗
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的方式:
- 用dict先存,最后转df(会快很多)
- 用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.计算相关系数:
参考
- pd
- numpy
- 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.做截面标准化:
- 手写:先算日mean和std序列,再遍历每只股票,序列相减、除,再concat(或用dict先存后转)
- 直接pd计算。涉及不同shape的pd直接用df.add/sub/mul/div,注明方向,axis=0是列运算。
- 转成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 ?