量化工具Numpy知识1:并行化思想与基础操作

Numpy是Python很多科学计算与工程库的基础库,在量化数据分析中最常使用的pandas也是基于Numpy的封装,可以说Numpy就是量化数据分析领域中的基础数组,学会Numpy是量化分析中关键的一步。

Numpy的输出是一个非常大且连续的并由同类型数据组成的内存区域,所以可以构造一个比普通列表大得多的数组,并且灵活高效地对数组中所有的元素进行并行化操作。

 一、并行化思想

示例1:构建10000个元素的普通列表循环求每个元素的平方,使用IPython Notebook的 %timeit 比较其与numpy.arrage()构建列表的运行效率。

代码块1:使用python自带的range()构建

normal_list = range(10000)
%timeit [i**2 for i in normal_list]

# 3.39 ms ± 73.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

代码块2:使用numpy.arange()构建

import numpy as np
np_list = np.arange(10000)
%timeit np_list**2

# 7.57 µs ± 42.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

比较结果:可以很直观地看出运行效率的提升,Numpy数组和普通列表的操作方式不同之处在于:Numpy通过广播机制作用于每一个内部元素,是一种并行化执行的思想,而普通list则作用于整体。

示例2:观察*3分别作用在普通列表与Numpy数组的区别。

代码块1:*3 操作被运作在Numpy数组的每一个元素上

np_list = np.ones(5) * 3
print(np_list)

# [3. 3. 3. 3. 3.]

代码块2:*3 操作认为是整体性操作

normal_list = [1, 1, 1, 1, 1] * 3
print(normal_list)

# [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

二、初始化操作

 常用的初始化np array的方式:

# 100个0
np.zeros(100)

# shape: 3行2列 全是0
np.zeros((3,2))

# shape: 3行2列 全是1
np.ones((3,2))

# shape: x=2, y=3, z=3 值随机
np.empty((2, 3, 3))

# 初始化序列与np_list一样的shape,值全是1
np.ones_like(np_list)

# 初始化序列与np_list一样的shape,值全是0
np.zeros_like(np_list)

# eye()得到对角线全是1的单位矩阵
np.eye(3)
print(np.eye(3))
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]

也可以将普通list作为参数,通过np.array来初始化np array:

data = [[1,2,3,4],[5,6,7,8]]
arr_np = np.array(data)
arr_np

# array([[1, 2, 3, 4],
#       [5, 6, 7, 8]])

还有一个常用的初始化方法为 linspace(),可以实现等间隔生成指定个数元素的序列:

# 示例在0~1之间等间隔生成10个元素的序列:
np.linspace(0,1,10)

# array([0.        , 0.11111111, 0.22222222, 0.33333333, 0.44444444,
#       0.55555556, 0.66666667, 0.77777778, 0.88888889, 1.        ])

随机生成200只股票504个交易日服从正态分布的涨跌幅数据(504=252x2是两年每股交易日总数,交易日的数量越多,股票的数量越多,生成的数据越服从正态分布):

stock_cnt = 200
view_days = 504
stock_day_change = np.random.standard_normal((stock_cnt,view_days))
print(stock_day_change.shape)
print(stock_day_change[0:1,:5])

# (200, 504)
# [[ 1.0289344   1.77915799  0.83710744 -0.60646853 -0.18224675]]

三、索引选取和切片选择

上面代码中 stock_day_change[0:1,:5] 选取了第一只股票前5个交易日涨跌幅的数据就是一种选取。

# tmp = a
tmp = stock_day_change[0:2, 0:5].copy()
# a = b
stock_day_change[0:2, 0:5] = stock_day_change[-2:, -5:]
# b = tmp
stock_day_change[-2:, -5:] = tmp
# view result
stock_day_change[0:2, 0:5], stock_day_change[-2:,-5:]

输出如下:

