NumPy基础:数组和矢量计算

NumPy(Numerical Python的简称)是高性能科学计算和数据分析的基础包。其部分功能包括:

  • ndarray ,一个具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组。
  • 用于对整组数据进行快速运算的标准数学函数(无需编写循环)
  • 用于读写磁盘数据的工具以及用于操作内存内存映射文件的工具。
  • 线性代数、随机数生成以及傅里叶变换功能。
  • 用于集成有C++,C,Fortran等编程语言的代码工具。

对于大部分数据分析应用而言,我最关注的功能主要集中在:

  • 用于数据整理和清理、子集构造和过滤、转换等快速的矢量化数组运算。
  • 通常的数组加法,如排序、唯一化、集合运算等。
  • 高效的描述统计和数据聚合/摘要运算。
  • 用于异构数据集的合并/连接运算的数据对齐和关系型数据运算
  • 将条件逻辑表述为数组表达式(而不是带有if-elif-else分支的循环)
  • 数据的分组运算(集合、转换、函数应用等)

一、NumPy的ndarray:一组多维数组对象

NumPy最重要的一个特点就是:其N维数组对象(即ndarray),该对象是一个快速而灵活的大数据集容器,可以利用这种数组对整块数据执行一些数学运算。

ndarray是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的。每个数组都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数据类型的对象)

1.创建ndarray

创建数组最简单的方法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy的数组。

嵌套序列(比如由一组等长列表组成的列表),将会被转换成一个多维数组。

  • 注:np.array会尝试为新建的这个数组推断出一个较为合适的数据类型。数据类型保存在一个特殊的dtype对象中。

  • 除了np.array之外,还有一些函数也可以新建数组。比如zeros和ones分别可以创建指定长度或形状的全0或全1数组。empty可以创建一个没有任何具体值的数组。要用这些方法创建多维数组,只需传入一个表示形状的元组即可。

  • arange 是python内置函数range的数组版.

  • 由于NumPy关注的是数值计算,因此,如果没有特别指定,数据类型基本都是fioat64(浮点数)

  • array 将输入数据(列表、元组、数组或其他序列类型)转换为ndarry。要么推断出dtype,要么显示指定dtype。默认直接复制输入数据。

  • asarray 将输入转化为ndarray,如果输入本身就是一个ndarray就不进行复制。

  • arange类似内置的range,但返回的是一个ndarray,而不是列表。

  • ones、ones_like根据指定形状和dtype创建一个全1数组。 ones_like以另一个数组为参数,并根据其形状和dtype创建一个全1数组。

  • zeros、zeros_like类似于ones、ones_like,只不过产生的是全0数组而已。

  • eye、identity创建一个正方的N*N单位矩阵(对角线为1,其余为0)

2.ndarray的数据类型

dtype(数据类型)是一个特殊的对象,它含有ndarray将一块内存解释为特定数据类型所需的信息。

数值形dtype的命名方式相同:一个类型名(如float或int),后面跟一个用于表示各元素位长的数字。

  • 可以通过ndarray的astype方法显式地转化其dtype。
  • 注:调用astype无论如何都会创建一个新的数组(原始数据的一份拷贝),即使新dtype跟老dtype相同也是如此。

3.数组和标量之间的运算

  • 数组很重要,因为它使你不用编写循环即可对数据执行批量运算。这通常就叫做矢量化。大小相等的数组之间的任何算术运算都会将运算应用到元素级。

  • 同样,数组与标量的算术运算也会将那个标量值传播到各个元素。

  • 不同大小的数组之间的运算叫做广播

4.基本的索引和切片

NumPy数组的索引是一个内容丰富的主题,因为选取数据子集或单个元素的方式有限多。

  • 当你将一个标量值赋值给一个切片时(如arr[5:8]=12),该值会自动传播到整个选区。跟列表最重要的区别在于,数组切片是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上。

  • 注:如果你想得到的是ndarray切片的一份副本而非视图,就需要显式地进行复制操作,例如:arr[5:8].copy()

  • 对于高维度数组,能做的事情更多。在一个二维数组中,各索引位置上的元素不再是标量而是一维数组。

  • 可以对各个元素进行递归访问,但这样需要做的事情有点多。可以传入一个以逗号隔开的索引列表来获取单个元素。也就是说,下面两种方式是等价的:arr2d[0][2]
    arr2d[0,2]

