NumPy基本操作与常用函数

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

声明

本文涉及的代码基于python 3.6.5 numpy 1.14.3
使用numpy模块之前首先需要在代码之前导入该模块

import numpy as np

文中会用到world_alcohol.txt文件,其中的数据格式如下所示(第一行为标题):

Year,WHO region,Country,Beverage Types,Display Value
1986,Western Pacific,Viet Nam,Wine,0
1986,Americas,Uruguay,Other,0.5
1985,Africa,Cte d'Ivoire,Wine,1.62
1986,Americas,Colombia,Beer,4.27

创建数组

numpy.array()函数的输入参数可以是一个list或者是list of lists。
当输入参数为list时,我们得到一个一维数组。

vector = np.array([5, 10, 15, 20])
print(vector)
# [ 5 10 15 20]

当输入参数为list of lists时,我们可以得到一个矩阵。

matrix = np.array([[5, 10, 15], [20, 25, 30], [35, 40, 45]])
print(matrix)
# [[ 5 10 15]
#  [20 25 30]
#  [35 40 45]]

我们可以使用numpy.arange()方法创建给定区间,固定步长的数组或矩阵,例如:

vector = np.arange(15)
print(vector)  # [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14]

我们还可以使用ndarray的reshape方法得到自己想要形状的矩阵:

matrix = np.arange(21, 81, 3).reshape(4, 5)
print(matrix)
# [[21 24 27 30 33]
#  [36 39 42 45 48]
#  [51 54 57 60 63]
#  [66 69 72 75 78]]

我们还可以利用numpy.zeros()方法和numpy.ones()方法获取全0矩阵和全1矩阵。

z = np.zeros((2, 3))
print(z)
# [[0. 0. 0.]
#  [0. 0. 0.]]
o = np.ones((2, 3), dtype=int)
print(o)
# [[1 1 1]
#  [1 1 1]]

我们还可以利用随机数创建矩阵:

# 利用[0, 1)之间的随机数创建2行3列的矩阵
r = np.random.random_sample((2, 3))
print(r)
# [[0.1013597  0.41281383 0.2252911 ]
#  [0.4636549  0.92180822 0.60799557]]

# 利用[2, 7)之间的随机数创建2行3列的矩阵
r = 5 * np.random.random_sample((2, 3)) + 2
print(r)
# [[2.87507067 6.21924318 5.68927464]
#  [4.96606253 4.44709691 2.54297613]]

# 利用[0, 20)之间的随机整数创建2行3列的矩阵
r = np.random.randint(0, 20, size=(4, 5))
print(r)
# [[19  0  7 15  0]
#  [12  2  6 16  1]
#  [10 15 12 19 11]
#  [11 17 13 12 10]]

# # 利用标准正态分布创建2行2列的矩阵
r = np.random.randn(2, 3)
print(r)
# [[-0.10322776  0.64940133 -0.33607361]
#  [-0.71023991  0.53969707  0.60760233]]

另外,numpy.linspace()方法也可以用来创建数组或矩阵,效果和numpy.arange()类似。

扫描二维码关注公众号,回复: 5379883 查看本文章
lin = np.linspace(2.0, 3.0, num=5)
print(lin)
# [2.   2.25 2.5  2.75 3.  ]

需要注意的是,numpy.linspace()默认的是在闭区间内获取数据,可以设置endpoint=False设置为左闭右开区间。另外,numpy.arange()方法利用step参数设置区间步长,而numpy.linspace()方法利用num参数设置区间内获取的数据个数,默认是50个。
还有很多方法可以创建numpy数组或矩阵,后面发现会继续完善。

shape属性

我们可以使用ndarray.shape属性获取数组中的元素个数或者说数组的形状。

print(type(vector))
print(type(matrix))
print(vector.shape)
print(matrix.shape)
# <class 'numpy.ndarray'>
# <class 'numpy.ndarray'>
# (4,)
# (3, 3)

dtype属性

需要注意的一点是numpy数组中的每个值必须具有相同的数据类型,当利用lists创建数组时或者读取数据到数组中时,numpy将自动确定适当的数据类型。我们可以是使用numpy数组的dtype属性检查数组的数据类型

numbers = np.array([1, 2, 3, 4])
print(numbers.dtype)  # int32
numbers = np.array([1, 2, 3, '4'])
print(numbers.dtype)  # <U11

ndim属性

我们可以用ndim属性获取矩阵的维度。例如:

matrix = np.arange(6).reshape(2, 3)
print(matrix)
# [[0 1 2]
#  [3 4 5]]
print(matrix.ndim)  # 2

