《利用Python进行数据分析》学习笔记ch04(5)
第4章 NumPy基础:数组和矢量计算
NumPy (Numerical Python的简称)是高性能科学计算和数据分析的基础包。是这本书所介绍的几乎所有高级工具的构建基础。
4.1NumPy的ndarray:一种多维数组对象
NumPy最重要的一个特点就是其N维数组对象(即ndarray),该对象是一个快速而灵活的大数据集容器。
ndarray是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的。每个数组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象)
4.1.1创建ndarray
创建数组最简单的办法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy。
import numpy as np
data1 = [6,7.5,8,0,1]
arr1 = np.array(data1)
arr1
array([ 6. , 7.5, 8. , 0. , 1. ])
嵌套序列(比如由一组等长列表组成的列表)将会被转换为一个多维数组:
data2 = [[1,2,3,4],[5,6,7,8]]
arr2 = np.array(data2)
arr2
array([[1, 2, 3, 4],
[5, 6, 7, 8]])
arr2.ndim
2
arr2.shape
(2, 4)
显示数据类型
arr1.dtype
dtype('float64')
arr2.dtype
dtype('int32')
除np.array之外,还有一些函数也可以新建数组。比如,zeros和ones分别可以创建指定长度和形状的全0和全1数组。empty可以创建一个没有任何具体值的数组。用这些方法创建多维数组,只需传入一个表示形状的元组即可。
np.zeros(10)
array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
np.zeros((3,6))
array([[ 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0., 0.]])
np.ones((2,3))
array([[ 1., 1., 1.],
[ 1., 1., 1.]])
np.empty((2,3,2))
array([[[ 1.20317161e-311, 6.36598740e-314],
[ 0.00000000e+000, 1.27319747e-313],
[ 1.27319747e-313, 1.27319747e-313]],
[[ 1.27319747e-313, 1.27319747e-313],
[ 8.41319374e+164, 3.14443259e+179],
[ 2.58470070e-313, 4.93432735e+257]]])
在很多情况下,np.empty返回的都是一些未初始化的垃圾值。
arange是Python内置函数range的数组版:
np.arange(15)
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
一些常用的数组创建函数
array 将输入数据(列表、元组、数组或其他序列类型)转换为ndarray。
asarray 将输入转换为ndarray,如果输入本身就是一个ndarray就不进行复制
arange 类似于内置的range,但返回的就是一个ndarray而不是列表
ones、ones_like ones_like以另一个数组为参数,并根据其形状和dtype创建一个全1数组
zero、zeros_like 产生全0数组
empty、empty_like 只分配内存空间但不填充任何值
eye、identity 创建一个正方的N x N单位矩阵(对角线为1,其余为0)
np.eye(6)
array([[ 1., 0., 0., 0., 0., 0.],
[ 0., 1., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0., 0.],
[ 0., 0., 0., 1., 0., 0.],
[ 0., 0., 0., 0., 1., 0.],
[ 0., 0., 0., 0., 0., 1.]])
4.1.2ndarray的数据类型
dtype(数据类型)是一个特殊的对象,它含有ndarray将一块内存解释为特定数据类型所需的信息。
arr1 = np.array([1,2,3],dtype=np.float64)
arr2 = np.array([1,2,3],dtype=np.int32)
arr1.dtype
dtype('float64')
arr2.dtype
dtype('int32')
数值型dtype的命名方式:一个类型名,后面跟一个用于表示元素位长的数字。
int、float、complex(复数)、bool(布尔类型)、object(Python对象类型)、string_(固定长度字符串),unicode
通过ndarray的astype方法显式地转换其dtype
arr = np.array([1,2,3,4,5])
arr.dtype
dtype('int32')
float_arr = arr.astype(np.float64)
float_arr.dtype
dtype('float64')
浮点数转换成整数,小数部分将会被截断:
arr = np.array([3.7,-1.2,-2.6,0.5,12.9,10.1])
arr
array([ 3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
arr.astype(np.int32)
array([ 3, -1, -2, 0, 12, 10])
如果某字符串组表示的全是数字,也可以用astype将其转换为数值形式:
numeric_strings = np.array(['1.25','-9.6','42'],dtype=np.string_)
numeric_strings
array([b'1.25', b'-9.6', b'42'],
dtype='|S4')
numeric_strings.astype(float)
array([ 1.25, -9.6 , 42. ])
numeric_strings
array([b'1.25', b'-9.6', b'42'],
dtype='|S4')
数组的dtype的另一个用法
int_array=np.arange(10)
calibers=np.array([.22,.270,.357,.380,.44,.50],dtype=np.float64)
int_array.astype(calibers.dtype)
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
还可以用简洁的类型代码来表示dtype:
empty_uint32=np.empty(8,dtype='u4')
empty_uint32
array([0, 0, 0, 0, 0, 0, 0, 0], dtype=uint32)
4.1.3数组和标量之间的运算
数组很重要,因为它使你不用编写循环即可对数据执行批量运算。这通常就叫做矢量化(vectorization)。大小相等的数组之间的任何算术运算都会将运算应用到元素级。
arr=np.array([[1.,2.,3.],[4.,5.,6.]])
arr
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
arr*arr
array([[ 1., 4., 9.],
[ 16., 25., 36.]])
arr-arr
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
数组与标量的算数运算也会将那个标量值传播到各个元素
1/arr
array([[ 1. , 0.5 , 0.33333333],
[ 0.25 , 0.2 , 0.16666667]])
arr*0.5
array([[ 0.5, 1. , 1.5],
[ 2. , 2.5, 3. ]])
不同大小的数组之间的运算叫做广播(broadcasting)
4.1.4基本的索引和切片
一维数组很简单,从表面上看,它们跟Python列表的功能差不多。
arr=np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr[5]
5
arr[5:8]
array([5, 6, 7])
arr[5:8]=12
arr
array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
将一个标量复制给一个切片时(如arr[5:8]=12),该值会自动传播(也就是后面会讲的广播)到整个选区。
跟列表最重要的区别在于,数组切片是原始数组的视图,这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上:
arr_slice=arr[5:8]
arr_slice[1]=1234
arr
array([ 0, 1, 2, 3, 4, 12, 1234, 12, 8, 9])
arr_slice
array([ 12, 1234, 12])
arr_slice[:]=64
arr
array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
arr_slice
array([64, 64, 64])
在一个二维数组中,各索引位置上的元素不再是标量而是一堆数组。
arr2d=np.array([[1,2,3],[4,5,6],[7,8,9]])
arr2d[2]
array([7, 8, 9])
下面两种访问元素的方式是等价的:
arr2d[0][2]
3
arr2d[0,2]
3
在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray。
arr3d=np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
arr3d
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d[0]
array([[1, 2, 3],
[4, 5, 6]])
标量值和数组都可以被赋值给arr3d[0]
old_values=arr3d[0].copy()
arr3d[0]=42
arr3d
array([[[42, 42, 42],
[42, 42, 42]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d[0]=old_values
arr3d
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[ 7, 8, 9],
[10, 11, 12]]])
arr3d[1,0]
array([7, 8, 9])
切片索引
arr[1:6] #这是前面的arr
array([ 1, 2, 3, 4, 64])
对高纬度对象,可以在一个或多个轴上进行切片,也可以跟整数索引混合使用。
arr2d
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
arr2d[:2]
array([[1, 2, 3],
[4, 5, 6]])
arr2d[:2,1:]
array([[2, 3],
[5, 6]])
arr2d[1,:2]
array([4, 5])
arr2d[2,:1]
array([7])
“只有冒号”表示选取整个轴
arr2d[:,:1]
array([[1],
[4],
[7]])
arr2d[:2,1:]=0
arr2d
array([[1, 0, 0],
[4, 0, 0],
[7, 8, 9]])
4.1.5布尔型索引
假设有一个用于存储数据的数组以及一个存储姓名的数组(含有重复项)。将使用numpy.random中的randon函数生成一些正太分布的随机数据:
names=np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
data=np.random.randn(7,4)
names
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'],
dtype='<U4')
data
array([[-0.09579414, 0.44941566, 0.06334145, -0.12607202],
[-0.07551462, -0.66297103, -1.45695496, -0.56527172],
[ 0.63798058, 0.13831193, 1.00999048, -0.36978514],
[-0.58749476, -0.46526609, -1.18718848, 0.94071767],
[ 0.74636389, 0.6088688 , 0.05842148, 0.27621051],
[ 0.08634887, -1.32465771, -0.2123516 , 1.48115521],
[-2.49197937, -0.77528574, -1.55484022, -0.8613561 ]])
names=='Bob'
array([ True, False, False, True, False, False, False], dtype=bool)
这个布尔型数组可用于数组索引
data[names=='Bob']
array([[-0.09579414, 0.44941566, 0.06334145, -0.12607202],
[-0.58749476, -0.46526609, -1.18718848, 0.94071767]])
布尔型数组的长度必须跟被索引的轴长度一致。此外,还可以将布尔型数组跟切片、整数混合使用
data[names=='Bob',2:]
array([[ 0.06334145, -0.12607202],
[-1.18718848, 0.94071767]])
data[names=='Bob',3]
array([-0.12607202, 0.94071767])
既可以使用不等号(!=),也可以通过符号(-)对条件进行否定。
names!='Bob'
array([False, True, True, False, True, True, True], dtype=bool)
data[-(names=='Bob')]
C:\Anaconda3\lib\site-packages\ipykernel\__main__.py:1: DeprecationWarning: numpy boolean negative, the `-` operator, is deprecated, use the `~` operator or the logical_not function instead.
if __name__ == '__main__':
array([[-0.07551462, -0.66297103, -1.45695496, -0.56527172],
[ 0.63798058, 0.13831193, 1.00999048, -0.36978514],
[ 0.74636389, 0.6088688 , 0.05842148, 0.27621051],
[ 0.08634887, -1.32465771, -0.2123516 , 1.48115521],
[-2.49197937, -0.77528574, -1.55484022, -0.8613561 ]])
选取这三个名字中的两个需要组合应用多个布尔条件,使用&(和)、|(或)之类的布尔算术运算符即可:
mask=(names=='Bob')|(names=='Will')
mask
array([ True, False, True, True, True, False, False], dtype=bool)
data[mask]
array([[-0.09579414, 0.44941566, 0.06334145, -0.12607202],
[ 0.63798058, 0.13831193, 1.00999048, -0.36978514],
[-0.58749476, -0.46526609, -1.18718848, 0.94071767],
[ 0.74636389, 0.6088688 , 0.05842148, 0.27621051]])
警告:Python关键字and和or在布尔型数组中无效。
将data中的所有负值都设置为0:
data[data<0]=0
data
array([[ 0. , 0.44941566, 0.06334145, 0. ],
[ 0. , 0. , 0. , 0. ],
[ 0.63798058, 0.13831193, 1.00999048, 0. ],
[ 0. , 0. , 0. , 0.94071767],
[ 0.74636389, 0.6088688 , 0.05842148, 0.27621051],
[ 0.08634887, 0. , 0. , 1.48115521],
[ 0. , 0. , 0. , 0. ]])
通过一维数组设置整行或整列的值:
data[names!='Joe']=7
data
array([[ 7. , 7. , 7. , 7. ],
[ 0. , 0. , 0. , 0. ],
[ 7. , 7. , 7. , 7. ],
[ 7. , 7. , 7. , 7. ],
[ 7. , 7. , 7. , 7. ],
[ 0.08634887, 0. , 0. , 1.48115521],
[ 0. , 0. , 0. , 0. ]])
4.1.6花式索引
花式索引(Fancy indexing)是一个NumPy术语,它指的是利用整数数组进行索引。
arr=np.empty((8,4))
for i in range(8):
arr[i]=i
arr
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]]
array([[ 4., 4., 4., 4.],
[ 3., 3., 3., 3.],
[ 0., 0., 0., 0.],
[ 6., 6., 6., 6.]])
使用负数索引将会从末尾开始选取行:
arr[[-3,-5,-7,-8]]
array([[ 5., 5., 5., 5.],
[ 3., 3., 3., 3.],
[ 1., 1., 1., 1.],
[ 0., 0., 0., 0.]])
一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组:
#有关reshape的知识将在第12章中讲解
arr=np.arange(32).reshape((8,4))
arr
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]]
array([ 4, 23, 29, 10])
arr[[1,5,7,2]][:,[0,3,1,2]]#前面的选行,后面的选列,交叉之后,对角线上的就是要的数字。
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])]
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])
花式索引跟切片不一样,它总是将数据复制到新数组中。
4.1.7数组转置和轴对换
转置(transpose)是重塑的一种特殊形式,它返回的是源数据的试图(不会进行任何复制操作)。数组不仅有transpose方法,还有一个特殊的T属性:
arr=np.arange(15).reshape((3,5))
arr
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
arr.T
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])
利用np.dot计算矩阵内积:
arr=np.random.randn(6,3)
np.dot(arr.T,arr) #3行6列乘6行3列
array([[ 4.89894782, -6.65385271, 4.55633952],
[ -6.65385271, 14.82959169, -7.53452034],
[ 4.55633952, -7.53452034, 5.88848523]])
对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置:
arr=np.arange(16).reshape((2,2,4))
arr
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
arr.transpose((1,0,2)) #这里的转置没有看懂
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]],
[[ 4, 5, 6, 7],
[12, 13, 14, 15]]])
ndarray还有一个swapaxes方法,它需要接受一对轴编号:
arr
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
arr.swapaxes(1,2)
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])
4.2通用函数:快速的元素级数组函数
通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数,可以将其看作简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。
许多ufunc都是简单的元素级变体,如sqrt和exp:
arr=np.arange(10)
np.sqrt(arr)
array([ 0. , 1. , 1.41421356, 1.73205081, 2. ,
2.23606798, 2.44948974, 2.64575131, 2.82842712, 3. ])
np.exp(arr)
array([ 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])
x=np.random.randn(8)
y=np.random.randn(8)
x
array([-0.60009486, -0.65277735, 0.8834158 , 0.12107996, 0.80851149,
1.33662151, -2.21588327, -1.06546608])
y
array([ 1.37178435, -0.68547186, -1.34764676, 1.43077179, 0.71101778,
-2.03954079, -0.38354531, 0.09934629])
np.maximum(x,y) #元素级最大值
array([ 1.37178435, -0.65277735, 0.8834158 , 1.43077179, 0.80851149,
1.33662151, -0.38354531, 0.09934629])
ufunc可以返回多个数组。modf是Python内置函数divmod的矢量化版本,用于浮点数数组的小数和整数部分。以两个独立数组的形式返回
arr=np.random.randn(7)*5
np.modf(arr)
(array([-0.73931298, -0.89978596, -0.11174737, -0.39405759, -0.97512539,
-0.73016793, -0.15706853]), array([-3., -7., -9., -9., -2., -8., -2.]))
4.3利用数组进行数据处理
用数组表达式代替循环贷做法,通常被成为矢量化。
假设我们想要在一组值(网格型)上计算函数sqrt(x^2+y^2)。np.meshgrid的函数接受两个一维数组,并产生两个二维矩阵(对应于两个数组中所有的(x,y)对):
points=np.arange(-5,5,0.01) #1000个间隔相等的点
xs,ys=np.meshgrid(points,points)
ys
array([[-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
array([[-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]])
import matplotlib.pyplot as plt
z=np.sqrt(xs**2+ys**2)
z
array([[ 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]])
%matplotlib inline
plt.title("Image plot of $\sqrt{x^2+y^2}$ for a grid of values")
plt.imshow(z,cmap=plt.cm.gray)
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x2376205add8>
4.3.1将条件逻辑表述为数组运算
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])
假设我们想要根据cond中的值选取xarr和yarr的值:当cond中的值为True时,选取xarr的值,否则从yarr中选取。使用np.where,则可以将该功能写的非常简洁。
result = np.where(cond,xarr,yarr)
result
array([ 1.1, 2.2, 1.3, 1.4, 2.5])
np.where的第二个和第三个参数不必是数组,他们都可以是标量值。在数据分析工作中,where通常用于根据另一个数组而产生一个新的数组。
假设有一个由随机数据组成的矩阵,将所有正值替换为2,将所有负值替换为-2
arr=np.random.randn(4,4)
arr
array([[-1.67996192, -1.52076103, -1.36474319, -0.72582546],
[-1.81193487, 0.15831915, 0.43843968, -1.24258572],
[ 2.0444632 , -0.17322205, -0.56679169, -1.89214385],
[-0.55643441, -0.03256283, 1.04896793, -1.05919124]])
np.where(arr>0,2,-2)
array([[-2, -2, -2, -2],
[-2, 2, 2, -2],
[ 2, -2, -2, -2],
[-2, -2, 2, -2]])
np.where(arr>0,2,arr)#只将正值设置为2
array([[-1.67996192, -1.52076103, -1.36474319, -0.72582546],
[-1.81193487, 2. , 2. , -1.24258572],
[ 2. , -0.17322205, -0.56679169, -1.89214385],
[-0.55643441, -0.03256283, 2. , -1.05919124]])
4.3.2数学和统计方法
通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean以及标准差std等聚合计算(aggregation)既可以当作数组的实例方法调用,也可以当作*NumPy函数使用:
arr=np.random.randn(5,4)#正太分布的数据
arr.mean()
-0.10229141464002138
np.mean(arr)
-0.10229141464002138
arr.sum()
-2.0458282928004277
arr
array([[-0.43116131, 0.51549724, 0.83101156, -0.33551315],
[ 0.55031463, 0.02006324, 1.09493371, -0.12046588],
[ 0.627503 , 0.39514287, -0.7054783 , -0.65368648],
[-1.36963797, -0.12663308, 0.37923112, 0.011082 ],
[ 0.3298322 , -0.10022833, -2.53777174, -0.41986361]])
mean和sum类的函数可以接受一个axis参数(用于计算该轴向上的统计值),最终结果是一个少一维的数组:
arr.mean(axis=1)#每一行的算术平均数
array([ 0.14495858, 0.38621142, -0.08412973, -0.27648948, -0.68200787])
arr.sum(0)#每一列的算数和
array([-0.29314944, 0.70384193, -0.93807366, -1.51844713])
其他如cumsum和cumprod之类的方法则不聚合,而是产生一个由中间结果组成的数组
arr=np.array([[0,1,2],[3,4,5],[6,7,8]])
arr.cumsum(0) #累计和:第1行到第m行的所有元素累加和
array([[ 0, 1, 2],
[ 3, 5, 7],
[ 9, 12, 15]], dtype=int32)
arr.cumprod(1) #累计积:每一列向量累积连乘的结果
array([[ 0, 0, 0],
[ 3, 12, 60],
[ 6, 42, 336]], dtype=int32)
4.3.3用于布尔型数组的方法
arr=np.random.randn(100)
(arr>0).sum() #正值的数量
48
any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True:
bools=np.array([False,False,True,False])
bools.any()
True
bools.all()
False
4.3.4排序
NumPy数组也可以通过sort方法就地排序:
arr=np.random.randn(8)
arr
array([ 0.53497467, 0.03571417, 1.56235859, -1.27847367, 0.63569895,
-0.07901753, 0.5300618 , -0.23415381])
arr.sort()
arr
array([-1.27847367, -0.23415381, -0.07901753, 0.03571417, 0.5300618 ,
0.53497467, 0.63569895, 1.56235859])
多维数组可以在任何一个轴向上进行排序,只需将轴编号传给sort即可:
arr=np.random.randn(5,3)
arr
array([[-0.88591564, 0.27293937, -0.76711151],
[-1.12301114, -0.56377763, 1.97032586],
[ 1.13397421, -0.97623014, 0.64121741],
[-1.23155629, 0.59293742, -2.08210442],
[ 0.61323608, 0.12385609, -0.94560293]])
arr.sort(1)
arr
array([[-0.88591564, -0.76711151, 0.27293937],
[-1.12301114, -0.56377763, 1.97032586],
[-0.97623014, 0.64121741, 1.13397421],
[-2.08210442, -1.23155629, 0.59293742],
[-0.94560293, 0.12385609, 0.61323608]])
4.3.5唯一化以及其他的集合逻辑
np.unique用于找出数组中的唯一值并返回已排序的结果。
names=np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
np.unique(names)
array(['Bob', 'Joe', 'Will'],
dtype='<U4')
ints=np.array([3,3,3,2,2,1,1,4,4])
np.unique(ints)
array([1, 2, 3, 4])
np.in1d用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组:
values=np.array([6,0,0,3,2,5,6])
np.in1d(values,[2,3,6])
array([ True, False, False, True, True, False, True], dtype=bool)
4.4用于数组的文件输入输出
NumPy能够读写磁盘上的文本数据或二进制数据
4.4.1将数组以二进制格式保存到磁盘
np.save和np.load是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为.npy的文件中的。
arr=np.arange(10)
np.save('some_array',arr)
np.load('some_array.npy')
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
通过np.savez可以将多个数组保存到一个压缩文件中,将数组以关键字参数的形式传入即可:
np.savez('array_archive.npz',a=arr,b=arr)
arch=np.load('array_archive.npz')
arch['b']
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
4.4.2存取文本文件
arr=np.loadtxt('C:\\pytest\\ch04\\array_ex.txt',delimiter=',')
arr
array([[ 0.580052, 0.18673 , 1.040717, 1.134411],
[ 0.194163, -0.636917, -0.938659, 0.124094],
[-0.12641 , 0.268607, -0.695724, 0.047428],
[-1.484413, 0.004176, -0.744203, 0.005487],
[ 2.302869, 0.200131, 1.670238, -1.88109 ],
[-0.19323 , 1.047233, 0.482803, 0.960334]])
np.savetxt执行的是相反的操作。
4.5线性代数
NumPy提供了一个用于矩阵乘法的dot函数(既是一个数组方法也是numpy命名空间中的一个函数):
x=np.array([[1.,2.,3.],[4.,5.,6.]])
y=np.array([[6.,23.],[-1,7],[8,9]])
x
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
y
array([[ 6., 23.],
[ -1., 7.],
[ 8., 9.]])
x.dot(y) #相当于np.dot(x,y)
array([[ 28., 64.],
[ 67., 181.]])
np.ones(3)
array([ 1., 1., 1.])
np.dot(x,np.ones(3))#这里有点奇怪,照理说ones(3)是个1行3列矩阵,不能和2行3列乘的,这里好像是把x转置了
array([ 6., 15.])
numpy.linalg中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的东西。
from numpy.linalg import inv,qr
X=np.random.randn(5,5)
mat=X.T.dot(X)
inv(mat)
array([[ 2.31391458e+00, 1.46690113e+00, 5.46264899e-01,
3.08017402e-01, -1.38759677e+00],
[ 1.46690113e+00, 5.94590138e+00, 1.22382024e-02,
1.74822439e-01, -3.97526290e+00],
[ 5.46264899e-01, 1.22382024e-02, 2.14462709e+00,
-1.39783561e+00, 4.49783651e-03],
[ 3.08017402e-01, 1.74822439e-01, -1.39783561e+00,
1.56095003e+00, -3.60085410e-01],
[ -1.38759677e+00, -3.97526290e+00, 4.49783651e-03,
-3.60085410e-01, 3.03538784e+00]])
mat.dot(inv(mat))
array([[ 1.00000000e+00, 0.00000000e+00, 4.20670443e-17,
1.38777878e-16, 0.00000000e+00],
[ 4.44089210e-16, 1.00000000e+00, 2.01227923e-16,
-1.11022302e-16, 0.00000000e+00],
[ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00,
-3.88578059e-16, 0.00000000e+00],
[ 2.22044605e-16, 4.44089210e-16, 3.55618313e-17,
1.00000000e+00, 0.00000000e+00],
[ 8.88178420e-16, 1.77635684e-15, 1.31838984e-16,
0.00000000e+00, 1.00000000e+00]])
q,r=qr(mat)
r
array([[-1.43612115, -0.80587675, 1.88203739, 1.98287746, -1.61079743],
[ 0. , -2.89178127, -1.61773731, -2.34026805, -4.29534052],
[ 0. , 0. , -0.98888937, -1.14324269, -0.12778707],
[ 0. , 0. , 0. , -0.47894831, -0.04924987],
[ 0. , 0. , 0. , 0. , 0.19219613]])
4.6随机数生产
用normal来得到一个标准正太分布数组:
samples = np.random.normal(size=(4,4))
samples
array([[-1.14444694, 0.17302754, -0.77575518, 0.0943392 ],
[-0.90267398, -0.64764345, 0.09607828, -0.87854896],
[-0.51893412, 0.11238075, 1.27695305, -0.07033393],
[ 1.05369546, 1.6923675 , -0.44648033, -0.2803228 ]])
4.7范例:随机漫步
通过模拟随机漫步来说明如何运用数组运算。
一个简单的随机漫步的例子:从0开始,步长1和-1出现的概率相等。通过内置的random模块以纯Python的方式实现1000步的随机漫步:
import random
position=0
walk=[position]
steps=1000
for i in range(steps):
step=1 if random.randint(0,1) else -1
position += step
walk.append(position)
plot绘图还不会,书上也没讲详细。
用np.random模块一次性随机产生1000个“掷硬币”结果(即两个数中任选一个),将其分别设置为1或-1,然后计算累计和:
nsteps=1000
draws=np.random.randint(0,2,size=nsteps)
steps=np.where(draws>0,1,-1)
walk=steps.cumsum()
walk.min()
-22
walk.max()
12
首次穿越时间,即随机漫步过程中第一次达到某个特定值的时间。
(np.abs(walk)>=10).argmax()
39
4.7.1一次模拟多个随机漫步
只要给numpy.random的函数传入一个二元元组就可以产生一个二维数组,然后我们就可以一次性计算5000个随机漫步过程(一行一个)的累计和:
nwalks=5000
nsteps=1000
draws=np.random.randint(0,2,size=(nwalks,nsteps)) #0或1
steps=np.where(draws>0,1,-1)
walks=steps.cumsum(1)
walks
array([[ 1, 2, 1, ..., -44, -43, -42],
[ -1, -2, -3, ..., -14, -13, -12],
[ -1, -2, -1, ..., -34, -33, -34],
...,
[ 1, 2, 1, ..., -6, -5, -4],
[ 1, 0, 1, ..., -6, -7, -8],
[ 1, 0, -1, ..., 6, 5, 6]], dtype=int32)
walks.max()
120
walks.min()
-118
hits30=(np.abs(walks)>=30).any(1)
hits30
array([ True, True, True, ..., False, False, False], dtype=bool)
hits30.sum()
3387
选出那些穿越了30(绝对值)的随机漫步(行),并调用argmax在轴1上获取穿越时间
crossing_times=(np.abs(walks[hits30])>=30).argmax(1)
crossing_times.mean()
499.14230882787126