一、数组转置和轴对换

1.1 矩阵转置

转置是重塑的一种特殊形式,它返回的是源数据的视图(不会进行任何复制操作)。数组不仅有transpose方法,还有一个特殊的T属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

arr = np.arange(15).reshape((3,5))
print(f'arr的值为:{arr}')
print(f'arr的值为:{arr.T}')

# 结果
arr的值为:[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
arr的值为:[[ 0 5 10]
[ 1 6 11]
[ 2 7 12]
[ 3 8 13]
[ 4 9 14]]

在进行矩阵计算时,经常需要用到该操作,比如利用np.dot计算矩阵内积:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np

arr = np.random.randn(6, 3)
print(f'arr的值为{arr}')
print(f'np.dot(arr.T, arr)的值为{np.dot(arr.T, arr)}')

# 结果
arr的值为[[ 0.40937937 0.109259 1.09231322]
[ 1.79341055 -1.59611149 0.34294822]
[-2.03273752 0.42860845 2.12615649]
[ 0.08557004 0.27998699 0.30052393]
[-1.94086972 0.05284612 0.54398195]
[-1.23355371 0.02882536 0.19043626]]
np.dot(arr.T, arr)的值为[[12.81188691 -3.80316984 -4.52469619]
[-3.80316984 2.82523096 0.6016296 ]
[-4.52469619 0.6016296 6.25380004]]

1.2 高维数组转置

对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑子):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np

# 高纬数组转置
arr = np.arange(16).reshape((2, 2, 4))
print(f'arr的值为{arr}')
print(f'arr.transpose((1,0,2))的值为{arr.transpose((1, 0, 2))}')

# 结果
arr的值为[[[ 0 1 2 3]
[ 4 5 6 7]]

[[ 8 9 10 11]
[12 13 14 15]]]
arr.transpose((1,0,2))的值为[[[ 0 1 2 3]
[ 8 9 10 11]]

[[ 4 5 6 7]
[12 13 14 15]]]

这里,第一个轴被换成了第二个,第二个轴被换成了第一个,最后一个轴不变。

简单的转置可以使用.T,它其实就是进行轴对换而已。ndarray还有一个swapaxes方法,它需要接受一对轴编号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
arr = np.arange(16).reshape((2, 2, 4))
print(f'arr.swapaxes(1,2)的值为{arr.swapaxes(1,2)}')

# 结果
arr.swapaxes(1,2)的值为[[[ 0 4]
[ 1 5]
[ 2 6]
[ 3 7]]

[[ 8 12]
[ 9 13]
[10 14]
[11 15]]]

swapaxes也是返回源数据的视图(不会进行任何复制操作)。

二、通用函数:快速的元素级数组函数

2.1 一元通用函数

通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。你可以将其看做简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。

许多ufunc都是简单的元素级变体,如sqrt和exp[一元(unary)ufunc]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np

arr = np.arange(10)
print(f'arr的值为{arr}')
print(f'np.sqrt(arr)的值为{np.sqrt(arr)}')
print(f'np.exp(arr)的值为{np.exp(arr)}')

# 结果
arr的值为[0 1 2 3 4 5 6 7 8 9]

np.sqrt(arr)的值为[0. 1. 1.41421356 1.73205081 2. 2.23606798
2.44948974 2.64575131 2.82842712 3. ]

np.exp(arr)的值为[1.00000000e+00 2.71828183e+00 7.38905610e+00 2.00855369e+01
5.45981500e+01 1.48413159e+02 4.03428793e+02 1.09663316e+03
2.98095799e+03 8.10308393e+03]

有些ufunc的确可以返回多个数组。modf就是一个例子,它是Python内置函数divmod的矢量化版本,它会返回浮点数数组的小数整数部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 返回多个数组
import numpy as np
arr = np.random.randn(7) * 5
print('-----arr-----')
print(arr)
remainder, whole_part = np.modf(arr)
print('-----remainder-----')
print(remainder)
print('----whole_part-----')
print(whole_part)

# 结果
-----arr-----
[-3.06859633 2.38831761 2.02454853 -4.16319571 6.15744959 2.00994978
-5.31488584]
-----remainder-----
[-0.06859633 0.38831761 0.02454853 -0.16319571 0.15744959 0.00994978
-0.31488584]
----whole_part-----
[-3. 2. 2. -4. 6. 2. -5.]

2.2 二元通用函数

add或maximum接受2个数组(因此也叫二元(binary)ufunc),并返回一个结果数组:

numpy.maximum计算x和y中元素级别最大的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import numpy as np
# 二元通用函数
x = np.random.randn(8)
y = np.random.randn(8)

