Python.Numpy极简入门

版权声明:版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/sinat_28576553/article/details/82535194

Numpy库一直在用,但从没有去了解过numpy到底是个什么东西,属于知其然但不知其所以然的境界,虽然也没什么大碍,但今天看到某本书里有介绍,看了一下,觉得还不错,可以算是个简单入门吧,所以依照书上的框架复述一遍,写了这篇博文。

目录

1. Numpy简介

2. ndarray对象

2.1 数组的创建

2.2 数组的形状获取和改变

2.3 数组元素类型的指定

3. ufunc对象

3.1 ufunc示例-sin()

3.2 性能比较

3.3 ufunc示例-add()

3.4 ufunc的广播机制

3.5 ogrid对象


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. ]]
"""

本文完。如果错误,欢迎指出。如有想法,欢迎交流。但不接受批评,毕竟没有钱拿。

猜你喜欢

转载自blog.csdn.net/sinat_28576553/article/details/82535194