什么是 numpy
numpy 是 Python 的一个科学计算包。提供了多种array
对象、衍生对象(masked arrays 和 matrices
)、及其对其日常快速操作,包括数学、逻辑、形状操作、分类、选择、I/O、离散傅里叶(discrete Fourier transforms)、基本线性代数、基本数据操作、随机墨迹等。
numpy 包的核心是ndarray
,他分装了n-维 array 对象(每个维度的数据类型一致),这使得各种操作都在编译好的代码中执行,从而提高了性能。
NumPyarray
对象 与 Python 标准序列区别:
- NumPy 对象在创建时有一个固定的大小,而 Python 的 list 没有。改变 ndarray 的大小会删除原来对象,创建一个新的对象
- NumPy所有数据类型是一致的,这样在内存中占用的大小才是一致的。例外:可以有对象数组(Python,包括 NumPy),允许不同大小元素的数组。
- NumPy 对象更容易对于大量数据进行高级数学和其他类型操作。相较于 Python 内置序列,NumPy 操作执行效率更高,代码更少。
- 越来越多的科学和数学 Python 包会使用 NumPy 包,尽管它们支持 Python 内置序列,但在操作之前都会转换成 NumPy 对象。换言之,要想有效的使用现在的科学\数学 Python 包,只了解 Python 内置序列是不够的,我们也要了解 NumPy 对象的使用。
科学计算的关键就是序列大小和速度。举个简单例子,在一个一维的序列中,每个元素要与另一个长度相同的序列相应的元素做乘积,我们可以迭代每个元素。
c = []
for i in range(len(a)):
c.append(a[i]*b[i])
这个过程会输出正确答案,但如果列表 a 和 b 每个元素都是几百万,那我们就会为 Python 低效的循环付出代价。c 语言可以更快的完成相同任务(我们假设不去做变量声明、初始化和内存分配):
for(i = 0; i < rows; i++):{
c[i] = a[i]*b[i]
}
这解决了解释Python代码和操作Python对象所涉及的所有开销,但牺牲了Python编码的好处。此外,随着数据维度增加,工作量也增加,如在 2 维数组中,c 语言编码为
for (i = 0; i < rows; i++): {
for (j = 0; j < columns; j++): {
c[i][j] = a[i][j]*b[i][j];
}
}
而 NumPy 为这两张方案这个为我们提供了最优解:当涉及到 ndarray 时,逐个元素操作是默认模式,而实际上是由 提前变异的C语言快速执行。NumPy 代码运行速度与 C 语言接近,同时基于 Python 代码也很简洁(比 Python 还简介简洁!)。最后一个例子说明了 NumPy 的两个优势:矢量化(vectorization)和广播(broadcasting)。c = a*b
矢量化
矢量化意味着代码中没有肉眼可见的循环和索引,而他们其实发生了,只不过优化后“秘密的进行着”——由预编译的 C 语言运行。
矢量化优点
- 矢量化代码更简洁易读
- 代码越少,bug 越少
- 代码更类似于标准的数学符号(这样修改代码的数学结构会更容易)
- 向量化会有更多“Pythonic”代码,没有矢量化,代码就会充斥着低效和难以读取的循环。
广播
广播是一个术语,用于描述操作的隐式逐个元素的行为。通常说来,在 NumPy中,包括算数、逻辑、按位、函数等所有操作,以这种隐式逐个元素的方式进行——即广播。此外,上述举例中,a 和 b 可以是形状相同的多维数组、标量、数组,甚至可以是不同形状的两个数组(前提是较小的数组扩大成较大数组的形状)。更多信息请见 broadcasting。
NumPy 的 ndarray 全面支持面向对象。例如,ndarray是一个类,具有多种方法和对象。其中,许多方法都被NumPy最外层命名空间函数反映,允许程序员在他们喜欢的任何范式中编写代码。这种灵活性使得 NumPy 数组和 NumPyndarray 成为了Python 中多位数据交互的实际语言。
安装
安装 NumPy 唯一需要的条件就是 Python。如果你不懂 Python,只想用最简单的方式的话,我们推荐anaconda,它包括了 Python、NumPy 和许多其他常见科学计算和数据分析的Python 包。
Linux 和 macOS 安装 NumPy 可以使用pip
和conda
,或者使用下载源。更多信息请参考https://numpy.org/install/#py...。
》
- donda
你可以从defaults
或者conda-forge
安装 numpy。
Best practice, use an environment rather than install in the base env
conda create -n my-env
conda activate my-env
# If you want to install from conda-forge
conda config --env --add channels conda-forge
# The actual install command
conda install numpy
- pip
pip install numpy
NumPy 快速启动
使用 NumPy 前需要先了解Python;需要安装matplotlib
。
学习目标
- 了解一维、二维和 n 维数组的区别
- 了解在不使用 for 循环的情况下如何将一些线性代数应用到 n 维数组中
- 了解 n 维数组的轴和形状特性
[基础知识]
NumPy 的主要对象为均匀的多维数组。数组是元素表(通产为数字),所有元素的类型一致,由一个非负整数的元组进行索引。在 NumPy 中,维度称为轴(axes)。
例如,在一个3 维空间中,一个点的坐标数组为[1, 2, 1],它只有一个轴,轴含有 3 个元素,因此我们可以说其长度为 3。下面的例子中的数组中有 2 个轴,第一个轴的长度为 2,第二个长度为 3.
[[1., 0., 0.],
[0., 1., 2.]]
NumPy 的数组类称为ndarray
,别名为array
。记住:numpy.array
与 Python 标准库的类array.array
不同,后者只能解决以为数组,且功能较少。
ndarray
最重要的参数:
- ndarray.ndim :数组维度的数量
- ndarray.shape: 数组的维度,返回一个标明每个维度的数组的大小的元组,如一个 n 行 m 列的矩阵,其 shape 为
(n,m)
,元组的长度就是ndim
值。 - ndarray.size:数组所有元素的个数,等于
shape
所有值的乘积 - ndarray.dtype:数组中元素的数据类型,我们可以创建或者指定为标准 Python 数据类型,也可以使用 NumPy 提供的类型,如numpy.int32、numpy.int16、numpy.float64等
- ndarray.itemsize:数组中每个元素的 字节大小,例如
float64
的大小为 8(64/8),float64
为 4 字节。等同于ndarray.dtype.itemsize
. - ndarray.data:
创建数组
- 将列表或者元组转换为数组
import numpy as np
a = np.array([2, 3, 4])
a #array([2, 3, 4])
a.dtype #dtype('int64')
b = np.array([1.2, 3.5, 5.1])
b.dtype #dtype('float64')
有一种常见的错误就是放入太多了参数,而要求是放入一个为序列的参数
a = np.array(1, 2, 3, 4) # WRONG
Traceback (most recent call last):
...
TypeError: array() takes from 1 to 2 positional arguments but 4 were given
a = np.array([1, 2, 3, 4]) # RIGHT
array
方法 将含有一层序列的序列转换成二维数组,将含有两层序列的序列转换成三维数组,以此类推。
b = np.array([(1.5, 2, 3), (4, 5, 6)])
b
array([[1.5, 2. , 3. ],
[4. , 5. , 6. ]])
b.shape #(2,3)
创建数组时可以指定数据类型
c = np.array([[1, 2], [3, 4]], dtype=complex)
c
array([[1.+0.j, 2.+0.j],
[3.+0.j, 4.+0.j]])
- 通过原始占位符创建数组
通常来说,数组元素的类型未知,但其大小一直。因此,NumPy 也可以通过原始占位符的方法创建数组,这种方法减少了扩充数组这种高成本操作的的需要。 - zero、ones、empty
zero
函数可以创建一个全是0 的数组,ones
函数可以创建全是 1 的数组,empty
函数可以根据缓存创建一个随机内容的数组。
这三种方法数据类型默认为float64,但可以通过关键字参数dtype
进行修改。
np.zeros((3, 4))
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
np.ones((2, 3, 4), dtype=np.int16)
array([[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]],
[[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]]], dtype=int16)
np.empty((2, 3))
array([[3.73603959e-262, 6.02658058e-154, 6.55490914e-260], # may vary
[5.30498948e-313, 3.14673309e-307, 1.00000000e+000]])
- 创建数字序列
arange
函数可以创建一个类似与range
的序列,但返回数组。
np.arange(10, 30, 5) #头、尾、步长
array([10, 15, 20, 25])
np.arange(0, 2, 0.3) # it accepts float arguments
array([0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])
- linspace
当浮点数作为arange
的参数时,由于精度问题,通常很难预测每个元素的值,因此最好使用linspace
函数,这个函数可以接收元素个数作为一个参数。
from numpy import pi
np.linspace(0, 2, 9)
array([0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ])
x = np.linspace(0, 2 * pi, 100)
f = np.sin(x)
- 其他
array|zeros|zeros_like|ones|ones_like|empty|empty_like
等等等等
打印数组
NumPy 数组结构形似嵌套列表,但具有一些特征
- 最后的轴从左向右打印
- 倒数第二从上到下打印
- 其余从上到下打印,每个切片与下一个切片各一个空行
- 如果矩阵太大的话,会自动跳过总监部分,只显示四周,若要全部显示,我们可以设置
set_printoptions
c = np.arange(24).reshape(2, 3, 4) # 3d array
print(c)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
np.set_printoptions(threshold=sys.maxsize) # sys module should be imported
基本操作
- 算术操作应用于元素身上,结果返回一个新的答案数组。
a = np.array([20, 30, 40, 50])
b = np.arange(4)
b #array([0, 1, 2, 3])
c = a - b
c #array([20, 29, 38, 47])
b**2 #array([0, 1, 4, 9])
10 * np.sin(a) #array([ 9.12945251, -9.88031624, 7.4511316 , -2.62374854])
a < 35
array([ True, True, False, False])
*
求乘积操作是针对于每个元素,如果要减小矩阵操作,使用@
或者dot
函数。
A = np.array([[1, 1],
[0, 1]])
B = np.array([[2, 0],
[3, 4]])
A * B # elementwise product
array([[2, 0],
[0, 4]])
A @ B # matrix product
array([[5, 4],
[3, 4]])
A.dot(B) # another matrix product
array([[5, 4],
[3, 4]])
+=
和*=
操作是对元数组的操作,不会返回一个新的数组。rg = np.random.default_rng(1) # create instance of default random number generator a = np.ones((2, 3), dtype=int) b = rg.random((2, 3)) a *= 3 a array([[3, 3, 3], [3, 3, 3]]) b += a b array([[3.51182162, 3.9504637 , 3.14415961], [3.94864945, 3.31183145, 3.42332645]]) a += b # b is not automatically converted to integer type Traceback (most recent call last): ... numpy.core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'
当操作的数组们的数据类型不同,数组结果与更加综合精确的一致(upcasting)
a = np.ones(3, dtype=np.int32) b = np.linspace(0, pi, 3) b.dtype.name 'float64' c = a + b c array([1. , 2.57079633, 4.14159265]) c.dtype.name 'float64' d = np.exp(c * 1j) d array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j, -0.54030231-0.84147098j]) d.dtype.name 'complex128'
许多一元操作,如求所有元素的和,在
ndarray
类的方法中。a = rg.random((2, 3)) a array([[0.82770259, 0.40919914, 0.54959369], [0.02755911, 0.75351311, 0.53814331]]) a.sum() 3.1057109529998157 a.min() 0.027559113243068367 a.max() 0.8277025938204418
在默认情况下,对数组的操作可以不管其形状,看做列表即可。但在特殊情况下,可以指定
axis
参数,沿着这个轴进行操作。b = np.arange(12).reshape(3, 4) b array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> b.sum(axis=0) # sum of each column array([12, 15, 18, 21]) >>> b.min(axis=1) # min of each row array([0, 4, 8]) >>> b.cumsum(axis=1) # cumulative sum along each row array([[ 0, 1, 3, 6], [ 4, 9, 15, 22], [ 8, 17, 27, 38]])