size属性

利用size属性可以获取矩阵或数组中的元素总数。例如:

matrix = np.arange(6).reshape(2, 3)
print(matrix.size)  # 6

数组切片

首先利用numpy的genfromtxt方法从文本文件world_alcohol.txt获取数组(矩阵)数据:

world_alcohol = np.genfromtxt('world_alcohol.txt', delimiter=',', dtype='U75', skip_header=1)
print(world_alcohol)
# [['1986' 'Western Pacific' 'Viet Nam' 'Wine' '0']
#  ['1986' 'Americas' 'Uruguay' 'Other' '0.5']
#  ['1985' 'Africa' "Cte d'Ivoire" 'Wine' '1.62']
#  ...
#  ['1987' 'Africa' 'Malawi' 'Other' '0.75']
#  ['1989' 'Americas' 'Bahamas' 'Wine' '1.5']
#  ['1985' 'Africa' 'Malawi' 'Spirits' '0.31']]

关于genfromtxt方法的具体用法这里不展开说,想要查看它的具体用法可以使用以下代码:

print(help(np.genfromtxt))

现在我们对world_alcohol矩阵中的元素进行访问

uruguay_other_1986 = world_alcohol[1, 4]
third_country = world_alcohol[2, 2]
print(uruguay_other_1986)  # 0.5
print(third_country)  # Cte d'Ivoire

可以看到uruguay_other_1986是矩阵中第1行第4列的元素(从0开始索引),third_country是矩阵中第2行第2列的元素。上述对数组元素的访问方式等同于:

uruguay_other_1986 = world_alcohol[1][4]
third_country = world_alcohol[2][2]

上述方法访问了数组或矩阵的某个特定数据,那么我们如何访问数组或矩阵的某些连续数据呢?这个时候可以采用分片的方式,请看代码:

vector = np.array([5, 10, 15, 20])
print(vector[0:3])  # [ 5 10 15]

在上述代码中我们获取了数组中下标大于等于0小于3的元素,分片区间左闭右开。

matrix = np.array([
                    [5, 10, 15],
                    [20, 25, 30],
                    [35, 40, 45]
                 ])
print(matrix[:, 1])  # [10 25 40]

上述代码表示获取矩阵中所有行中的第1个元素,也就是获取矩阵第1列的元素,在数组分片中,:表示某个维度的所有数据。

matrix = np.array([
                    [5, 10, 15],
                    [20, 25, 30],
                    [35, 40, 45]
                 ])
print(matrix[1:3, 0:2])
# [[20 25]
#  [35 40]]

如上述代码所示,利用分片技术,我们就可以获取矩阵中任意区域的元素。
另外,利用分片技术我们还可以快速的获取倒置序列:

vector = np.array([1, 2, 3])
print(vector[::-1])  # [3 2 1]

广播机制

vector = np.array([5, 10, 15, 20])
equal_to_ten = (vector == 10)
print(equal_to_ten)  # [False  True False False]
print(vector[equal_to_ten])  # [10]	返回vector在equal_to_ten中为True的位置的元素

上述代码实现的功能是将vector中的每个元素和10进行比较,如果相等则返回True,如果不相等则返回False,返回结果构成和vector维度相同的bool数组。这里面有个问题是,numpy是如何做到一个数组和单个元素进行比较的?按照常规的做法,我们会采用for循环,将vector中的每个元素和10进行比较。实际上,在这个比较过程中,numpy自动将单个数据10扩展成[10 10 10 10],然后再和vector进行比较,这就是numpy的广播机制。这种机制不仅简化了代码,也为不同形状数组之间的操作提供了很大便利。
下面我们再看几个例子。

a = np.array([
    [1, 2, 3],
    [4, 5, 6]
])
b = np.array([100, 200, 300])
print(a + b)
# [[101 202 303]
#  [104 205 306]]

上述代码中a的维度记作mn,那么b的维度就是1n,b经过广播后被复制成m*n的矩阵,也就是变为

[[100 200 300]
[100 200 300]]

如果b的维度为m1的话,那么b也将被广播为mn的矩阵。

a = np.array([
    [1, 2, 3],
    [4, 5, 6]
])
b = np.array([[100], [200]])
print(a + b)
# [[101 102 103]
#  [204 205 206]]

在上述例子中,b被广播为:

[[100 100 100]
[200 200 200]]

总之,如果两个数组的最后一个维度的大小相符或者其中一个数组的最后一维的大小是1,那么他们就是广播兼容的。广播会在缺失维度和维度大小为1的维度上进行。

