python数据处理基础之numpy的运算

上一章我们一起学习了numpy库的基础知识,以及np的主要数据结构ndarray

我们都知道,程序 = 数据结构 + 算法,这章中,我们一起学习下基于ndarray这个多维数组的一些基础运算。

本章所说的运算主要是numpy中ndarray的运算。在我们平常的编程工作中,数组的运算应该是用得最多的运算了,我们常常需要把数据放到数组里(也就是给数组赋值),遍历数组,对数组中数据进行处理等。在大数据处理中,数组的运算就更为重要了。下面介绍一些初级的数组运算,随着我们进一步的学习,后面会学习更多更高级运算以及结合统计学或线性代数的知识学习更多运用的场合。

学习时,可以边对照着下面的导图边学习各章内容,这样思路会更清晰。

1 导图

(建议看每一章时,先把此导图打印出来对着看,好让自己对整体内容有个大概的把握与知道自己学习到哪个位置:)我自己做此笔记也是这样,把导图都打印出来,然后对着自己的笔记回顾,看完哪里就去哪里打个勾表示复习过一遍了)

2 简单运算

2.1 算术运算

numpy中数组的算术运算异常地强大,不信?请你想想看如果使用py中基本的list,或是使用c语言的数组等,怎么计算两个数组元素两两相乘?你必须对它们分别遍历对吧?那么看下下面的代码:

多维数组相乘
1
2
3
4
5
6
7
8
arr = np.array([[1,2,3], [4,5,6]]) # 创建一个二维数组2*3
arr = arr * arr
print(arr)

==> out:
[[ 1 4 9]
[16 25 36]]
----------

根本不需要遍历,就这么简单。以此类推,数组的加、减运算也是这么轻松可以完成。

2.2 逻辑运算

对ndarray作逻辑运算,得到的结果也是ndarray每个元素计算之后的结果组成的结果数组。描述运算总是太抽象,还是看看具体示例吧:

多维数组的逻辑运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
arr = np.array([[1,2,3], [4,5,6]]) # 创建一个二维数组2*3
arr = arr > 3
print(arr)

==> out:
1,2,3 [[False False False]
> 3 -->
4,5,6 [ True True True]]
----------

arr = np.array([1,4,8])
arr2 = np.array([5,3,7])
print(arr < arr2) # 这样也是两两元素进行对比,输出结果
==> out:
[ True False False]
----------

2.3 索引与切片

索引与切片是数组求子串的一种非常优雅的运算方式

  • 切片不包括末位元素(如:arr[1:3] 为 arr[1]、arr[2],不包括arr[3])
切片不包括末位元素
1
2
3
arr = np.arange(10) # 生成0-9的数组[0 1 2 3 4 5 6 7 8 9]
print(arr[5]) # 输出 5
print(arr[3:8]) # [3 4 5 6 7], 注意,切片不包括末位元素
  • 切片具有”广播”效应

这是因为numpy为了效率,一般情况下不会对数据进行复制!就是切下来组成新数组的元素有可能会被原数组修改(因为numpy设计来是用于处理大数据的,所以这种通常来说不需要的复制它都能免则免了)。

切片具有”广播”效应
1
2
3
arr2 = arr[3:8] # [3 4 5 6 7]
arr[5] = 100
print(arr2) # [3 4 100 6 7] ,这里可见,arr的修改影响到了arr2!
  • 使用copy()解决广播问题

如果不想被影响,可以显示地复制数据 -> arr2 = arr[3:5].copy()

2.3.1 布尔型索引

布尔型索引可以用于对数组元素进行过滤或选取。

  • 过滤或选取

将布尔值组成的矩阵作为索引代入原矩阵,会输出由True索引出的元素组成的新矩阵。还是看看下面的例子,比如我们要选出一个数组中的所有偶数值,这相当于一个过滤器方法(过滤出源中的偶数值):

布尔型索引,选出数组中的偶数元素
1
2
3
a = np.arange(10)     # [0,1,...,9]
print(a % 2 == 0) # [ True False True False True False True False True False]
print(a[a % 2 == 0]) # 将布尔矩阵作为原矩阵索引,只选取True位置的元素
  • 使用布尔索引来进行数据清洗

后面我们会详细介绍数据清洗,现在我们简单说下,比如你有一个由随机数组成的矩阵,已知负值为不合法的输入,现在你想把矩阵中所有负值都置为0,我们看看怎么做:

使用布尔索引来进行数据清洗,将负值元素置为0
1
2
3
4
5
6
7
8
data = np.random.randn(4, 4) # 随机生成一个4*4的-1~1随机数矩阵
data[data < 0] = 0

