NumPy 基础:数组和矢量计算
NumPy 的 ndarray : 一种多维数组对象
import numpy as np
data = np.array()
data.shape #对象的结构,如(2,3)
data.dtype #对象元素的数据类型
data.ndim #对象的维度
创建ndarray
data = [] #创建一个list对象
arr = np.array(data) #传递一个list对象创建ndarray对象
np.zeros(10) #创建长度为10的全0数组
np.ones(10) #创建长度为10的全1数组
np.zeros((3,6)) #创建3行6列的全0二维数组
np.arrange(10) #对应python内置的range函数
数组创建函数
函数 | 说明 |
---|---|
array | 将输入数据(列表、元祖、数组或其他序列类型)转换为ndarray。 |
asarray | 将输入转换为ndarray,如果输入本身就是一个ndarray就不进行复制。 |
arrange | 类似于内置的range。 |
ones、ones_like | 根据指定的形状和dtype创建一个全1数组。ones_like以另一个数组为参数。 |
zeros、zeros_like | 同上 |
empty、empty_like | 创建新数组,只分配内存空间但不填充任何值。 |
eye、identity | 创建一个正方的NxN单位矩阵(对角线为1,其余为0) |
ndarray的数据类型
arr1 = np.array([1, 2, 3], dtype=np.float64)
arr2 = np.array([1, 2, 3], dtype=np.int32)
float_arr1 = arr1.astype(np.float64) #data.astype()显式转换数据类型
float_arr2 = arr2.astype(arr1.dtype)
调用astype无论如何都会常见一个新的数组(原始数组的一份拷贝),即使新dtype跟老dtype相同也是如此。
基本的索引和切片
一维数组跟Python列表的功能差不多,跟列表最重要的区别在于,数组切片是原始数组的视图,并非拷贝。如果要想得到的是ndarray切片的一个副本而非视图,就需要显式的进行复制操作,例如==arr[5:8].copy()==。
以下两种方式是等价的
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d[0][2]
arr2d[0, 2]
切片索引
ndarray的切片语法跟Python列表这样的一维对象差不多。高维度对象的花样更多,可以在一个或多个轴上进行切片,也可以跟整数索引混合使用。
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d
Out:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
arr2d[:2]
Out:
array([[1, 2, 3],
[4, 5, 6]])
arr2d[:2,1:]
Out:
array([[2, 3],
[5, 6]])
布尔型索引
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = randn(7,4) //使用numpy.random中的randn函数生成一些正态分布的随机数据。
data
Out:
array([[-0.29387507, 0.17205361, 0.66953008, 1.52117065],
[ 1.10443341, 1.38657273, -0.07698117, 0.14266073],
[ 0.76090409, 0.971822 , -0.32016532, 0.8410247 ],
[-0.16898366, 0.18393705, -0.68519614, 1.22744236],
[ 0.50100988, -0.75611675, 0.18750586, -1.64240784],
[-0.3859573 , 0.53440464, -0.74281885, 0.83665929],
[-0.52089554, 0.65959834, -0.17651269, -1.58237464]])
names == 'Bob'
Out: array([ True, False, False, True, False, False, False], dtype=bool)
data[names == 'Bob']
Out:
array([[-0.29387507, 0.17205361, 0.66953008, 1.52117065],
[-0.16898366, 0.18393705, -0.68519614, 1.22744236]])
布尔型数组的长度必须跟被索引的轴长度一致。还可以跟切片、整数(或整数序列)混合使用:
data[names == 'Bob',2:]
Out:
array([[ 0.66953008, 1.52117065],
[-0.68519614, 1.22744236]])
要选择“Bob”以外的其他值,既可以使用不等于号(!=),也可以通过负号(-)对条件进行否定:
data[names != 'Bob']
data[-(names == 'Bob')]
如果需要对布尔条件进行组合使用,可以使用&(和)、|(或)之类的布尔算数运算符即可:
mask = (names == 'Bob') | (names == 'Will')
mask
Out: array([ True, False, True, True, True, False, False], dtype=bool)
data[mask]
Out:
array([[-0.29387507, 0.17205361, 0.66953008, 1.52117065],
[ 0.76090409, 0.971822 , -0.32016532, 0.8410247 ],
[-0.16898366, 0.18393705, -0.68519614, 1.22744236],
[ 0.50100988, -0.75611675, 0.18750586, -1.64240784]])
通过布尔型索引选取数组中的数据,总是创建数据的副本,即使返回一模一样的数组也是如此。
==Python关键字and和or在布尔型数组中无效。==
可以对数组中的一些符合条件的数据进行重新赋值:
data[data < 0] = 0
data
Out:
array([[ 0. , 0.17205361, 0.66953008, 1.52117065],
[ 1.10443341, 1.38657273, 0. , 0.14266073],
[ 0.76090409, 0.971822 , 0. , 0.8410247 ],
[ 0. , 0.18393705, 0. , 1.22744236],
[ 0.50100988, 0. , 0.18750586, 0. ],
[ 0. , 0.53440464, 0. , 0.83665929],
[ 0. , 0.65959834, 0. , 0. ]])
花式索引
即利用整数数组进行索引。
为了以特定的书序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray即可:
arr = np.empty((8, 4))
for i in range(8):
arr[i] = i
arr
Out:
array([[ 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.]])
arr[[4, 3, 0, 6]]
Out:
array([[ 4., 4., 4., 4.],
[ 3., 3., 3., 3.],
[ 0., 0., 0., 0.],
[ 6., 6., 6., 6.]])
一次传入多个索引数组返回的是一个以为数组,其中的元素对应各个索引元组:
arr = np.arange(32).reshape((8, 4))
arr
Out:
array([[ 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]])
arr[[1, 5, 7, 2], [0, 3, 1, 2]] #返回的是元素(1,0)、(5,3)、(7,1)和(2,2)。
Out: array([ 4, 23, 29, 10])
我们可以通过以下方式获取矩阵的行列子集:
arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]]
Out:
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])
还可以通过使用np.ix函数,它可以将两个一维整数数组转换为一个用于选取方形区域的所引器:
arr[np.ix_([1, 5, 7, 2], [0, 3, 1, 2])]
Out:
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])
==花式索引总是将数据复制到新数组中。==
数组转置和轴对换
==转置是重塑的一种特殊形式,它返回的事源数据的视图(不会进行任何复制操作)==。数组不仅有transppose方法,还有一个特殊的T属性:
arr = np.arange(15).reshape((3, 5))
arr
Out:
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
arr.T
Out:
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])
arr = np.random.randn(6, 3)
np.dot(arr.T, arr) #利用np.dot计算矩阵内积X.T*X
对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费脑子):
arr = np.arange(16).reshape((2, 2, 4))
arr
Out:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
arr.transpose(1,0,2)
Out:
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]],
[[ 4, 5, 6, 7],
[12, 13, 14, 15]]])
ndarray还有一个swapaxes方法,它接受一对轴编号:
arr
Out:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
arr.swapaxes(1,2)
Out:
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])
通用函数:快速的元素级数组函数
一元ufunc
函数 | 说明 |
---|---|
abs、fabs | 计算整数、浮点数或复数的绝对值。对于复数数值,可以使用更快的fabs。 |
sqrt | 计算各元素的平方根 |
square | 计算各元素的平方 |
exp | 计算各元素的指数e^x |
log、log10、log2、log1p | 分别为自然对数、底数为10的log、底数为2的log、log(1 + x) |
sign | 计算各元素的正负号:1(正数)、0(零)、-1(负数) |
ceil | 计算各元素的ceiling值 |
floor | 计算各元素的floor值 |
rint | 四舍五入到正数,保留dtype |
modf | 将数组的小数和整数部分以两个独立数组的形式返回 |
isnan | 返回一个表示“哪些值是NaN”的布尔型数组 |
isfinite、isinf | 分别返回一个表示“哪些元素是有穷的”或“哪些元素是无穷的”的布尔型数组 |
cos、cosh、sin、sinh、tan、tanh | 普通型和双曲型三角函数 |
arccos、arccosh、arcsin、arcsinh、arctan、arctanh | 反三角函数 |
logical_not | 计算各元素not x的真值。想到与-arr |
二元ufunc
函数 | 说明 | |
---|---|---|
add | 将数组中对应的元素相加 | |
subtract | 从第一个数组中减去第二个数组中的元素 | |
multiply | 数组元素相乘 | |
divide、floor_divide | 除法或向下圆整除法 | |
power | 对第一个数组中的元素A,根据第二个数组中的相应元素B,计算A的B次方 | |
maximum、fmax | 元素级的最大值计算。fmax忽略NaN | |
minmum、fmin | 元素级的最小值计算。fmin忽略NaN | |
mod | 元素级的求模计算 | |
copysign | 将第二个数组中的值得负号复制到第一个数组中的值 | |
greate、greate_equal、less、less_equal、equal、not_equal | 执行元素级的比价运算,最终产生布尔型的数组。相当于运算符>、>=、<、<=、==、!= | |
logical_and、logical_or、logical_xor | 执行元素级的真值逻辑运算。相当于中缀运算符&、 | 、^ |
利用数组进行数据处理
将条件逻辑表述为数组运算
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)]
result
Out: [1.1000000000000001, 2.2000000000000002, 1.3, 1.3999999999999999, 2.5]
这样做会有许多问题。首先,对于大的数组,它不会很快(因为所有的工作都是有纯Python来做的)。其次,对于多维数组,它不能工作。使用 np.where 你可以像这样非常简洁的编写:
result = np.where(cond, xarr, yarr)
result
Out: array([ 1.1, 2.2, 1.3, 1.4, 2.5])
np.where 的第一个和第二个参数不需要是数组;它们中的一个或两个可以是纯量。 在数据分析中 where 的典型使用是生成一个新的数组,其值基于另一个数组。假如你有一个矩阵,其数据是随机生成的,你想要把其中的正值替换为2,负值替换为-2,使用 np.where 非常容易:
arr = randn(4, 4)
arr
Out:
array([[ 0.6372, 2.2043, 1.7904, 0.0752],
[-1.5926, -1.1536, 0.4413, 0.3483],
[-0.1798, 0.3299, 0.7827, -0.7585],
[ 0.5857, 0.1619, 1.3583, -1.3865]])
np.where(arr > 0, 2, -2)
Out:
array([[ 2, 2, 2, 2],
[-2, -2, 2, 2],
[-2, 2, 2, -2],
[ 2, 2, 2, -2]])
np.where(arr > 0, 2, arr) # 仅设置正值为 2
Out:
array([[ 2. , 2. , 2. , 2. ],
[-1.5926, -1.1536, 2. , 2. ],
[-0.1798, 2. , 2. , -0.7585],
[ 2. , 2. , 2. , -1.3865]])
np.where 可以嵌套使用。
数学统计方法
一组数学函数,计算整个数组或一个轴向上数据的统计,和数组函数一样是容易访问的。聚合(通常被称为 reductions ),如 sum , mean ,标准偏差 std 可以使用数组实例的方法,也可以使用顶层NumPy的函数:
arr = randn(5,4)
arr
Out:
array([[ 0.39013323, -0.65003199, -1.7659255 , 0.50657869],
[ 1.49064958, -2.12313076, -0.06437275, -1.74020972],
[ 0.58393273, -2.54944833, 1.3207072 , 0.4929906 ],
[ 0.29181077, 0.24600015, -0.88524769, 0.1694354 ],
[-1.29550423, -0.67156125, 0.04152137, 0.6270823 ]])
arr.mean()
Out: -0.27922950905899768
np.mean(arr)
Out: -0.27922950905899768
arr.sum()
Out: -5.5845901811799532
np.sum(arr)
Out: -5.5845901811799532
像 mean 和 sun 函数可以有一个可选的 axis参数,它对给定坐标轴进行统计,结果数组将会减少一个维度:
arr.mean(axis=1)
Out[84]: array([-0.37981139, -0.60926591, -0.03795445, -0.04450034, -0.32461545])
arr.sum(0)
Out[86]: array([ 1.46102209, -5.74817217, -1.35331736, 0.05587727])
像 cumsum 和 cumprod 这些函数并不聚集,而是产生一个中间结果组成的数组:
arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
arr.cumsum(axis=0)
Out:
array([[ 0, 1, 2],
[ 3, 5, 7],
[ 9, 12, 15]])
arr.cumprod(axis=0)
Out:
array([[ 0, 1, 2],
[ 0, 4, 10],
[ 0, 28, 80]])
数学统计方法清单
方法 | 描述 |
---|---|
sum | 对数组的所有或一个轴向上的元素求和。零长度的数组的和为灵。 |
mean | 算术平均值。灵长度的数组的均值为NaN。 |
std, var | 标准差和方差,有可选的调整自由度(默认值为n)。 |
min, max | 最大值和最小值 |
argmin, argmax | 索引最小和最大元素。 |
cumsum | 从0元素开始的累计和。 |
cumprod | 从1元素开始的累计乘。 |
用于布尔型数组的方法
在上面的方法中,布尔值被强制为1( True )和0a( False )。因此, sum 经常被用来作为对一个布尔数组中的 True 计数的手段:
arr = randn(100)
(arr > 0).sum() # 正值的个数
Out: 44
有两个额外的方法, any 和 all ,对布尔数组尤其有用。 any 用来测试一个数组中是否有一个或更多的 True ,而 all 用来测试所有的值是否为 True :
bools = np.array([False, False, True, False])
bools.any()
Out: True
bools.all()
Out: False
==这些方法这些方法也可以工作在非布尔型数组上,非零元素作为 True 。==
排序
arr.sort(axis=None)会就地排序修改数组本身,而np.sort(arr,axis=None)返回的是数组的已排序数组。计算数组分位数最简单的方法是对其进行排序,然后选取特定位置的值:
large_arr = randn(1000)
large_arr.sort()
large_arr[int(0.05 * len(large_arr))] # 5% 分位点
Out: -1.5791023260896004
唯一化以及其他的集合逻辑
Numpy有一些基本的针对一维ndarrays的集合操作。最常使用的一个可能是 np.unique ,它返回一个数组的经过排序的 unique 值:
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
np.unique(names)
Out:
array(['Bob', 'Joe', 'Will'],
dtype='|S4')
ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
np.unique(ints)
Out: array([1, 2, 3, 4])
另一个函数 np.in1d 用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组:
values = np.array([6, 0, 0, 3, 2, 5, 6])
np.in1d(values, [2, 3, 6])
Out: array([ True, False, False, True, True, False, True], dtype=bool)
函数 | 说明 |
---|---|
unique(x) | 计算x单一的元素,并对结果排序 |
intersect1d(x, y) | 计算x和y相同的元素,并对结果排序 |
union1d | 结合x和y的元素,并对结果排序 |
in1d(x, y) | 得到一个布尔数组指示x中的每个元素是否在y中 |
setdiff1d(x, y) | 差集,在x中但不再y中的集合 |
setxor1d(x, y) | 对称差集,不同时在两个数组中的元素 |
用于数组的文件输入输出
NumPy 能够保存数据到磁盘和从磁盘加载数据,不论数据是文本或二进制的。
将数组以二进制格式保存到磁盘
np.save 和 np.load 是两个主力功能,有效的保存和加载磁盘数据。数组默认保存为未经过压缩的原始二进制数据,文件扩展名为 .npy :
arr = np.arange(10)
np.save('some_array', arr)
如果文件路进并不是以 .npy 结尾,扩展名将会被自动加上。在磁盘上的数组可以使用 np.load 加载:
np.load('some_array.npy')
Out: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
可以使用 np.savez 并以关键字参数传递数组来保存多个数组到一个zip的归档文件中:
np.savez('array_archive.npz', a=arr, b=arr)
加载一个 .npz 文件时,会得到一个字典对象,该对象会对各个数组进行延时加载:
arch = np.load('array_archive.npz')
arch['b']
Out: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
存取文本文件
从文件加载文本是一个相当标准的任务。对一个新人来说,Python的文件加读取和写入函数的景象可能有一点儿混乱,因此我将主要集中在pandas的 read_csv 和 read_table 函数上。有时使用 np.loadtxt 或更专门的 np.genfromtxt 对于加载数据到普通的 NumPy 数组是很有用的。
# Windows下用type
!cat array_ex.txt
0.580052,0.186730,1.040717,1.134411
0.194163,-0.636917,-0.938659,0.124094
-0.126410,0.268607,-0.695724,0.047428
-1.484413,0.004176,-0.744203,0.005487
2.302869,0.200131,1.670238,-1.881090
-0.193230,1.047233,0.482803,0.960334
# 直接将文件加载到一个二维数组中
arr = np.loadtxt('array_ex.txt', delimiter=',')
arr
Out:
array([[ 0.5801, 0.1867, 1.0407, 1.1344],
[ 0.1942, -0.6369, -0.9387, 0.1241],
[-0.1264, 0.2686, -0.6957, 0.0474],
[-1.4844, 0.0042, -0.7442, 0.0055],
[ 2.3029, 0.2001, 1.6702, -1.8811],
[-0.1932, 1.0472, 0.4828, 0.9603]])
np.savatxt 执行相反的操作:将数组写到以某种分隔符隔开的文本文件中。 genfromtxt 与 loadtxt 相似,但是它是面向结构数组和缺失数据处理的。
线性代数
dot 函数,是数组的一个方法和 numpy 命名空间中的一个函数,用来进行矩阵乘法运算:
x = np.array([[1., 2., 3.], [4., 5., 6.]])
In [195]: y = np.array([[6., 23.], [-1, 7], [8, 9]])
x.dot(y) # 等价于 np.dot(x, y)
Out:
array([[ 28., 64.],
[ 67., 181.]])
numpy.linalg 有一个关于矩阵分解和像转置和行列式等的一个标准集合。
from numpy.linalg import inv, qr
常见numpy.linalg
函数 | 描述 |
---|---|
diag | 返回一个方阵的对角线(或非对角线)元素为一个一维数组,或者转换一个一维数组到一个方阵(非对角线元素为零) |
dot | 矩阵乘积 |
trace | 计算对角线上元素的和 |
det | 计算矩阵行列式 |
eig | 计算方阵的特征值和特征向量 |
inv | 计算方阵的逆 |
pinv | 计算方阵 Moore-Penrose 伪逆 |
qr | 计算 QR 分解 |
svd | 计算奇异值分解(SVD) |
solve | 求解线性系统方程 Ax = b 的x,其中A是一个方阵 |
lstsq | 计算 y = Xb 的最小二乘解 |
随机数生成
numpy.random 模块对 Python 内置的 random 进行了补充,增加了一些用于高效生成多种概率分布的样本值得函数。
numpy.random 函数
函数 | 说明 |
---|---|
seed | 确定随机数生成数的种子 |
permutation | 返回一个序列的随机排列或返回一个随机排列的范围 |
shuffle | 对一个序列就地随机排列 |
rand | 产生均匀分布的样本值 |
randint | 从给定的上下限范围内(不包括上限)随机选取整数 |
randn | 产生标准正态分布的样本值 |
binomial | 产生二项分布的样本值 |
normal | 产生正态(高斯)分布的样本值 |
beta | 产生Beta分布的样本值 |
chisquare | 产生卡方分布的样本值 |
gamma | 产生Gamma分布的样本值 |
uniform | 产生在[0,1]中均匀分布的样本值 |
模拟随机漫步
多个随机漫步
nwalks = 5000
nsteps = 1000
draws = np.random.randint(0, 2, size=(nwalks, nsteps)) # 0 or 1
steps = np.where(draws > 0, 1, -1)
walks = steps.cumsum(1)
walks
Out:
array([[ 1, 0, 1, ..., 8, 7, 8],
[ 1, 0, -1, ..., 34, 33, 32],
[ 1, 0, -1, ..., 4, 5, 4],
...,
[ 1, 2, 1, ..., 24, 25, 26],
[ 1, 2, 3, ..., 14, 13, 14],
[ -1, -2, -3, ..., -24, -23, -22]])
# 我们可以获得所有游走的最大和最小值
walks.max() In [229]: walks.min()
Out: 138 Out[229]: -133
# 在这些游走中,让我们来计算到达30或-30的最短时间。这有一点儿狡猾,
# 因为不是所有的5000个游走都能到达30。我们可以使用 any 方法来检测
hits30 = (np.abs(walks) >= 30).any(1)
hits30
Out: array([False, True, False, ..., False, True, False], dtype=bool)
hits30.sum() # 30或-30的个数
Out: 3410
# 我们可以使用这个布尔数组来选择这些游走中跨过绝对值30的行,并调用 argmax 来取得坐标轴1的穿越时间:
crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
crossing_times.mean()
Out[234]: 498.88973607038122