补充内容
来自《利用Python进行数据分析》,仅供自己学习使用,严禁转载用于其他商业用途。
神奇的索引
arr = np.empty((8, 4))
for i in range(8):
arr[i] = i
print(arr)
print('------------')
print(arr[[4, 3, 0, 6]])
print('------------')
print(arr[[-3, -5, -7]])
#结果
[[0. 0. 0. 0.]
[1. 1. 1. 1.]
[2. 2. 2. 2.]
[3. 3. 3. 3.]
[4. 4. 4. 4.]
[5. 5. 5. 5.]
[6. 6. 6. 6.]
[7. 7. 7. 7.]]
------------
[[4. 4. 4. 4.]
[3. 3. 3. 3.]
[0. 0. 0. 0.]
[6. 6. 6. 6.]]
------------
[[5. 5. 5. 5.]
[3. 3. 3. 3.]
[1. 1. 1. 1.]]
首先,我们生成一个8行4列的数组,然后根据索引进行切片,取出第4,3,0,6四行的数据,最后,我们根据索引,取出倒数第3、5、7行的数据。
传递多个索引数组时情况有些许不同,这样会根据每个索引元组对应的元素选出一个一维数组:
arr = np.arange(32).reshape((8, 4))
print(arr)
#结果
[[ 0 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]]
print(arr[[1,5,7,2],[0,3,1,2]])
#
[ 4 23 29 10]
上述例子中,元素(1,0)、(5,3)、(7,1)、(2,2)被选中。如果不考虑数组的维数,索引的结果总是一维的。
数组转置和换轴
转置是一种特殊的数据重组形式,可以返回底层数据的视图而不需要赋值任何内容。数组拥有transpose方法,也有特殊的T属性。
import numpy as np
arr = np.arange(15).reshape((3, 5))
print(arr)
print('--------------')
print(arr.T)
#
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
--------------
[[ 0 5 10]
[ 1 6 11]
[ 2 7 12]
[ 3 8 13]
[ 4 9 14]]
可以发现,原数组的行和列进行了互换。
当进行矩阵操作时,有事还会用到一些特定操作。如计算矩阵内积会使用np.dot
arr = np.random.randn(6, 3)
print(arr)
print('--------')
print(np.dot(arr.T, arr))
#
[[ 2.36529252 1.45634028 -0.45027236]
[-1.85345472 1.50493214 -0.13627173]
[-1.05013824 -0.13728287 -0.24801649]
[-0.85897001 0.14457997 1.79092397]
[ 1.24476335 -1.34613385 -0.23993914]
[-0.77263369 0.04402339 -0.49494594]]
--------
[[13.0169215 -1.03430871 -2.00660628]
[-1.03430871 6.23951211 -0.26664834]
[-2.00660628 -0.26664834 3.79277832]]
对于更高维度的数组,transpose方法可以接受包含轴编号的元组,用于置换轴:
arr = np.arange(16).reshape((2, 2, 4))
print(arr)
print('--------')
print(arr.transpose((1, 0, 2)))
#
[[[ 0 1 2 3]
[ 4 5 6 7]]
[[ 8 9 10 11]
[12 13 14 15]]]
--------
[[[ 0 1 2 3]
[ 8 9 10 11]]
[[ 4 5 6 7]
[12 13 14 15]]]
这里,轴已经被重新排序,使得原先的第二个轴变为第一个,原先的第一个轴变成第二个,最后一个轴并没有变。
通用函数:快速的逐元素数组函数
通用函数,也可以成为ufunc,是一种在ndarray数据中进行逐元素操作的函数。某些简单函数接受一个或多个标量数值,并产生一个或多个标量结果,而通用函数就是对这些简单函数的向量化封装。
arr = np.arange(10)
print(arr)
print('----------')
print(np.sqrt(arr))
print('----------')
print(np.exp(arr))
#
[0 1 2 3 4 5 6 7 8 9]
----------
[0. 1. 1.41421356 1.73205081 2. 2.23606798
2.44948974 2.64575131 2.82842712 3. ]
----------
[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]
使用sqrt()方法对数组内的元素开根号,exp()方法返回e的n次方,e是一个常数为2.71828。
这是所谓的一元通用函数,还有一些通用函数,比如add或maximum则会接受两个数组并返回一个数组作为结果,因此成为二元通用函数:
x = np.random.randn(8)
y = np.random.randn(8)
print(x)
print(y)
print(np.maximum(x, y))
#
[-2.01960726 0.51696239 0.77124305 1.20857829 -1.204375 1.4427587
0.12709071 0.29780411]
[-0.28531979 -0.32238757 -0.44377128 -0.3816275 1.75887677 0.98860426
-0.76177926 -0.84858413]
[-0.28531979 0.51696239 0.77124305 1.20857829 1.75887677 1.4427587
0.12709071 0.29780411]
numpy.maximum逐个元素地将x和y中元素最大的值计算出来。
也有一些返回多个数组的函数。比如modf,是python内建函数divmod的向量化版本,它返回一个浮点值数组的小数部分和整数部分:
arr = np.random.randn(7) * 5
print(arr)
remainder, whole_part = np.modf(arr)
print(remainder)
print(whole_part)
#
[ 8.11757348 -0.70141921 -0.41606526 0.55060716 6.30934265 -5.97310928
3.95041647]
[ 0.11757348 -0.70141921 -0.41606526 0.55060716 0.30934265 -0.97310928
0.95041647]
[ 8. -0. -0. 0. 6. -5. 3.]
赋值时,前面的是小数部分,后面的是整数部分。
函数名 | 描述 |
---|---|
abs、fabs | 逐元素地计算整数、浮点数或复数的绝对值 |
sqrt | 计算每个元素的平方根(与arr ** 0.5相等) |
square | 计算每个元素的平方(与arr**2相等) |
exp | 计算每个元素的自然指数值e**x |
log、log10、log2、log1p | 分别对应:自然对数(e为底)、对数10为底、对数2为底、log(1+x) |
sign | 计算每个元素的符号值:1(正数)、0(0)、-1(负数) |
ceil | 计算每个元素的最高整数值(即大于等于给定数值的最小整数) |
floor | 计算每个元素的最小整数值(即小于等于给定数值的最大整数) |
modf | 分别将数组中的小数部分和整数部分按数组形式返回 |
isnan | 返回数组中的元素是否是一个NaN(不是一个数值),形式为布尔值数组 |
isfinite、isinf | 分别返回数组中的元素是否有限(非inf、非NaN)、是否无限的,形式为布尔值数组 |
cos、cosh、sin、sinh、tan、tanh | 常规的三角函数 |
arccos、arccosh、arcsin、 arcsinh、arctan、arctanh | 反三角函数 |
logical_not | 对数组的元素按位取反(与~arr效果一样) |
函数名 | 描述 |
add | 将数组的对应元素增加 |
subtract | 在第二个数组中,将第一个数组中包含的元组去除 |
multiply | 将数组的对应元素相乘 |
divide, floor_divide | 除或整除(放弃余数) |
power | 将第二个数组的元素作为第一个数组对应元素的幂次方 |
maximum, fmax | 逐个元素计算最大值,fmax忽略NaN |
minimum, fmin | 逐个元素计算最小值,fmax忽略NaN |
mod | 按元素的求模计算(即求除法的余数) |
copysign | 将第一个数组的符号值改为第二个数组的符号值 |
greater, greater_equal, less, less_equal, equal, not_equal, logical_and, logical_or, logical_xor | 进行逐个元素的比较,返回布尔值数组(与数学操作符>、>=、<、<=、==、!= 效果一致)进行逐个元素的逻辑操作(与逻辑操作符&、|、^ 效果一致) |
使用数组进行面向数组编程
利用数组表达式来替代显示循环的方法,称为向量化。
points = np.arange(-5, 5, 0.01)
xs, ys = np.meshgrid(points, points)
print(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]]
z = np.sqrt(xs ** 2 + ys ** 2)
print(z)
#
[[7.07106781 7.06400028 7.05693985 ... 7.04988652 7.05693985 7.06400028]
[7.06400028 7.05692568 7.04985815 ... 7.04279774 7.04985815 7.05692568]
[7.05693985 7.04985815 7.04278354 ... 7.03571603 7.04278354 7.04985815]
...
[7.04988652 7.04279774 7.03571603 ... 7.0286414 7.03571603 7.04279774]
[7.05693985 7.04985815 7.04278354 ... 7.03571603 7.04278354 7.04985815]
[7.06400028 7.05692568 7.04985815 ... 7.04279774 7.04985815 7.05692568]]
可以求出根号下x**2 + y**2的值。
将条件逻辑作为数组操作
numpy.where函数式三元表达式x if condition else y的向量化版本。
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)
#
[1.1, 2.2, 1.3, 1.4, 2.5]
假设cond中的元素为True时,我们取axrr中的对应元素值,否则取yarr中的元素值,可以通过以上代码完成。但如果数组很大,速度会很慢,而使用np.where时,就可以非常简单地完成:
result = np.where(cond, xarr, yarr)
print(result)
#
[1.1 2.2 1.3 1.4 2.5]
np.where的第二个和第三个参数并不需要是数组,它们可以是标量。
arr = np.random.randn(4, 4)
print(arr)
print(arr > 0)
print(np.where(arr > 0, 2, -2))
#
[[ 0.61766413 0.05565645 0.17577733 -0.37404451]
[-0.13572482 -0.86114914 -0.07353302 -0.03741651]
[-0.27460935 0.41297701 0.27868873 -0.63462777]
[ 0.18022908 -0.54048348 0.43320103 0.31274484]]
[[ True True True False]
[False False False False]
[False True True False]
[ True False True True]]
[[ 2 2 2 -2]
[-2 -2 -2 -2]
[-2 2 2 -2]
[ 2 -2 2 2]]
例子中可以看到,我们将其中的正值都替换成2,负值都替换为-2,使用np.where很容易实现。
如果仅将正值替换为2,可以写成:print(np.where(arr > 0, 2,arr))
数学和统计方法
arr = np.random.randn(5, 4)
print(arr)
print(arr.mean())
print(np.mean(arr))
print(arr.sum())
#
[[-2.80990453e-01 -7.58276181e-01 -1.14295903e+00 -1.38200808e+00]
[-1.99594965e-01 -1.38398991e+00 -1.19742730e+00 -3.04548718e-01]
[-2.27579601e-03 -1.31106338e-01 9.01807874e-01 5.98710554e-01]
[ 2.30283103e+00 2.25508761e+00 -2.11443469e-01 1.31561031e+00]
[ 1.34378629e+00 7.02711284e-01 1.58238066e+00 9.33821461e-01]]
0.24710634198581216
0.24710634198581216
4.942126839716243
像mean、sum等函数可以接受一个可选参数axis,这个参数可以用于计算给定轴向上的统计值,形成一个下降一维度的数组:
arr = np.random.randn(5, 4)
print(arr)
print(arr.mean(axis=1))
print(arr.sum(axis=0))
#
[[ 0.59696253 1.11121983 -0.1610959 -1.8332755 ]
[ 0.07409587 -2.14484861 0.50016975 0.76153254]
[-1.28510856 0.28761388 0.55825006 0.64558745]
[-0.38153078 0.83174068 -0.92845526 0.1239845 ]
[ 0.22249535 -0.68529482 -0.17869785 -0.7053025 ]]
[-0.07154726 -0.20226261 0.05158571 -0.08856521 -0.33669996]
[-0.77308559 -0.59956904 -0.20982921 -1.00747352]
arr.mean(1)表示“计算每一行的平均值”,而arr.sum(0)表示“计算列轴向的累和”。
其他的方法,例如cumsum和cumprod并不会聚合,它们会产生一个中间结果:
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7])
print(arr.cumsum())
#
[ 0 1 3 6 10 15 21 28]
在多维数组中,像cumsum这样的累积函数返回相同长度的数组,但是可以在指定轴向上根据较低维度的切片进行部分聚合:
arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
print(arr)
print(arr.cumsum(axis=0))
print(arr.cumprod(axis=1))
#
[[0 1 2]
[3 4 5]
[6 7 8]]
[[ 0 1 2]
[ 3 5 7]
[ 9 12 15]]
[[ 0 0 0]
[ 3 12 60]
[ 6 42 336]]
方法 | 描述 |
sum | 沿着轴向计算所有元素的累和,0长度的数组,累和为0 |
mean | 数学平均,0长度的数组平均值为NaN |
std, var | 标准差和方差,可以选择自由度调整(默认分母是n) |
min, max | 最大值和最小值 |
argmin, argmax | 最大值和最小值的位置 |
cumsum | 从0开始元素累积和 |
cumprod | 从1开始元素累积积 |