(array([[-0.06369542, -0.7972173 , -0.57146662, -0.68044207,  1.57640019],
        [ 0.34902178, -0.10373248,  0.99236321, -0.80271732,  0.34376734]]),
 array([[ 1.0289344 ,  1.77915799,  0.83710744, -0.60646853, -0.18224675],
        [-1.57926826,  1.19324891,  2.36893525,  1.07212257, -0.70798104]]))

四、数据转换与规整

数据进行类型转换的目的,有时是为了规整数据,有时可以通过类型转换进一步得到有用的信息。

示例1:使用astype(int)将涨跌幅转换为int后的结果,可以更清晰地发现涨跌幅数据两端的极限值。

print(stock_day_change[0:2, 0:5])
stock_day_change[0:2, 0:5].astype(int)

输出如下:

[[-0.06369542 -0.7972173  -0.57146662 -0.68044207  1.57640019]
 [ 0.34902178 -0.10373248  0.99236321 -0.80271732  0.34376734]]
array([[0, 0, 0, 0, 1],
       [0, 0, 0, 0, 0]])

示例2:规整float的数据,如保留两位小数,可使用np.around()函数。

np.around(stock_day_change[0:2, 0:5], 2)

输出如下:

array([[-0.06, -0.8 , -0.57, -0.68,  1.58],
       [ 0.35, -0.1 ,  0.99, -0.8 ,  0.34]])

示例3:处理缺失数据,Numpy中的np.nan代表缺失,这里手工使切片中的第一个元素变为nan。

tmp_test = stock_day_change[0:2, 0:5].copy()
tmp_test[0][0] = np.nan
tmp_test

输出如下:

array([[        nan, -0.7972173 , -0.57146662, -0.68044207,  1.57640019],
       [ 0.34902178, -0.10373248,  0.99236321, -0.80271732,  0.34376734]])

示例4:pandas中的dropna()和fillna()等方式更适合na处理,可以使用np.nan_to_num()函数来用0填充nan。

tmp_test = np.nan_to_num(tmp_test)
tmp_test

输出如下:

array([[ 0.        , -0.7972173 , -0.57146662, -0.68044207,  1.57640019],
       [ 0.34902178, -0.10373248,  0.99236321, -0.80271732,  0.34376734]])

五、逻辑条件进行数据筛选

示例1:找出切片内涨幅超过0.5的股票时段,通过输出结果可以看到返回的mask是bool的数组。

mask = stock_day_change[0:2, 0:5] > 0.5
mask

# array([[False, False, False, False,  True],
#       [False, False,  True, False, False]])

mask的使用示例如下:

tmp_test = stock_day_change[0:2,0:5].copy()
tmp_test[mask]

# array([1.57640019, 0.99236321])

实际代码中一般会一行写完,比如找出tmp_test切片中 >0.5 的元素并且赋值为1:

tmp_test[tmp_test > 0.5] = 1
tmp_test

# array([[-0.06369542, -0.7972173 , -0.57146662, -0.68044207,  1.        ],
#       [ 0.34902178, -0.10373248,  1.        , -0.80271732,  0.34376734]])

示例2:针对多重筛选条件,可以使用 |、&完成复合的逻辑,注意需要使用括号奖每一个条件括起来,否则会出错。

tmp_test = stock_day_change[-2:,-5:]
print(tmp_test)
tmp_test[(tmp_test > 1) | (tmp_test < -1)]

输出如下:

[[ 1.0289344   1.77915799  0.83710744 -0.60646853 -0.18224675]
 [-1.57926826  1.19324891  2.36893525  1.07212257 -0.70798104]]
array([ 1.0289344 ,  1.77915799, -1.57926826,  1.19324891,  2.36893525,
        1.07212257])

六、通用序列函数

1、np.all()函数:判断序列中的所有元素是否全部是True,即对bool序列进行与操作。

np.all(stock_day_change[0:2,0:5] > 0)

# False

2、np.any()函数:判断序列中是否有元素为True,即对bool序列进行或操作。

np.any(stock_day_change[0:2,0:5] > 0)

# True