output: ===>
[ 0.34164317 -1.11070441 -0.63823478 0.80319368] [ 0.34164317 0 0 0.80319368]
[ 0.50016268 -0.54812653 0.71050926 0.05316833] [ 0.50016268 0 0.71050926 0.05316833]
[-0.63185555 -0.54865819 0.99373464 0.32828296] [ 0 0 0.99373464 0.32828296]
[ 0.55370335 -0.44756017 -1.11465901 0.14144133] -> [ 0.55370335 0 0 0.14144133]

2.3.2 多维数组的索引问题

多维数组的索引,总是优先从外层往内层访问,这是什么意思呢?比如一个3维数组

多维数组的索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
arr3d = np.arange(24).reshape(2,3,4) #2*3*4的3维数组
my_print(arr3d)
my_print(arr3d[0])

==> arr3d:
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]

[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]

==> arr3d[0]:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
----------

我们现在来看看什么叫从外层到内层,由于我们思维不常接触多维数据,所以对于多维的想像力是不够的。所以我们怎么看代如3维或更高维的数组呢?我有个办法就是从外到内层层剥离:比如这个2*3*4的数组可以看成是 2个3*4的数组组成的数组;那么最外层就是这个2,所以arr3d[0]表示的就是这2个3*4的数组中的第一个。

这样想之后,我们就能轻松地理解上面的代码段了。作为思考,我们想想arr3d[1,2]会输出什么呢? 同样,多维数组也可以使用切片运算如arr3d[x:y]。更高级一些arr[x:y, z:s]表示对arr第一层使用[x:y]切片后,再对第二层使用[z:s]切片。

多维数组多次切片示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
arr2d = np.arange(20).reshape(4,5) # 4*5的2维数组
arr = arr2d[1:3, 3:]
print(arr2d)
print(arr)

==> arr2d: 由 0-19组成的4*52维数组
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]
[15 16 17 18 19]]

==> arr:
[[ 8 9]
[13 14]]
----------

我们分析下上面操作的结果,最后输出结果是加了阴影的几个(为了对齐,我在单位数字前加了个0)
00 01 02 03 04
05 06 07 08 09
10 11 12 13 14
15 16 17 18 19

首先,arr2d[1:3, 3:]的第一个切片[1:3]选取了矩阵的第2行和第3行(index从0开始)。然后第2个切片从列开始切,[3:]表示选取了3-4列(index也是0开始),也就是后两列。这样就“切剩下”结果了。

2.4 花式索引(Fancy indexing)

花式过引有点像SQL里的select语句,往索引选取符号[]里传入一个数组,以之为index,可以选出你要想的行子集:

花式索引
1
2
3
4
5
6
7
8
arr = np.arange(20).reshape(4,5) #4*5的2维数组
print(arr[1, 3]) # 8
print(arr[[1, 3]])

==> out:
[[ 5 6 7 8 9]
[15 16 17 18 19]]
----------

上面这个例子很好地解释了花式索引了。我们看,arr[1,3]表示取第2行第4列元素,从之前我们输出过的arr的那个加阴影的矩阵可以看出,就是数字8。而arr[[1, 3]],注意哦,[]里面给的不是一个元组,而是一个数组[1,3],这样就表示我要获取第2行和第4行, 结果返回的是切出来的新数组。

  • 布尔矩阵mask应用

花式索引还有一种mask应用的:

花式索引之mask应用
1
2
3
4
a = np.arange(10)
print(a % 2 == 0) # [ True False True False True False True False True False]
b = a[a % 2 == 0] # 只选取了偶数元素
print(b) # [0 2 4 6 8]

这里选使用数组的算术运算a % 2 == 0,得出一个布尔数组,再使用这个布尔数组对原数组进行花式索引!从而选出了只有值为True的元素,即偶数元素。

3 矩阵运算

因为 ndarray运算功能的强大,使得它可以运用于矩阵的运算。更多关于矩阵运算及应用的知识,我们将在后面python与线性代数的相关学习里提及,这里说些简单的介绍性的。

3.1 矩阵的转置T

ndarray有个内置的T属性,可以直接返回一个矩阵的转置。

矩阵的转置T
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
matrix = np.arange(12).reshape(3, 4) # 快速建立一个3*4的矩阵
log(matrix)
log(matrix.T)

==> matrix
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
----------

==> matrix.T
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]

3.2 矩阵的点积

这个也是很简单, 直接 np.dot(matrix1, matrix2)

比如这样算一个矩阵的内积,就是矩阵与自己转置的点积 np.dot(matrix.T, matrix)

3.3 轴对换

轴对换主要两个方法:

1
2
ndarray.transpose(tuple)
ndarray.swapaxes()

transpose 参数给出新的轴排序;而swapaxes,给出要替换的轴位置。

transpose 参数给出新的轴排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
arr = np.arange(24).reshape((2,3,4)) # 创建一个2*3*4的3维数组
transposeArr = arr.transpose((1,0,2))

==> 原数组
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]

