从这里开始我们就要学习Python的一些工具包了,学习这些包也有一定的路线,那就是:这是什么包,可以做什么,怎么用。就像人类的终极三问:我是谁,我从哪里来,将到哪里去。在学Numpy时把这三个问题整明白了,你也就算是入门了。

Numpy是一个运行速度非常快的数学库,主要用于数组计算。它可以让你在Python中使用向量和数学矩阵。它主要包括:

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

Numpy是Python在科学计算领域取得成功的关键之一,如果你想通过Python学习数据科学或者机器学习,就必须学习Numpy。
—— Jamal Moir

创建数组

本节我们将依照标准的NumPy约定,即总是使用import numpy as np。当然,你也可以为了不写np.而直接在代码中使用from numpy import *,但是最好还是不要养成这样的坏习惯。

Numpy的核心是数组,具体来说是多维数组(ndarrays)。通过这些数组我们可以快速的使用像向量和数学矩阵之类的功能。创建数组最简单的办法就是使用array函数,它接收一切序列型的对象(还记得哪些是序列型对象吗?),然后产生一个新的含有传入参数的Numpy数组。

a = np.array([0, 1, 2, 3, 4])
print(a)
b = np.array((0, 1, 2, 3, 4))
print(b)
c = np.arange(5)
print(c)
d = np.linspace(0, 2*np.pi, 5)
print(d)

上边的代码展示了创建数组的四种不同方式。最基本的方式是传递一个序列给Numpy的 array() 函数;你可以传给它任意的序列,不仅仅是我们常见的列表之类的。比如其他数组。

注意: 本节中当你看到“数组”、“NumPy数组”、”ndarray”时,基本上都指的是同一样东西,即ndarray对象。

上边数组的例子给你展示了如何在 NumPy 中表示向量,接下来我们来看一下怎么表示矩阵和多维数组。

e = np.array([[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]])

print(e) 

通过给 array() 函数传递一个列表的列表(或者是一个序列的序列),可以创建二维数组。如果我们想要一个三维数组,那我们就传递一个列表的列表的列表,四维数组就是列表的列表的列表的列表,以此类推。

下面列出一些常用的数组创建函数。

函数 说明
array 将输入数据(列表、元组、数组或其他序列类型)转换为ndarray。要么推断出dtype,要么显式指定dtype。默认直接复制输入数据
asarray 将输入转换为ndarray,如果输入本身就是一个ndarray就不进行复制
arange 类似于内置的range,但是返回的是一个ndarray而不是列表
ones、ones_like 根据指定的形状和dtype创建一个全1数组。ones_like以另一个数组为参数,并根据其形状和dtype创建一个全1数组
zeros、zeros_like 类似于ones和ones_like,只不过产生的是全0数组而已
empty、empty_like 创建新数组,只分配内存空间但不填充任何值
eye、identity 创建一个正方的NxN单位矩阵(对角线为1,其余为0)

数组属性

在使用 NumPy 时,如果我们想知道数组的某些信息。怎么办呢?Numpy包里边包含了很多便捷的方法,可以给我们想要的信息。

e = np.array([[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]])
              
print(type(e))    # >>><class 'numpy.ndarray'>
print(e.dtype)    # >>>int64
print(e.size)     # >>>25
print(a.shape)    # >>>(5, 5)
print(a.itemsize)  # >>>8
print(a.ndim)     # >>>2
print(a.nbytes)   # >>>200

正如打印出来的信息一样,上边的代码中 NumPy 的数组其实被称为 ndarray,表示它是一个n维数组(n dimensional array)。

dtype(数据类型)是一个特殊的对象,它表示ndarray的数据类型。

类型 类型代码 说明
int8、uint8 i1、u2 有符号和无符号的8位(1字节)整型
int16、uint16 i2、u2 有符号和无符号的16位(2字节)整型
int32、uint32 i4、u4 有符号和无符号的32位(4字节)整型
int64、uint64 i8、u8 有符号和无符号的64位(8字节)整型
float16 f2 半精度浮点数
float32 f4或f 标准的单精度浮点数。与C的float兼容
float64 f8或d 标准的双精度浮点数。与C的double和python的float对象兼容
float128 f16或g 扩展精度浮点数
complex64 c8 复数,由两个 32 位浮点表示(实部和虚部)
complex128 c16 复数,由两个 64 位浮点表示(实部和虚部)
complex256 c32 复数,由两个 128 位浮点表示(实部和虚部)
bool 存储True和False值得布尔类型

注意:记不住这些NumPy的dtype也没关系,新手更是如此。通常只需要知道你所处理的数据的大致类型是浮点数、复数、整数、布尔值、字符串,还是普通的Python对象即可。当你需要控制数据在内存和磁盘中的存储方式时(尤其是对大数据集),那就得了解如何控制存储类型。