3、maximum()与minimun()函数:对两个序列对应的元素两两比较,maximum()结果集取大,相对使用minimum()为取小的结果集。

np.maximum(stock_day_change[0:2,0:5],stock_day_change[-2:,-5:])

# array([[ 1.0289344 ,  1.77915799,  0.83710744, -0.60646853,  1.57640019],
#       [ 0.34902178,  1.19324891,  2.36893525,  1.07212257,  0.34376734]])

4、np.unique()函数:序列中数值唯一且不重复的值组成新的序列。

change_int = stock_day_change[0:2,0:5].astype(int)
print(change_int)
np.unique(change_int)

# [[0 0 0 0 1]
#  [0 0 0 0 0]]

# array([0, 1])

5、np.diff()函数:diff()执行的操作是前后两个临近数值进行减法运算。默认情况下 axis=1,axis代表操作轴向。

# axis = 1
print(stock_day_change[0:2,0:5])
np.diff(stock_day_change[0:2,0:5])


# array([[-0.06369542, -0.7972173 , -0.57146662, -0.68044207,  1.57640019],
#        [ 0.34902178, -0.10373248,  0.99236321, -0.80271732,  0.34376734]]

# array([[-0.73352188,  0.22575068, -0.10897545,  2.25684226],
#        [-0.45275427,  1.0960957 , -1.79508054,  1.14648466]])

下面同样使用diff()函数作用于相同的序列,但是axis=0,示例如下:

# axis = 0
print(stock_day_change[0:2, 0:5])
np.diff(stock_day_change[0:2, 0:5],axis=0)

# [[-0.06369542 -0.7972173  -0.57146662 -0.68044207  1.57640019]
# [ 0.34902178 -0.10373248  0.99236321 -0.80271732  0.34376734]]
# array([[ 0.4127172 ,  0.69348482,  1.56382983, -0.12227525, -1.23263285]])

6、np.where()函数:where()函数的语法句有点类似Java中的三目运算符,第一个参数是一个条件表达式,如果成立走第二个参数,否则走第三个参数。特点是条件表达式的结果是一组序列,而不是一个。

示例1:将涨幅大于0.5的标识为1,其他的都为0。

tmp_test = stock_day_change[-2:,-5:]
print(np.where(tmp_test > 0.5, 1, 0))

# [[1 1 1 0 0]
# [0 1 1 1 0]]

示例2:将涨幅大于0.5的标识为1,其他的都保持不变。

print(np.where(tmp_test > 0.5, 1, tmp_test))

# [[ 1.          1.          1.         -0.60646853 -0.18224675]
#  [-1.57926826  1.          1.          1.         -0.70798104]]

如果逻辑表达式是复合逻辑条件,则使用 np.logical_and()和np.logical_or()函数。

示例3:将序列中的值大于0.5并且小于1的赋值为1,否则赋值为0:

np.where(np.logical_and(tmp_test > 0.5, tmp_test < 1), 1, 0)

# array([[0, 0, 1, 0, 0],
#        [0, 0, 0, 0, 0]])

示例4:将序列中的值大于0.5或者小于-0.5的赋值为1,否则赋值为0:

np.where(np.logical_or(tmp_test > 0.5, tmp_test < -0.5),1,0)

# array([[1, 1, 1, 1, 0],
#        [1, 1, 1, 1, 1]])

P.S. np.where()函数在很多量化数据分析场景中起着很大作用,需要理解并掌握这种语法方式。

七、数据本地序列化操作

np.save()函数可以轻松地将Numpy序列持久化保存。注意不需要添加后缀,save内部会自动生成.npy文件,示例如下:

np.save('./stock_day_change',stock_day_change)

 读取只需要使用np.load()函数即可,但需要注意参数文件名要加上npy后缀,示例如下:

stock_day_change = np.load('./stock_day_change.npy')
stock_day_change.shape

# (200, 504)

 

你可能感兴趣的:(Python量化分析,python,numpy)