向量化

受广播机制启发,在使用numpy进行计算时,我们可以省略掉很多for循环,改为采用矩阵运算的思想去计算,这种思想叫做向量化。向量化在需要大量矩阵运算的深度学习中很实用,能大幅度提升算法的效率。
下面我们可以看一个例子:

import numpy as np
import time

vector_a = 3 * np.random.random_sample((2000, 5000))
vector_b = 7 * np.random.random_sample((2000, 5000))
vector_a_copy = vector_a.copy()
vector_b_copy = vector_b.copy()

tic = time.process_time()
for a in vector_a:
    for b in vector_b:
        a += b
toc = time.process_time()
print("Computation time use for loop = " + str(1000 * (toc - tic)) + " ms")

tic = time.process_time()
vector_a_copy += vector_b_copy
toc = time.process_time()
print("Computation time use vectorization = " + str(1000 * (toc - tic)) + " ms")

# Computation time use for loop = 19375.0 ms
# Computation time use vectorization = 62.5 ms

可以看到同样的计算,for循环耗时是向量化的310倍。
在使用numpy写代码时,我们应当尽量避免使用for循环,而是多多使用numpy提供的广播机制以及函数接口进行向量化运算。
下面我们再看几个例子,假设我们有数据:

x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]

向量内积运算的传统写法:

dot = 0
for i in range(len(x1)):
    dot += x1[i]*x2[i]

向量化实现:

dot = np.dot(x1, x2)

向量外积运算的传统写法:

outer = np.zeros((len(x1), len(x2)))
for i in range(len(x1)):
    for j in range(len(x2)):
        outer[i, j] = x1[i]*x2[j]

向量化实现:

outer = np.outer(x1, x2)

向量对应元素相乘的传统实现:

mul = np.zeros(len(x1))
for i in range(len(x1)):
    mul[i] = x1[i]*x2[i]

向量化实现:

mul = np.multiply(x1, x2)

类型转换

我们可以使用ndarray.astype()对数组的变量类型进行转换,如下所示:

vector = np.array(["1", "2", "3"])
print(vector.dtype)  # <U1
print(vector)  # ['1' '2' '3']
vector = vector.astype(float)
print(vector.dtype)  # float64
print(vector)  # [1. 2. 3.]

特定维度上运算

当我们想要对某个矩阵特定维度(轴)上的数据进行运算时,可以使用axis参数。
比如:

matrix = np.array([
                [5, 10, 15],
                [20, 25, 30],
                [35, 40, 45]
             ])
print(matrix.sum(axis=1))  # [ 30  75 120]

上述代码对矩阵中的元素按行求和。

matrix = np.array([
                [5, 10, 15],
                [20, 25, 30],
                [35, 40, 45]
             ])
print(matrix.sum(axis=0))  # [60 75 90]

上述代码对矩阵中的元素按列求和。

其他常用方法

求均值

a = np.arange(6).reshape(2, 3)
print(a)
# [[0 1 2]
#  [3 4 5]]
print(np.mean(a))  # 2.5
print(np.mean(a, axis=0))  # [1.5 2.5 3.5]
print(np.mean(a, axis=1))  # [1. 4.]

幂、开方、指数与对数

a = np.arange(6).reshape(2, 3)
power_a = np.power(a, 2)
print(power_a)
# [[ 0  1  4]
#  [ 9 16 25]]
print(np.sqrt(power_a))
# [[0. 1. 2.]
#  [3. 4. 5.]]

a = np.array([0, 1, 2])
exp_a = np.exp(a)
print(exp_a)
# [1.         2.71828183 7.3890561 ]
print(np.log(exp_a))
# [0. 1. 2.]
power10 = np.power(10, a)
print(power10)
# [  1  10 100]
print(np.log10(power10))
# [0. 1. 2.]

矩阵变形

a = np.arange(6).reshape(2, 3)
a.resize(3, 2)  # 无返回值,在原矩阵上操作
print(a)
# [[0 1]
#  [2 3]
#  [4 5]]
a = a.reshape(2, 3)  # 返回新形状的矩阵
print(a)
# [[0 1 2]
#  [3 4 5]]
a = a.reshape(2, -1)  # -1表示根据数据量推断剩余的某个维度的大小
print('-1', a)
# [[0 1 2]
#  [3 4 5]]
print(a.T)
# [[0 3]
#  [1 4]
#  [2 5]]

# 将矩阵展开
print(a.ravel())
# [0 1 2 3 4 5]

舍入取整