[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
----------


==> transpose((1,0,2)之后)
[[[ 0 1 2 3]
[12 13 14 15]]

[[ 4 5 6 7]
[16 17 18 19]]

[[ 8 9 10 11]
[20 21 22 23]]]

上面这个例子怎么理解呢,首先我们要理解“轴”的概念。对于2*3*4的数组,其中2、3、4就是它的轴,我们可以解决为此数据有3个轴,transpose((1,0,2))中的参数决定了轴的重排序,就是1、0、2,也就是第0轴与第1轴换了下位置。所以得出的新数组就应该是3*2*4的数组。

swapaxes表示哪两个轴交换位置,这里就不给出例子了,像上面的transpose,其实就是第0、1轴交换位置。所以也可以用arr.swapaxes(0, 1),和transpose((1,0,2))效果是一样的。

4 小结

作为小结和对本章的练习,大家可以试着在纸上算算下面程序的输出,并亲自复制到源文件中执行对比看自己对了多少:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
def log(msgOrOjb, obj=None):
"""一个实用的log方法."""
if isinstance(msgOrOjb, str):
if obj is not None:
print('\n==> ' + msgOrOjb + ':' , type(obj))
print(obj)
else:
print('\n==> ' + msgOrOjb)
else:
print('\n==> ', type(msgOrOjb))
print(msgOrOjb)
print('----------')


def main():
# --- ndarray ---
# 打印ndarray类型
ndarray = np.array([1, 2, 3])
print(type(ndarray)) # <class 'numpy.ndarray'>

# ndarray的两个重要属性
ndarray = np.random.rand(2, 3)
print(ndarray)
print(ndarray.shape)
print(ndarray.dtype)

# --- 创建ndarray ---
# 创建ndarray的各种方法
ndarray = np.array([[1,2,3], [4,5,6]])
log(ndarray)

ndarray = np.zeros((2,3))
log(ndarray)

ndarray = np.full((2,3), 10)
log(ndarray)

# --- ndarray的运算 ---
# ndarray的算术运算
arr = np.array([[1,2,3], [4,5,6]]) # 创建一个二维数组2*3
arr = arr * arr
log(arr)

# ndarray逻辑运算
arr = np.array([[1,2,3], [4,5,6]]) # 创建一个二维数组2*3
arr = arr > 2
log(arr)

arr = np.array([1,4,8]) # 创建一个二维数组2*3
arr2 = np.array([5,3,7]) # 创建一个二维数组2*3
log(arr < arr2)

# 索引与切片
arr = np.arange(10) # 生成0-9的数组[0 1 2 3 4 5 6 7 8 9]
print(arr[5]) # 输出 5
log(arr[3:8]) # [3 4 5 6 7], 注意,切片不包括末位元素
arr2 = arr[3:8] # [3 4 5 6 7]
arr3 = arr[3:8].copy() # 使用copy避免广播问题
arr[5] = 100
log(arr2) # [3 4 100 6 7]
log(arr3) # [3 4 5 6 7]

# 多维数组的索引
arr3d = np.arange(24).reshape(2,3,4) #2*3*4的3维数组
log(arr3d)
log(arr3d[0])

# 多维数组多次切片示例
arr2d = np.arange(20).reshape(4,5) #4*5的2维数组
log(arr2d)
arr = arr2d[1:3, 3:]
log(arr)

# 花式索引
arr = np.arange(20).reshape(4,5) #4*5的2维数组
log(arr[1, 3])
log(arr[[1, 3]])

# 布尔矩阵mask应用
a = np.arange(10)
print(a % 2 == 0) # [ True False True False True False True False True False]
b = a[a % 2 == 0] # 只选取了偶数元素
log(b) # [0 2 4 6 8]

# 使用布尔索引来进行数据清洗
data = np.random.randn(4, 4) # 随机生成一个4*4的-1~1随机数矩阵
log(data)
data[data < 0] = 0
log(data)

# --- ndarray矩阵运算 ---
matrix = np.arange(12).reshape(3, 4) # 快速建立一个3*4的矩阵
log(matrix)
log(matrix.T)

# 轴对换
arr = np.arange(24).reshape((2,3,4)) # 创建一个2*3*4的3维数组
transposeArr = arr.transpose((1,0,2))
log('原数组', arr)
log('transpose((1,0,2)之后)', transposeArr)
log('transpose((0,2,1)之后)', arr.transpose((0,2,1)))
log('arr.swapaxes(0, 1)', arr.swapaxes(0, 1))

5 引用

  1. 《利用python进行数据分析》 - WesMcKinney著,SeanCheney译 - WesMcKinney是pandas的开发者,所以必须是讲解numpy、pandas等数据处理工具最权威的专家了,他的这本书写的很简洁很易读,SeanCheney翻译的也是非常完美。十分值得推荐的。