Numpy库一直在用,但从没有去了解过numpy到底是个什么东西,属于知其然但不知其所以然的境界,虽然也没什么大碍,但今天看到某本书里有介绍,看了一下,觉得还不错,可以算是个简单入门吧,所以依照书上的框架复述一遍,写了这篇博文。
目录
1. Numpy简介
Numpy为Python带来了多维数组功能,并提供丰富的函数库来处理这些数组。他将常用的数学函数进行了数组化,使得这些数学函数能够直接对数组进行操作,将本来需要在Python级别进行的循环,放到C语言的运算中,明显的提高了程序的运行速度。这点是很重要的,因为Python最大的每种不足就是性能问题。
在Python中自带有list(列表)对象,用来保存一组“值”,我们可以将list近似的当作数组来使用,但是列表中的元素可以是任何对象,比如['a',0.01,{"age":18}],因此列表中所保存的是对象的指针,这样为了保存一个简单的[1,2,3],需要3个指针和三个整数对象,在时间和空间上都造成了极大浪费。
为了克服list的弊端,就可以使用Numpy库所带来的数组。
Numpy库提供了两种基本的对象,一是ndarray(N-dimensional array object)是存储单一数据类型的多维数组。二是ufunc(universal function object)是能够对数组进行处理的函数。
2. ndarray对象
2.1 数组的创建
通过给array函数传递Python的序列对象来创建数组,如果传递的是多层嵌套序列,将创建多维数组。示例如下:
import numpy as np
a = np.array( [1,2,3,4] )
b = np.array( (5,6,7,8) )
c = np.array( [[1,2,3,4],[4,5,6,7],[7,8,9,10]] )
print(a) #[1 2 3 4]
print(b) #[5 6 7 8]
print(c)
'''
[[ 1 2 3 4]
[ 4 5 6 7]
[ 7 8 9 10]]
'''
2.2 数组的形状获取和改变
数组的大小可以通过数组的shape属性获得。示例如下:
#紧接上面的代码
print(a.shape) #(4,)
print(c.shape) #(3, 4)
数组a的shape只有一个元素,因此可以判定为一维数组,而数组c的第0轴长度为3,第1轴的长度为4,是3*4的数组。
可以通过修改数组的shape属性,在保持元素个数不变的情况下,改变数组每个轴的长度。相当于做了重排。
c.shape=4,3
print(c)
'''
[[ 1 2 3]
[ 4 4 5]
[ 6 7 7]
[ 8 9 10]]
'''
当某个轴的元素为-1时,将根据元素的个数自动计算此轴的长度,因此下面的程序将c的shape改为了(2,6)。
c.shape=2,-1
print(c)
'''
[[ 1 2 3 4 4 5]
[ 6 7 7 8 9 10]]
'''
使用数组的reshape方法,可以创建一个改变了尺寸的新数组,原数组的shape保持不变。
此处要注意的是,reshape方法和shape属性的区别。
d=a.reshape((2,2))
e=a.reshape(2,2) #两种方式都可以
print(d)
print(e)
print(a)
'''
[[1 2]
[3 4]]
[[1 2]
[3 4]]
[1 2 3 4]
'''
数组a和数组b,共享数据存储内存区域,因此修改其中任何一个数组的元素都会同时修改另一个。
print(a) #[1 2 3 4]
a[1]=100 #将数组a的第二个元素给为100
print(a) #[ 1 100 3 4]
print(d)
'''
[[ 1 100] 数组d中也被改变了
[ 3 4]]
'''
2.3 数组元素类型的指定
数组内部元素的类型可以通过dtype属性获得。上面例子中,参数序列的元素都是整数类型,因此数组内元素类型也是整形。还可以在具体创建数组时,显示地指定元素类型,通过dtype参数。
temp01=np.array( [[1,2,3,4],[4,5,6,7],[7,8,9,10]],dtype=np.float)
print(temp01) #浮点数类型
'''
[[ 1. 2. 3. 4.]
[ 4. 5. 6. 7.]
[ 7. 8. 9. 10.]]
'''
temp02=np.array( [[1,2,3,4],[4,5,6,7],[7,8,9,10]],dtype=np.complex)
print(temp02) #复数类型
'''
[[ 1.+0.j 2.+0.j 3.+0.j 4.+0.j]
[ 4.+0.j 5.+0.j 6.+0.j 7.+0.j]
[ 7.+0.j 8.+0.j 9.+0.j 10.+0.j]]
'''
array方法里面的参数是一个Python序列,也就是说,是先创建了一个Python序列,然后用array函数将其转换为数组。这样效率不高,Numpy还提供了,许多特定的方法来创建数组,例如linspace。
3. ufunc对象
Python里面提供了许多universal function ,可以直接对数组的每个元素都进行操作。并且由于Numpy内置的许多ufunc函数都是基于C语言编写的,因此有较好的性能。
3.1 ufunc示例-sin()
import numpy as np
x=np.linspace(0,2*np.pi,10)
print(type(x)) #<class 'numpy.ndarray'> x是数组类型
print(x)
'''
[0. 0.6981317 1.3962634 2.0943951 2.7925268 3.4906585
4.1887902 4.88692191 5.58505361 6.28318531]
'''
y=np.sin(x)
print(type(y)) #<class 'numpy.ndarray'> 结果y也是数组类型
print(y)
'''
[ 0.00000000e+00 6.42787610e-01 9.84807753e-01 8.66025404e-01
3.42020143e-01 -3.42020143e-01 -8.66025404e-01 -9.84807753e-01
-6.42787610e-01 -2.44929360e-16]
'''
3.2 性能比较
用下面的程序比较numpy.sin和math.sin的计算速度。
import time
import math
import numpy as np
x = [i * 0.001 for i in range(10000000)]
start = time.clock()
for i,t in enumerate(x):
x[i] = math.sin(t)
print("math.sin: ",time.clock() - start)
#math.sin: 2.5702852179138964
x = [i * 0.001 for i in range(10000000)]
x=np.array(x)
start = time.clock()
t=np.sin(x)
print("numpy.sin: ",time.clock() - start)
#math.sin: 2.526883316363891
#numpy.sin: 0.10765761889063619
运行了10000000次正弦运算,numpy.sin速度比math.sin的速度快上一个数量级,这是因为numpy.sin可以一次性对一个数组进行计算。也就是说,numpy.sin是在C语言级别上进行循环的,而math.sin是在Python级别进行循环,众所周知,Python的一大弊端就是性能不佳,因此numpy.sin的运行速度自然远胜math.sin。
当然numpy.sin同样也支持对单个数值求正弦。
3.3 ufunc示例-add()
import numpy as np
a=np.arange(0,4)
print(a,type(a)) #[0 1 2 3] <class 'numpy.ndarray'>
#arange([start,] stop[, step,], dtype=None)根据start与stop指定的范围以及step设定的步长,生成一个 ndarray
b=np.arange(1,5)
print(b,type(b))
c=np.add(a,b)
print(c) #[1 3 5 7]
3.4 ufunc的广播机制
使用ufunc函数对两个数组进行计算时,ufunc函数会对这两个数组的对应元素进行计算,因此要求着两个函数的形状相同。如果形状不同,会进行如下广播处理:
1).让所有输入数组都向其中维数最多的数组看齐,shape属性中不足的部分通过在前面加1补齐。
2).输出数组的shape属性是输入数组的shape属性在各个轴上的最大值。
3).如果输入数组的某个轴长度为1或与输出数组对应轴的长度相等,这个数组就能够用来计算,否则出错。
4).当输入数组的某个轴长度为1时,沿着这条轴运算时,都用此轴上的第一组值。
示例如下:
首先创建形状为(6,1)的二维数组a:
import numpy as np
a=np.arange(0,60,10).reshape(-1,1)
print(a)
"""
[[ 0]
[10]
[20]
[30]
[40]
[50]]
"""
print(a.shape) #(6, 1)
再创建形状为(5,)的一维数组b。
b=np.arange(0,5)
print(b) #[0 1 2 3 4]
print(b.shape) #(5,)
计算数组a和b的和,得到一个加法表,相当于计算两个数组中所有元素组的和,得到形状为(6,5)的数组。
c=a+b
print(c.shape) #(6, 5)
print(c)
'''
[[ 0 1 2 3 4]
[10 11 12 13 14]
[20 21 22 23 24]
[30 31 32 33 34]
[40 41 42 43 44]
[50 51 52 53 54]]
'''
a是2维数组,b是1维数组,根据规则1),需要让数组b的shape属性向数组a对齐,在数组b的shape属性前面+1,补齐后为(1,5),相当于做如下运算。
b.shape=1,5
print(b) #[[0 1 2 3 4]]
这样一来,做加法运算的两个输入数组的shape属性分别为(6,1)和(1,5),根据规则2),可知输出数组的shape属性为(6,5)。由于数组b的第0轴长度为1,而数组a的第0轴长度为6,因此,为了能够让他们在第0轴上相加,需要将数组b第0轴的长度拓展为6,这相当于:
b=b.repeat(6,axis=0)
print(b)
'''
[[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]
[0 1 2 3 4]]
'''
#同理把a的第1轴拓展为5
a = a.repeat(5,axis=1)
print(a)
"""
[[ 0 0 0 0 0]
[10 10 10 10 10]
[20 20 20 20 20]
[30 30 30 30 30]
[40 40 40 40 40]
[50 50 50 50 50]]
"""
经过上述处理后,数组a和数组b就可以按其对应元素进行相加运算。当然,在执行“a+b"时,Numpy内部并不会真正将长度为1的轴用repeat()进行拓展,这样太浪费空间。由于这样的广播计算很常用,Numpy提供了ogrid对象,用以快速产生能进行广播运算的数组。
3.5 ogrid对象
x,y=np.ogrid[0:5,0:5]
print(x)
"""
[[0]
[1]
[2]
[3]
[4]]
"""
print(y)
"""
[[0]
[1]
[2]
[3]
[4]]
"""
ogrid对象和多维数组一样,用切片元组作为下标,返回的是一组可以用来广播计算的数组。其切片下标有两种形式:
开始值,结束值,步长和“np.arange(开始值,结束值,步长)”类似。
开始值,结束值,长度j,当第三个参数为虚数时,他表示所返回数组的长度,其和np.linspace(开始值,结束值,长度)”类似。
x,y=np.ogrid[0:1:4j,0:1:3j]
print(x)
"""
[[0. ]
[0.33333333]
[0.66666667]
[1. ]]
"""
print(y)
"""
[[0. 0.5 1. ]]
"""
本文完。如果错误,欢迎指出。如有想法,欢迎交流。但不接受批评,毕竟没有钱拿。