print('-----x-----')
print(x)
print('-----y-----')
print(y)
print('-----maximum(x, y)-----')
print(np.maximum(x, y))

# 结果
-----x-----
[-0.60947737 0.63877237 1.93746945 -0.07678216 -1.3485691 -0.37963133
-0.22176804 -1.21756813]
-----y-----
[-0.10912956 -1.84583805 1.56942896 -0.35833812 1.8785937 -0.20553932
1.66348886 -1.91367473]
-----maximum(x, y)-----
[-0.10912956 0.63877237 1.93746945 -0.07678216 1.8785937 -0.20553932
1.66348886 -1.21756813]

2.3 inplace操作

Ufuncs可以接受一个out可选参数,这样就能在数组原地进行操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np
arr = np.random.randn(7) * 5
print('-----arr-----')
print(arr)
print(np.sqrt(arr, arr))

# 结果
-----arr-----
[-1.18200846 5.75373989 -0.84204594 -2.18080399 -5.01037861 -7.21998518
-2.3902697 ]
[ nan 2.39869546 nan nan nan nan
nan]
-----remainder-----
[ nan 0.39869546 nan nan nan nan
nan]
----whole_part-----
[nan 2. nan nan nan nan nan]

三、利用数组进行数据处理

用数组表达式代替循环的做法,通常被称为矢量化。

假设我们想要在一组值(网格型)上计算函数sqrt(x^2+y^2)np.meshgrid函数接受两个一维数组,并产生两个二维矩阵(对应于两个数组中所有的(x,y)对):

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
import numpy as np

points = np.arange(-5, 5, 0.01)
xs, ys = np.meshgrid(points, points)
print('-----xs-----')
print(xs)
print('-----ys-----')
print(ys)
print('-----xs**2+ys**2-----')
print(xs ** 2 + ys ** 2)

# 结果
-----xs-----
[[-5. -4.99 -4.98 ... 4.97 4.98 4.99]
[-5. -4.99 -4.98 ... 4.97 4.98 4.99]
[-5. -4.99 -4.98 ... 4.97 4.98 4.99]
...
[-5. -4.99 -4.98 ... 4.97 4.98 4.99]
[-5. -4.99 -4.98 ... 4.97 4.98 4.99]
[-5. -4.99 -4.98 ... 4.97 4.98 4.99]]
-----ys-----
[[-5. -5. -5. ... -5. -5. -5. ]
[-4.99 -4.99 -4.99 ... -4.99 -4.99 -4.99]
[-4.98 -4.98 -4.98 ... -4.98 -4.98 -4.98]
...
[ 4.97 4.97 4.97 ... 4.97 4.97 4.97]
[ 4.98 4.98 4.98 ... 4.98 4.98 4.98]
[ 4.99 4.99 4.99 ... 4.99 4.99 4.99]]
-----xs**2+ys**2-----
[[50. 49.9001 49.8004 ... 49.7009 49.8004 49.9001]
[49.9001 49.8002 49.7005 ... 49.601 49.7005 49.8002]
[49.8004 49.7005 49.6008 ... 49.5013 49.6008 49.7005]
...
[49.7009 49.601 49.5013 ... 49.4018 49.5013 49.601 ]
[49.8004 49.7005 49.6008 ... 49.5013 49.6008 49.7005]
[49.9001 49.8002 49.7005 ... 49.601 49.7005 49.8002]]

用matplotlib创建这个二维数组的可视化:

1
2
3
4
5
6
···
import matplotlib.pyplot as plt
plt.imshow(z, cmap=plt.cm.gray);
plt.colorbar()
plt.title('Image plot of $\sqrt{x^2 + y^2}$ for a grid of values')
plt.show()

image-20230801085449276

3.1 将条件逻辑表述为数组运算

numpy.where函数是三元表达式x if condition else y的矢量化版本。假设有一个布尔数组和两个值数组,根据cond中的值选取xarr和yarr的值:当cond中的值为True时,选取xarr的值,否则从yarr中选取:

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np

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, True, True, False])

# 列表推导式的写法
# result = [(x if c else y) for x, y, c in zip (xarr, yarr, cond)]
# print(result)

# np.where写法
result = np.where(cond, xarr, yarr)
print(result)

np.where的第二个和第三个参数不必是数组,它们都可以是标量值。在数据分析工作中,where通常用于根据另一个数组而产生一个新的数组。假设有一个由随机数据组成的矩阵,你希望将所有正值替换为2,将所有负值替换为-2。若利用np.where,则会非常简单:

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
import numpy as np
arr = np.random.randn(4, 4)
print('-----arr-----')
print(arr)
bool_arr = arr > 0
print('-----bool_arr-----')
print(bool_arr)
z = np.where(bool_arr, 2, -2)
print('-----z-----')
print(z)

