本节概要
Numpy详解
安装
Numpy的安装已经不想多说。。在确保pip或pip3的路径被添加到系统环境变量里面之后,就可以直接用下面语句进行安装。
pip install numpyor pip3 install numpy
当然PyCharm里面也可以用搜索的方式安装。
Numpy是什么?
Numpy(Numerical Python的简称)是高性能科学计算和数据分析的基础包。是很多高级工具的构建基础。部分功能如下:
1、ndarray,一个具有矢量算术运算和复杂关闭能力的快速节省空间的多维数组
2、用于对整组数据进行快速运算的标准数学函数(无需编写循环!!!!)
3、用于读写磁盘数据的工具以及用于操作内存映射文件的工具。
4、线性代数,随机数以及傅里叶变换功能
5、用于继承C、C++、Fortran等语言编写的代码的工具
最后一点也就是从生态系统角度来看最重要的一点,由于Numpy提供了一个简单易用的C的API,因此很容易将数据传递给由低级语言编写的外部库,外部库也能以NumPy数组的形式将数据返回给Python。这个功能使Python成为一种包装C/C++/Fortran历史代码库的选择,并使被包装库有一个动态的,易用的接口。
a、NumPy的ndarray (多维数组对象)
ndarra对象是一个快速灵活的大数据集容器。
创建
import numpy as np data1 = [6, 7.5, 8, 0, 1] arr1 = np.array(data1) data2 = [[1,2,3],[4,5,6]] arr2 = np.array(data2) print(arr2.ndim) # dimension 维度 print(arr2.shape) # 多维行列参数 print(arr2.dtype) # 数据类型
其它创建数组:zeros 和 ones 可以创建指定长度或形状的全0或者全1数组
arr3 = np.zeros((3, 6))arr4 = np.ones(10)
arr5 = np.empty((10,2)) ==> 并不会返回全0数组结构。很多情况下都是返回未初始化的垃圾值 arr6 = np.arange(15) ==> [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
b、dtype
设置:arr1 = np.array([1,2,3], dtype=np.float64)转换:arr2 = arr1.astype(np.float32)
如果浮点型转换成整型,小数位会被截断;如果写的是float,而不是np.float64,NumPy会聪明的将Python类型映射到等价的dtype上
NumPy 如此强大、灵活的原因之一就是dtype,多数情况下,它直接映射到相应的机器表示,这使得"读写磁盘上的二进制数据流" 以及 "集成低级语言代码(c,Fortran)"等工作变得简单dtype的另类用法:
int_arr = np.arange(10)calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64)int_arr.astype(calibers.dtype) ==> array([0., 1., 2., 3., ....])
也可以用简介的类型代码来表示dtype:
empty_unit32 = np.empty(8, dtype='u4') ==> dtype=unit32
c、数组跟标量的运算(广播 运算)
arr = np.array([[1,2,3],[4,5,6]]) arr * arr ==> [[1,4,9],[16,25,36]]arr - arr ==> [[0,0,0],[0,0,0]]1/arr arr ** 0.5 同理
d、索引与切片
arr = np.array(10) ==> [0,1,2,3,4,5,6,7,8,9] arr[5:8] ==> [5,6,7]arr[5:8] = 12 ==> [12,12,12] 广播赋值
这里我们再看一下原始数组
arr ==> [0,1,2,3,4,12,12,12,8,9]
跟python最主要的区别就是,NumPy数组的切片是原始数组的视图。也就意味着数组不会被复制,视图上的任何修改都会直接反映到源数组上
arr_slice = arr[5:8]arr_slice[1] = 12345 arr ==> [0,1,2,3,4,12,12345,12,8,9]arr_slice[:] = 64 arr ==> [0,1,2,3,4,64,64,64,8,9]
如果不想修改到源数组,真正要的是一份副本,则需要使用copy(深copy)
arr[5:8].copy()
对于二维数组,各索引位置上的元素不再是标量而是一维数组
arr = np.array([[1,2,3],[4,5,6],[7,8,9]])arr[2] ==> [7,8,9] 维度为2的数组或者维度索引为2的数组
访问二维数组的元素:
arr[0][2]arr[0,2]这是两种等价的方式
多维数组索引
arr = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])arr[0] ==> [[1,2,3],[4,5,6]] 标量值跟数组都可以赋值给arr[0]old_value = arr[0].copy()arr[0] = 1 ==> arr([[[1,1,1],[1,1,1]],[[7,8,9],[10,11,12]]])arr[0] = old_value ==> arr([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])以此类推arr[1,0] ==> [7,8,9]切片索引arr = np.array([[1,2,3],[4,5,6],[7,8,9]])arr[:2] ==> [[1,2,3],[4,5,6]]arr[:2,1:] ==> [[2,3],[5,6]]arr[1,:2] ==> [4,5]arr[2,:1] ==> [7]只有冒号表示选取整个轴arr[:,:1] ==>[[1],[4],[7]]
e、布尔型索引
总是创建数据副本,即使是返回一模一样的数组也是
names = np.array(['dandy','bob','renee','will','crystal','sam','sam'])data = random.randn(7,4) names=='dandy' ==> [ True False False False False False False]data[names=='dandy'] ==> [[ 0.64963011 -0.35657571 0.78491052 -0.78356789]] 布尔数组的长度跟被索引的轴的长度必须一致
同时布尔值数组跟切片,整数可以混用
data[names=='dandy', 2:] ==>[[xx,xx],[xx,xx]]data[names=='dandy', 3] ==>[xx,xx] dandy以外的其他值:!= 或者- (条件否定)data[names != 'dandy']data[-(names == 'dandy')]
与或非 ==> &, |, ! (关键字and or无效)
mask = (names == 'dandy')|(names == 'bob')data[mask]
布尔型数组设置值
data[data>0] = 0 ==>数组中所有大于零的全部赋值为0data[names!='dandy'] = 7 ==>数组布尔索引并赋值
f、花式索引
复制数据到新数组中
arr = np.empty((8,4))for i in range(8): arr[i] = i arr([[0,0,0,0],...,[7,7,7,7]]) arr[[4,3,0,6]] 取arr的4,3,0,6行 [[4,4,4,4],[3,3,3,3],[0,0,0,0],[6,6,6,6]]
负数索引 则从末尾开始取值
重塑
arr = np.arange(32).reshape((8,4)) ==> 重塑 [[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11] [12 13 14 15] [16 17 18 19] [20 21 22 23] [24 25 26 27] [28 29 30 31]]
arr[[1,5,7,2],[0,3,1,2]] ==> (1,0),(5,3),(7,1),(2,2)位置坐标 输出: [4,23,29,10] arr[[1,5,7,2]][:,[0,3,1,2]]
输出:
[[ 4 7 5 6] [20 23 21 22] [28 31 29 30] [ 8 11 9 10]]也可以用np.ix_函数来去数组
arr[np.ix_([1,5,7,2],[0,3,1,2])] [[ 4 7 5 6] [20 23 21 22] [28 31 29 30] [ 8 11 9 10]]
g、数组转置跟轴对换 (返回的是源数据的视图)
arr = np.arange(15).reshape((3,5))
转置 arr.T
矩阵内积:
np.dot(arr.T, arr)
高维数组
arr = np.arange(16).reshape((2,2,4)) [[[ 0 1 2 3] [ 4 5 6 7]] [[ 8 9 10 11] [12 13 14 15]]] 轴对换 arr = np.arange(16).reshape((2,2,4)) print(arr.transpose((1,0,2))) [[[ 0 1 2 3] [ 8 9 10 11]] [[ 4 5 6 7] [12 13 14 15]]] 转置 arr.swapaxes(1,2) [[[ 0 4] [ 1 5] [ 2 6] [ 3 7]] [[ 8 12] [ 9 13] [10 14] [11 15]]]
h、通用函数
arr = np.arange(10)np.sqrt(arr) => 平方根np.exp(arr) => 指数幂(e) maximum x = randn(8)y = randn(8)np.maximum(x,y) ==> 返回元素级最大值的一维数组 arr = randn(7)*5np.modf(arr) ==> 返回整数数组跟小数数组
i、数组的数据处理
points = np.arange(-5,5,0.01)meshgrid接受两个一维数组,产生两个二维数组xs,ys = np.meshgrid(points,points)
求值运算
z = np.sqrt(xs**2, ys**2) ==> 因xs等于ys;则z=根号2xs
where函数(三元表达式)
xarr = np.array([1.1,1.2,1.3,1.4,1.5])yarr = np.array([2.1,2.2,2.3,2.4,2.5])cond = np.array([True, False, False, False, False])
当cond为True,选取xarr;false则yarr
result = np.where(cond, xarr, yarr) => [1.1,2.2,2.3,2.4,2.5]
where的第二个跟第三个参数不必是数组,标量值也可以
arr = randn(4,4)np.where(arr>0,2,-2) => 随机数组大于0的为2,小于0的为-2np.where(arr>0,2,arr) => 随机数组大于0的为2,小于0的不变
j、数学跟统计方法
arr = np.random.randn(5,4)
求平均值2种方法
arr.mean() np.mean(arr)
sum
arr.sum()
mean跟sum这类函数可以接收一个参数axis(用于计算该轴向上的统计值)
axis不设置 对m*n个数求值,返回一个实数axis = 0 关注列,压缩成一行 对各列求值,返回1*n矩阵axis = 1 关注行,压缩成一列 对各行求值,返回m*1矩阵
cumsum & cumprod (不聚合,产生一个由中间结果组成的数组)
arr = np.array([[0,1,2],[3,4,5],[6,7,8]])arr.cumsum(0) ==> 行往下 累加结果 [[ 0 1 2] [ 3 5 7] [ 9 12 15]]arr.cumprod(1) ==> 列往右 累乘结果 [[ 0 0 0] [ 3 12 60] [ 6 42 336]]
k、布尔型数组方法
arr = randn(100)(arr>0).sum() ==> 统计正值
bools = np.array([True, False, False, False])bools.any() 测试数组是否存在True结果 Truebools.all() 数组是否全部都是True any 跟 all 也能用于非布尔型数组,非0元素将被当做True
l、排序
产生数组副本
一维数组 arr = randn(8) arr.sort()二维数组 arr = randn(5,3) arr.sort(1) ==> 关注行内排序 选取分位数 large_arr =randn(1000) large_arr.sort() ==> 排序 large_arr[int(0.05 * len(large_arr))] 5%分位数
m、唯一性及其他逻辑
names = np.array(['dandy','bob','renee','will','crystal','sam','sam']) np.unique(names) ==> 去重,string,int都可以
我们知道python的列表是自动去重的,所以
sorted(set(names))
测试一个数组值在另一个数组中的成员资格,返回布尔数组
values = np.array([6,0,0,3,2,5,6])np.in1d(values, [2,3,6])array([True,False.....,True])
n、数组文件的输入输出
数组文件 保存 arr = np.arange(10) np.save('test', arr) 没有扩展名.npy,会自动补上 读取 arr = np.load('test') ==> 获得的是一个数组 压缩 np.savez 类型(npz) 可以将对多个数组保存到一个压缩文件中,将数组以关键字形式传入即可: np.savez('array_archive.npz', a=arr1, b=arr2) 加载.npz文件时,你会得到一个类似字典的对象,该对象会对各个数组延迟加载 arch = load('array_archive.npz') arch['b'] ==> arr2数组对象 文本文件 arr = np.loadtxt('arr_ay.txt', delimiter=",") 逗号分隔的二维数组 np.savetxt跟上面的差不多
np.savetxt执行的是相反操作,将数组写到以某种分隔符隔开的文本文件中。getfromtxt跟loadtxt差不多,只不过它面向的是结构化数组和缺失数据处理
o、线性代数
线性代数是任何数组库的重要组成部分。通过*对两个二维数组相乘得到的是一个元素级的积。因此,Numpy提供了一个用于矩阵乘法的dot函数
x = np.array([[1.,2.,3.],[4.,5.,6.,]])y = np.array([[6.,23.],[-1,7],[8,9]])print(x)print(y)print(x.dot(y)) # 相当于np.dot(x,y)
一个二维数组跟一个大小合适的一维数组的矩阵点积运算之后得到一个一维数组:
np.dot(x, np.ones(3)) [ 6. 15.]
numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西。跟MATLAB和R语言所使用的的是相同的行业标准级的Fortran库,如:BLAS、LAPACK、IntelMKL
实例:
from numpy.linalg import inv,qrX = np.random.randn(5,5)mat = X.T.dot(X) # X和X的转置矩阵的点积print(inv(mat)) # 逆矩阵
输出:
[[ 2.75183656 -2.3478836 -2.61320548 -1.04234063 0.93357253] [-2.3478836 2.74218612 2.3537533 1.45492871 -1.03972442] [-2.61320548 2.3537533 2.75010067 1.21183773 -1.0909068 ] [-1.04234063 1.45492871 1.21183773 1.35983185 -0.95369447] [ 0.93357253 -1.03972442 -1.0909068 -0.95369447 0.90587929]]
继续:(就不要问为什么逆矩阵乘本身得到这个结果了。。。看线性代数去吧)
print(mat.dot(inv(mat)))q, r = qr(mat) # 至于qr分解,暂时还没看懂什么意思0.0,楼主也在研究线性代数print(r)
输出:
[[ 1.00000000e+00 6.93889390e-18 1.80411242e-16 -2.42861287e-17 0.00000000e+00] [ -5.55111512e-17 1.00000000e+00 4.44089210e-16 1.38777878e-17 3.88578059e-16] [ 0.00000000e+00 0.00000000e+00 1.00000000e+00 -2.22044605e-16 -4.44089210e-16] [ 1.66533454e-16 -1.11022302e-16 0.00000000e+00 1.00000000e+00 -2.22044605e-16] [ -2.22044605e-16 -2.22044605e-16 0.00000000e+00 3.33066907e-16 1.00000000e+00]][[-11.19953315 -11.83491161 8.73455769 1.36529151 -2.31283099] [ 0. -6.4372489 4.65628801 -1.1686483 -3.36988755] [ 0. 0. -9.98879178 5.88445016 9.82750912] [ 0. 0. 0. -5.78724596 2.23174313] [ 0. 0. 0. 0. 1.54121747]]
p、随机数生成
numpy.random模块对Python内置的random进行了补充,增加了一些用于高效生成多种概率分布的样本值函数。例如,你可以用normal来得到一个标准正态分布的4*4样本数组:
samples = np.random.normal(size=(4, 4))print(samples)
[[-0.14196335 -0.07402951 -0.02838652 -1.67057212] [ 0.10550666 0.55619815 0.08196452 -0.03504712] [ 0.23530906 0.05405759 0.87008255 0.52502829] [ 0.22493807 0.25954076 1.57337562 0.05819842]]
而Python内置的random模块则只能一次生成一个样本值。从下面的测试结果可以看出,如果需要产生大量样本值,numpy.random快了不止一个数量级:
from random import normalvariateN = 10000%timeit samples = [normalvariate(0, 1) for _ in xrange(N)]%timeit np.random.normal(size=N)
实例:随机漫步
从0开始,步长1和-1出现的概率相等。
为了展示出一个对比度,我们先用纯python写:
import randomposition = 0walk = [position]steps = 1000for i in range(steps): step = 1 if random.randint(0, 1) else -1 position += step walk.append(position)
这时我们用numpy.random模块一次性随机产生1000个结果,将其分别设置为1和-1,然后求累积和:
import numpy as npnsteps = 1000 # 定义步数draws = np.random.randint(0, 2, size=nsteps) # 生成随机数steps = np.where(draws>0, 1, -1) # 三元运算walk = steps.cumsum() # 累加
有了这些数据,就可以做一些统计工作了,比如求取最大最小值:
walk.min() # 最小值walk.max() # 最大值
现在我们统计一个有点难度,复杂的统计任务,首次穿越时间,即随机漫步过程中第一次到达长度10(绝对值),需要多久?
思路: np.abs(walk) > 10可以得到一个布尔型数组,表示的是距离是否大于等于10;我们需要的是第一个达到的索引。可以用argmax来解决这个问题,它返回的是该布尔型数组第一个最大值的索引(True就是最大值):
(np.abs(walk) >= 10).argmax()
这里的argmax并不是很高效,因为无论如何它都会对数组进行完全扫描,其实没这个必要。
一次模拟多个随机漫步
import numpy as npnwalks = 5000nsteps = 1000 # 定义步数draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 生成随机数 数组steps = np.where(draws>0, 1, -1) # 三元运算walks = steps.cumsum(1) # 行累加
计算所有随机漫步过程中的最大值,最小值:
walks.max()walks.min()
我们现在来计算下达到30步距离的最小穿越时间,因为不是所有的随机漫步都会满足条件,我们需要一个any方法来检验
import numpy as npnwalks = 5000nsteps = 1000 # 定义步数draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 生成随机数 数组steps = np.where(draws>0, 1, -1) # 三元运算walks = steps.cumsum(1) # 行累加hits = (np.abs(walks) >= 30).any(1) # 关注每行print(hits)# 输出结果[ True True True ..., True True False]print(hits.sum()) # 达到30或-30的总数量# 输出 3358
然后利用这个布尔型数组选出穿越了30的随机漫步(行),并调用argmax在轴1上获取穿越时间:
cross_times = (np.abs(walks[hits]) >= 30).argmax(1)print(cross_times.mean()) # 获取平均值
尝试使用其他方式得到漫步数据。只需使用不通的随机数生成函数即可,如normal用于生成指定均值和标准差的正态分布数据
steps = np.random.normal(loc=0, scale=0.25, size=(nwalks,nsteps))