a = 10 * np.random.random((2, 3))
print(a)
# [[6.49139195 7.53784378 7.96044578]
#  [6.5218208  8.97543018 0.38446087]]
print(np.ceil(a))
# [[7. 8. 8.]
#  [7. 9. 1.]]
print(np.floor(a))
# [[6. 7. 7.]
#  [6. 8. 0.]]
print(np.round(a))
# [[6. 8. 8.]
#  [7. 9. 0.]]

在上述代码中,numpy.round()方法做四舍五入,numpy.ceil()方法获取大于等于每个元素的最小整数,numpy.floor()方法获取小于等于每个元素的最大整数。
注意,因为原始数据是浮点型,所以在调用以上方法后还需要将浮点型数据转换为整型数据。

矩阵合并与分割

a = np.arange(6).reshape((2, 3))
print(a)
# [[0 1 2]
#  [3 4 5]]
b = np.arange(6, 12, 1).reshape(2, 3)
print(b)
# [[ 6  7  8]
#  [ 9 10 11]]

# 横向合并矩阵
print(np.hstack((a, b)))
# [[ 0  1  2  6  7  8]
#  [ 3  4  5  9 10 11]]

# 纵向合并矩阵
print(np.vstack((a, b)))
# [[ 0  1  2]
#  [ 3  4  5]
#  [ 6  7  8]
#  [ 9 10 11]]

c = np.arange(8).reshape(2, 4)
print(c)
# [[0 1 2 3]
#  [4 5 6 7]]

# 纵向平均分割成两部分
print(np.hsplit(c, 2))
# [array([[0, 1],
#        [4, 5]]), array([[2, 3],
#        [6, 7]])]
# 从第2列(从0开始计数)和第3列之前切分
print(np.hsplit(c, (2, 3)))
# [array([[0, 1],
#        [4, 5]]), array([[2],
#        [6]]), array([[3],
#        [7]])]

d = np.arange(12).reshape(4, 3)
print(d)
# [[ 0  1  2]
#  [ 3  4  5]
#  [ 6  7  8]
#  [ 9 10 11]]

# 横向平均分割成两部分
print(np.vsplit(d, 2))
# [array([[0, 1, 2],
#        [3, 4, 5]]), array([[ 6,  7,  8],
#        [ 9, 10, 11]])]
# 从第2行(从0开始计数)和第3行之前切分
print(np.vsplit(d, (2, 3)))
# [array([[0, 1, 2],
#        [3, 4, 5]]), array([[6, 7, 8]]), array([[ 9, 10, 11]])]

浅拷贝与深拷贝

# 利用等号进行赋值,并没有做到拷贝,只是建立了指向同一个对象的两个引用
a = np.arange(12)
print(a)
# [ 0  1  2  3  4  5  6  7  8  9 10 11]
b = a
# a和b只是同一个对象的不同名称
print(b is a)  # True
b.shape = 3, 4
print(a.shape)  # (3, 4)
print(id(a))  # 2280803111840
print(id(b))  # 2280803111840  a和b的id一样,说明是同一个对象

# view 创建了一个新的数组对象来查看另一个数组的数据
c = a.view()
print(c is a)  # False
c.shape = 2, 6
c[0, 4] = 1234
# 修改c的数据,但是a的数据也发生了改变,说明只是浅拷贝
print(a)
# [[   0    1    2    3]
#  [1234    5    6    7]
#  [   8    9   10   11]]
print(id(a))  # 2280803111840
print(id(c))  # 2280804773360  a和c不是同一个对象,但是共享同一块内存区域

# copy方法对数组及其数据进行深入的拷贝
d = a.copy()
print(d is a)  # False
d[0, 0] = 9999
print(d)
# [[9999    1    2    3]
#  [1234    5    6    7]
#  [   8    9   10   11]]
print(a)  # 修改d的数据,a的数据没有发生变化,说明进行了深拷贝
# [[   0    1    2    3]
#  [1234    5    6    7]
#  [   8    9   10   11]]

上述代码中利用“=”赋值的方法只是创建了指向同一个数组对象的不同引用;numpy.view()方法仅仅改变了看待数组数据的方式,通过这种方式创建的两个数组对象虽然是不同的,但是共享同一片内存区域,数据相同,只是解析数据的方式不同;而numpy.copy()方法则完全创建了一个新的对象,使用了不同的内存区域。

最后,非常感谢大家花费时间阅读这篇文章。如果文章中有什么问题,欢迎在评论中指出,我看到了会及时纠正,避免对大家产生误导。

猜你喜欢

转载自blog.csdn.net/hfutdog/article/details/85713291