# 结果
-----arr-----
[[ 0.74480762 1.9961354 1.85356757 -0.62426027]
[ 0.82830309 -1.77342089 0.18499548 0.91050648]
[ 0.51440189 -0.16841895 0.93366603 0.55430863]
[ 1.01458267 -0.61725371 -1.02016501 -1.15918923]]
-----bool_arr-----
[[ True True True False]
[ True False True True]
[ True False True True]
[ True False False False]]
-----z-----
[[ 2 2 2 -2]
[ 2 -2 2 2]
[ 2 -2 2 2]
[ 2 -2 -2 -2]]

使用np.where,可以将标量和数组结合起来。例如,我可用常数2替换arr中所有正的值:

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
import numpy as np
arr = np.random.randn(4, 4)
print('-----arr-----')
print(arr)
bool_arr = arr > 0
print('-----bool_arr-----')
print(bool_arr)
z = np.where(bool_arr, 2, arr)
print('-----z-----')
print(z)

# 结果
-----arr-----
[[-0.95424723 -0.1088034 -0.55509971 -0.55104994]
[ 1.26593876 -0.87900668 -0.20989613 -0.40595546]
[-1.54684211 -0.12016153 -0.33720294 -2.01278861]
[-1.72034114 0.35513998 -0.92319834 0.75596698]]
-----bool_arr-----
[[False False False False]
[ True False False False]
[False False False False]
[False True False True]]
-----z-----
[[-0.95424723 -0.1088034 -0.55509971 -0.55104994]
[ 2. -0.87900668 -0.20989613 -0.40595546]
[-1.54684211 -0.12016153 -0.33720294 -2.01278861]
[-1.72034114 2. -0.92319834 2. ]]

传递给where的数组大小可以不相等,甚至可以是标量值。

3.2 数学和统计方法

可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean以及标准差std等聚合计算(aggregation,通常叫做约简(reduction))既可以当做数组的实例方法调用,也可以当做顶级NumPy函数使用。

1
2
3
4
5
6
7
8
9
10
11
import numpy as np

arr = np.random.randn(5, 4)
print('-----arr-----')
print(arr)
print('----arr.mean()-----')
print(arr.mean())
print('----np.mean(arr)-----')
print(np.mean(arr))
print('-----arr.sum()-----')
print(arr.sum())

mean和sum这类的函数可以接受一个axis选项参数,用于计算该轴向上的统计值,最终结果是一个少一维的数组,arr.mean(1)是“计算行的平均值”,arr.sum(0)是“计算每列的和”。:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
···
print('----arr.mean(axis=1)-----')
print(arr.mean(axis=1))
print('-----arr.sum(axis=0)-----')
print(arr.sum(axis=0))

# 结果
-----arr-----
[[-0.28509933 -0.2332892 -1.28545835 0.52036633]
[-0.64965521 0.23421706 -0.79651927 0.20428213]
[ 1.4742175 0.36748721 -1.22918593 -0.54799161]
[ 0.44538458 0.26254038 -0.01386919 0.86799059]
[ 0.76530995 -0.61765575 0.90276933 0.38609619]]
----arr.mean(axis=1)-----
[-0.32087014 -0.25191882 0.01613179 0.39051159 0.35912993]
-----arr.sum(axis=0)-----
[ 1.75015748 0.0132997 -2.42226342 1.43074362]

其他如cumsumcumprod之类的方法则不聚合,而是产生一个由中间结果组成的数组:

1
2
3
4
5
6
7
8
9
10
11
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7])
print('-----arr.cumsum()-----') #累加
print(arr.cumsum())
print('-----arr.cumprod()-----') #累乘
print(arr.cumprod())

# 结果
-----arr.cumsum()-----
[ 0 1 3 6 10 15 21 28]
-----arr.cumprod()-----
[0 0 0 0 0 0 0 0]

在多维数组中,累加函数(如cumsum)返回的是同样大小的数组,但是会根据每个低维的切片沿着标记轴计算部分聚类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import numpy as np
arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
print('----arr----')
print('----arr.cumsum(axis=0)-----')
print(arr.cumsum(axis=0))
print('-----arr.cumprod(axis=1)')
print(arr.cumprod(axis=1))

# 结果
-----arr-----
[[0 1 2]
[3 4 5]
[6 7 8]]
-----arr.cumsum(axis=0)-----
[[ 0 1 2]
[ 3 5 7]
[ 9 12 15]]
-----arr.cumprod(axis=1)-----
[[ 0 0 0]
[ 3 12 60]
[ 6 42 336]]

3.3 基本数组统计方法