shape(数组的形状)是指它有多少行和列,上边的数组有五行五列,所以他的形状是(5,5)。

itemsize 属性是每一个条目所占的字节。这个数组的数据类型是 int64,一个 int64 的大小是 64 比特,8 比特为 1 字节,64 除以 8 就得到了它的字节数,8 字节。

ndim 属性是指数组有多少维。这个数组有二维。但是,比如说向量,只有一维。

nbytes 属性表示这个数组中所有元素占用的字节数。你应该注意,这个数值并没有把额外的空间计算进去,因此实际上这个数组占用的空间会比这个值大点。

基本索引和切片

数组的索引和Python中的列表或其他序列很像。你也可以对它们使用切片,这里我不再演示一维数组的切片,从表面上看,它们跟Python列表的功能差不多。

注意二维数组是如何成行成列排布的。如果要索引一个二维数组,只需要引用相应的行数和列数即可。

f = np.array([[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]])

print(f[2,4]) # >>>25

多维数组切片比一维数组要复杂一点,同时它也是你在用 NumPy 的时候经常会用到的。

print(f[0, 1:4])  # >>>[12 13 14]
print(f[1:4, 0])  # >>>[16 21 26]
print(f[::2,::2]) # >>>[[11 13 15]
                  #     [21 23 25]
                  #     [31 33 35]]
print(f[:, 1])    # >>>[12 17 22 27 32]

就像你看到的一样,多维数组切片就是要分别在每个维度上切片,并用逗号隔开。在二维数组中,第一个切片的含义是对行切片,第二个切片的含义是对列切片。

高级索引

花式索引

花式索引(Fancy indexing)是一个NumPy术语,它指的是利用整数数组进行索引,也称整数索引。假设我们有一个8×4数组:

arr = np.empty((8.4))
for i in range(8):
    arr[i] = i
print(arr)

为了以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray即可:

print(arr[[4, 3, 0, 6]]

使用负数索引将会从末尾开始选取行:

print(arr[[-3, -5, -7]])

如果传入两个索引数组,第一个为行索引,第二个为列索引。行索引包含所有行号,列索引指定要选择的元素。它返回的是一个一维数组,其中的元素对应各个索引元组:

print(arr[[1, 5, 7, 2], [0, 3, 1, 2]])

我们来看看具体是怎么一回事。最终选出的是元素(1,0)、(5,3)、(7,1)和(2,2)。这个花式索引的行为可能会跟我们的预期不一样,选取矩阵的行列子集应该是矩形区域的形式才对嘛。哈哈如果我们想得到这样的结果:

print(arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]])

布尔索引

当结果对象是布尔运算(例如比较运算符)的结果时,将使用此类型的高级索引。

大于5会作为布尔索引的结果返回:

import numpy as np 
x = np.array([[  0,  1,  2],[  3,  4,  5],[  6,  7,  8],[  9,  10,  11]])  
print(x) 
print(x[x >  5])

使用了~(取补运算符)来过滤NaN:

import numpy as np 
a = np.array([np.nan,  1,2,np.nan,3,4,5])  
print(a[~np.isnan(a)])

从数组中过滤掉非复数元素:

import numpy as np 
a = np.array([1,  2+6j,  5,  3.5+5j])  
print(a[np.iscomplex(a)])

使用数组

基本操作符

仅仅会赋值、取值和得到一些属性是不能满足你的需求的,有时候你还需要做一些数学运算。你可以利用基本的操作符实现这些,比如 +, -, /,等等。

a = np.arange(25)
a = a.reshape((5, 5))

b = np.array([10, 62, 1, 14, 2, 56, 79, 2, 1, 45, 4, 92, 
              5, 55, 63, 43, 35, 6, 53, 24,56, 3, 56, 44, 78])
b = b.reshape((5,5))

print(a + b)
print(a - b)
print(a * b)
print(a / b)
print(a ** 2)
print(a < b) 
print(a > b)

这些操作符都是对数组进行逐元素运算。比如 (a, b, c) + (d, e, f) 的结果就是 (a+d, b+e, c+f)。它将分别对每一个元素进行配对,然后对它们进行运算。它返回的结果是一个数组。注意,当使用逻辑运算符比如 “<” 和 “>” 的时候,返回的将是一个布尔型数组,这点有一个很好的用处,后边我们会提到。

统计函数

NumPy 有很多有用的统计函数,用于从数组中给定的元素中查找最小,最大,百分标准差和方差等。 函数说明如下:

numpy.amin() 和 numpy.amax() 这些函数从给定数组中的元素沿指定轴返回最小值和最大值。

import numpy as np 
a = np.array([[3,7,5],[8,4,3],[2,4,9]])  
print(np.amin(a))  
print(np.amin(a,1))    # 沿1轴
print(np.amin(a,0))    # 沿0轴
print(np.amax(a))
print(np.amax(a,1))    # 沿1轴
print(np.amax(a, axis =  0))   # 沿0轴