在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray(它含有高一级维度上的所有数据)

5.切片索引

  • ndarray的切片语法跟Python列表这样的一维对象差不多

  • 高维度对象的花样更多,你可以在一个或多个轴上进行切片,也可以跟整数索引混合使用。它是沿着第0轴(即第一个轴切片的)。也就是说,切片是沿着一个轴向选取元素的,可以一次传入多个切片,就像传入多个索引那样。
    arr2d[ :2,1: ]

  • 注:括号外面的“维度”是一维、二维、三维、四维之类的意思,而括号里面的应该理解为“轴”。

  • 注:“只有冒号”表示选取整个轴

6.布尔型索引

布尔型数组的长度必须跟被索引的轴的长度一致。此外,还可以将布尔型数组跟切片、整数(或整数序列)混合使用

  • 通过布尔型索引选取数组中的数据,将总是创建数据的副本,即使返回一模一样的数组也是如此。

  • 注:python关键字and和or在布尔型数组中无效。

  • 需要组合应用多个布尔条件,使用&(和)、|(或)之类的布尔算术运算符即可。

  • 通过布尔型数组设置值是一种经常用到是手段。为了将data中的所有负值都设置为0,我们只需:data[data<0]=o

7.花式索引

花式索引(Fancy indexing)是一个NumPy术语,它指的是利用整数数组进行索引。

  • 一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组。

注:有关reshape的知识将在第十二章中讲解

In [108]:arr=np.arange(32).reshape((8,4))
In [109]:arr
out [109]:
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]])
In [110]:arr[[1,5,7,2],[0,3,1,2]]
Out [110]:array([4,23,29,10])
这个[110]索引,最终选出来的是元素(1,0)、(5,3)、(7,1)和(2,2)。
In [111]:arr[1,5,7,2][ : , [0,3,1,2]]
Out [111]:
array([[4,7,5,6,],
[20,23,21,22],
[28,31,29,30],
[8,11,9,10]])
另外一个办法是使用np.ix_函数,它可以将两个一维整数数组转换为一个用于选取方形区域的索引器:
In[112]:arr[np.ix_([1,7,5,2],[0,3,1,2])]
Out[112]:
array([[4,7,5,6,],
[20,23,21,22],
[28,31,29,30],
[8,11,9,10]])

  • 注:花式索引跟切片不一样,它总是将数据复制到新数组中。

8.数组转置和轴对换

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

In[110]:arr=np.arange(15).reshape((3,5))
In[111]:arr
Out[110]:
array([[0,1,2,3,4],
[5,6,7,8,9,],
[10,11,12,13,14]])

In[112]:arr.T
Out[112]:
array([[0,5,10],
[1,6,11],
[2,7,12]
[3,8,13],
[4,9,14]])
在进行矩阵计算时,经常需要要到该操作,比如利用np.dot计算矩阵内积XTX:

In[113]:arr=np.random.randn(6,3)
In[114]:np.dot(arr.T,arr)
Out[114]:
array([[2.584,1.8753,0.8888],
[1.8753,6.6636,0.3884],
[0.8888,0.3884,3.9781])

  • 对于高维数组,transpose需要得到一个由轴编号组成元组才能对这些轴进行转置(比较费脑子)
    In[115]:arr=np.arange(16)reshape((2,2,4))
    In[116]:arr
    Out[116]:
    array([[[0,1,2,3],
    [4,5,6,7]],
    [[8,9,10,11],
    [12,13,14,15]]])

In[117]:arr.transpose((1,0,2))
Out[117]:
array([[[0,1,2,3],
[8,9,10,11]],
[[4,5,6,7],
[12,13,14,15]]])

简单的转置可以使用.T,它其实就是轴对换而已;ndarry还有一个swapaxes方法,它需要接受一对轴编号。
In[118]:arr
Out[118]:
array([[[0,1,2,3],
[4,5,6,7]],
[[8,9,10,11],
[12,13,14,15]]])

In[119]:arr.swapaxes(1,2)
Out[119]:
array([[[0,4],
[1,5],
[2,6],
[3,7]],
[[8,12],
[9,13],
[10,14],
[11,15]]])

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

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

  • 通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。可以将其看作简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。
  • 许多ufunc都是简单的元素级变体,比如sqrt和exp

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

numpy数组可以将许多种数据处理任务表述为简洁的数组表达式。用数组表达式代替循环的做法,通常被称为矢量化。

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

numpy.where函数是三元表达式 x if condntion else y 的矢量化版本
np.where的第二个和第三个参数不必是数组,他们都可以是标量值。在数据分析工作中,where通常用于根据另外一个数组而产生一个新数组。
例:假如有一个由随机数组成的矩阵,将所有正值替换为2,将所有负值替换为-2——np.where(arr>0,2,-2)
np.where(arr>0,2,arr)#只将正值设置为2.

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

2.数学和统计方法

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

  • mean和sum这类的函数可以接受一个axis参数(用于计算该轴向上的统计值),最终结果是一个少一维数组。
  • sum 对数组中全部或某轴向元素求和。零长度的数组的sum为0
  • mean算术平均数。零长度数组的mean为NaN。
  • std、var分别为标准差和方差,自由度可调(默认为n)
  • min、max最大值和最小值
  • argmin、argmax分别为最小和最大元素的索引。
  • cumsum所有元素的累计和
  • cumpord所有元素的累计积

3.用于布尔型数组的方法

  • 在上面这些方法中,布尔值会被强制转化为1(true)和0(false),因此sum经常被用来对布尔型数组中的True值计数:
    In[160]:arr =randn(100)
    (arr>0).sum()#正值的数量
  • 另外还有两个方法any和all,他们对布尔型数组非常有用。any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True
    In[162]:bools=np.array([False,False,True,False])
    In[163]:bools.any()
    Out[163]:True
    In[163]:bools.all()
    Out[164]:False

4.排序

根python内置的列表类型一样,NumPy数组也可以通过sort方法就地排序

  • 多维数组可以在任何一个轴上排序,只需将轴编号传给sort即可。
  • 顶级方法np.sort返回的是数组的已排序副本,而就地排序则会修改数组本身。计算数组分位数最简单的办法是对其进行排序,然后选取特定位置的值。

5.唯一化以及其他的集合逻辑

NumPy提供了一些针对一维ndarray的基本集合运算。最常用的可能要数np.uniqe了, 它用于找出数组中的唯一值并返回已排序结果。

数组的集合运算

  • unique(x)计算x中的唯一元素,并返回有序结果
  • intersect1d(x,y)计算x和y中的公共元素,并返回有序结果
  • union1d(x,y)计算x和y的并集,并返回有序结果
  • in1d(x,y)得到一个表示“x的元素是否包含于y”的布尔型数组
  • setdiff1d(x,y)集合的差,即元素在X中且不在y中
  • setxor1d(x,y)集合的对称差,即存在于一个数组中但不同时存在于两个数组中的元素。

四、用于数组的文件输入输出

1.将数组以二进制格式保存到磁盘

np.save和np.load是读写磁盘数组数据的两个主要函数。默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为.npy的文件中的。
In[183]:arr=np.arange(10)
In[184]:np.save('some_array',arr)

  • 如果文件路径末尾没有扩展名.npy,则该扩展名会被自动加上。然后具可以通过np.load读取磁盘上的数组:
    In[185]:np.load('some_array.npy')
    Out[185]:array([0,1,2,3,4,5,6,7,8,9])
  • 通过np.savez可以将多个数组保存到一个压缩文件中,将数组以关键字参数的形式传入即可

2.存取文本文件

有时,我们需要用np.loadtxt或更为专门化的np.genfromtxt将数据加载到普通的NumPy数组中。

五、线性代数

线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分。NumPy提供了一个用于矩阵乘法的dot函数(既是一个数组方法也是NumPy命名空间的一个函数)

  • dot矩阵乘法
  • trane计算对角线元素的和
  • deg计算矩阵行列式
  • eig计算方阵的本征值和本征向量
  • inv 计算方阵的逆

六、随机数生成

numpy.random模块对Python内置的random进行了补充,增加了一些用于高效生成多种概率分布的样本值的函数。

  • 用normal得到一个标准正态分布的4*4样本数组:

七、范例:随机漫步

推荐阅读更多精彩内容