这一篇的主题,我们来谈一谈如何不用for遍历,以及一定要用循环遍历的话,如何优雅。
我不是要逼你用while循环,我是在讨论优雅的遍历方法,这里优雅的标准是,代码简洁清晰,时间复杂度方面也高效,同时兼具python“implicit is better than explicit”的准则。
用for 循环有一个不好的地方在于代码不易读,但是写成列表解析的方式会非常清晰:
def process(item):
...
...
return result
result= [process(item) for item in list]
#列表解析的优雅 类似于 优雅的定义函数:
In [3]: (lambda x:x>2)(np.arange(10))
Out[3]: array([False, False, False, True, True, True, True, True, True, True], dtype=bool)
In [4]: [i+j for i in range(5) for j in range(i)]
Out[4]: [1, 2, 3, 3, 4, 5, 4, 5, 6, 7]
In [5]: [j for i in range(5) for j in range(i)]
Out[5]: [0, 0, 1, 0, 1, 2, 0, 1, 2, 3]
当然也有很多时候不用写for循环,还可以用一些比较pythonic的函数。比如:
1. map
如果你想把一个list映射到另一个list,可以用map函数
oldlist=range(5)
square=map(lambda x:x**2,oldlist)
print square
[0, 1, 4, 9, 16]
“扁平结构比嵌套结构更好” – 《Python之禅》
比如 list(map(lambda x:4 if x==3 else x),a)
如果一定要用for:
a = [1, 2, 3, 4, 5, 6]
3变成4
[4 if x==3 else x for x in a]
b = [1, 2, 4, 4, 5, 6]
删除4
[x for x in b if x != 4]
也是可以的。。
2.filter
In [88]: filter(lambda x:x%2==1,[1,4,6,7,9,12,17])
Out[88]: [1, 7, 9, 17]
3.apply与rolling
rolling返回的是一个ndarray,如果要用对Series的方法,如rank(),才能使用。例如,想要知道当前的因子值是过去40根bar中的排名,俗称ts_rank(x,40),实现方法:
In [26]:a=pd.Series(np.random.random(10))
0 0.537490
1 0.008421
2 0.075805
3 0.205377
4 0.063493
5 0.893637
6 0.554811
7 0.727446
8 0.355941
9 0.543967
dtype: float64
In [27]:a.rolling(3).apply(lambda x:pd.Series(x).rank()[3-1])
Out[27]:
0 NaN
1 NaN
2 2.0
3 3.0
4 1.0
5 3.0
6 2.0
7 2.0
8 1.0
9 2.0
dtype: float64
然而上面这个操作的不是很优雅,原因在于python自带的pd.rank()函数返回的是所有元素的排名对应的Series,但是我们只关心当前的因子在前M根bar中的排名,我们可以使用如下更为优雅的方法,速度大概快了30倍左右。
a.rolling(M).apply(lambda x:(x>x[-1]).sum()+1)
不过这个方法的缺点在于没有考虑元素相同的情况下的平均排名。意思是例如[1,2,3,4,4,4],4的排名就是6,5,4,rank()会返回5。我们的简约算法会返回排名中的最小值4。值得庆幸的是,ts_rank一般考虑的周期比较大,例如120,那么119.5相对于120的差别较4与5的差别可以忽略不计了。
4.根据需要的时间点取出对应行,并做相同操作。
df.loc[df.time.isin(timelist)]#可以取出想要的行
这个操作就是很典型的解释性语言的风格,凸显了implicit的风格,而喜欢explicit风格的人自然想到了用for循环。
比如下面:
[i in timelist for i in df.time]