numpy.ptp()函数返回沿轴的值的范围(最大值 - 最小值)

import numpy as np 
a = np.array([[3,7,5],[8,4,3],[2,4,9]])  
print(np.ptp(a))
print(np.ptp(a, axis = 1))   # 沿1轴
print(np.ptp(a, axis = 0))   # 沿0轴

numpy.percentile()百分位数是统计中使用的度量,表示小于这个值的观察值的百分比。 函数numpy.percentile()接受以下参数。

numpy.percentile(a, q, axis)   # a 输入数组; q 要计算的百分位数,在 0 ~ 100 之间; axis 沿着它计算百分位数的轴
import numpy as np 
a = np.array([[30,40,70],[80,20,10],[50,90,60]])
print(np.percentile(a,50))     # 50%的分位数,就是a里排序之后的中位数
print(np.percentile(a,50, axis = 1))   # 沿1轴
print(np.percentile(a,50, axis = 0))   # 沿0轴

numpy.median()中值定义为将数据样本的上半部分与下半部分分开的值。 numpy.median()函数的用法如下面的程序所示

import numpy as np
a = np.array([[30,65,70],[80,95,10],[50,90,60]])
print(np.median(a))
print(np.median(a, axis = 0))
print(np.median(a, axis = 1))

numpy.mean()算术平均值是沿轴的元素的总和除以元素的数量。 numpy.mean()函数返回数组中元素的算术平均值。 如果提供了轴,则沿其计算。

import numpy as np
a = np.array([[1,2,3],[3,4,5],[4,5,6]])
print(np.mean(a))
print(np.mean(a, axis = 0))
print(np.mean(a, axis = 1))

numpy.average()加权平均值是由每个分量乘以反映其重要性的因子得到的平均值。 numpy.average()函数根据在另一个数组中给出的各自的权重计算数组中元素的加权平均值。 该函数可以接受一个轴参数。 如果没有指定轴,则数组会被展开。

考虑数组[1,2,3,4]和相应的权重[4,3,2,1],通过将相应元素的乘积相加,并将和除以权重的和,来计算加权平均值。

加权平均值 = (1*4+2*3+3*2+4*1)/(4+3+2+1)
import numpy as np 
a = np.array([1,2,3,4])
print(np.average(a))
wts = np.array([4,3,2,1])
print(np.average(a,weights = wts)) 
print(np.average([1,2,3,4],weights = [4,3,2,1], returned = True))

在多维数组中,可以指定用于计算的轴。

import numpy as np 
a = np.arange(6).reshape(3,2) 
wt = np.array([3,5])  
print(np.average(a, axis = 1, weights = wt))  
print(np.average(a, axis = 1, weights = wt, returned = True))

标准差-标准差是与均值的偏差的平方的平均值的平方根。 标准差公式如下:

std = sqrt(mean((x - x.mean())**2))

如果数组是[1,2,3,4],则其平均值为2.5。 因此,差的平方是[2.25,0.25,0.25,2.25],并且其平均值的平方根除以4,即sqrt(5/4)是1.1180339887498949。

import numpy as np 
print(np.std([1,2,3,4]))

方差-方差是偏差的平方的平均值,即mean((x - x.mean())** 2)。 换句话说,标准差是方差的平方根。

import numpy as np 
print(np.var([1,2,3,4]))

可能到这里你就会对一个‘轴’概念搞蒙了,这又是什么?我们来看一下:

X = np.reshape(np.arange(24), (2, 3, 4))
print(X)
print(X[:, :, 0])       # 每一个平面的构成
print(X[:, :, 1])
print(X[:, :, 2])
print(X[:, :, 3])       
print(np.amin(X, 0))     #   沿0轴
print(np.amin(X, 1))     #   沿1轴
print(np.amin(X, 2))     #   沿2轴

也即在对 np.arange(24)(0, 1, 2, 3, …, 23) 进行重新的排列时,在多维数组的多个轴的方向上,先分配最后一个轴(对于二维数组,即先分配行的方向,即axis=1表示行,axis=0表示列;对于三维数组即先分配平面的方向,即axis=2表示平面,axis=1表示每一个平面的每一行,axis=0表示每一个平面的每一列)

线性代数

NumPy 包包含numpy.linalg模块,提供线性代数所需的所有功能。 此模块中的一些重要功能如下表所述。

序列 函数及描述
1. dot 两个数组的点积
2. vdot 两个向量的点积
3. inner 两个数组的内积
4. matmul 两个数组的矩阵积
5. determinant 数组的行列式
6. solve 求解线性矩阵方程
7. inv 寻找矩阵的乘法逆矩阵