NumPy(Numerical Python的简称)是高性能科学计算和数据分析的基础包。它的部分功能如下:
- ndarray,一个具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组
- 用于对整组数据进行快速运算的标准数学函数(无需编写循环)
- 用于读写磁盘数据的工具以及用于操作内存映射文件的工具
- 线性代数、随机数生成以及傅里叶变换功能
- 用于集成由C、C++、FORTRAN等语言代码的工具
NumPy本身并没有提供很高级的数据分析功能,但是理解NumPy数组以及面向对象数组的计算有助于更加高效地使用诸如pandas之类的工具。
首先,我们需要引入NumPy模块,按照标准的NumPy约定,我们应该总是使用import numpy as np
这种方式引入NumPy模块,而不是这种方式。from numpy import *
import numpy as np
NumPy的ndarray:一种多维数组对象
NumPy最重要的一个特点就是其N维数组对象(ndarray),该对象是一个快速而灵活的大数据集容器。每个ndarray都有一个shape(一个表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象)。ndarray对象中的所有元素都必须是相同类型的。
创建ndarray
创建数组最简单的方式就是使用array()函数。该函数接受一切序列型的对象(包括其他数组),然后产生一个新的NumPy数组。
data1 = [6, 7.5, 8, 0, 1]
arr1 = np.array(data1)
print(arr1)
[ 6. 7.5 8. 0. 1. ]
嵌套序列会被转化成一个多维数组:
data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
arr2 = np.array(data2)
print(arr2)
print(arr2.ndim)
print(arr2.shape)
[[1 2 3 4]
[5 6 7 8]]
2
(2, 4)
除非显式地说明,np.array会尝试为新建的数组推断出一个较为合适的数据类型:
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.ones((2,3,4))
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.]]])
np.empty((2,3))
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
注意:认为np.empty会返回全0数组的想法是不安全的。很多情况下它返回的都是一些未初始化的垃圾值(尽管这里创建了一个全0的多维数组)。
下表给出了一些数组常见函数。由于NumPy注重的是科学计算,因此如果没有特别说明,数据类型基本上都是float64。
函数 | 说明 |
---|---|
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 | 创建一个N × N的单位矩阵 |
数组和标量之间的运算
大小相等的数组之间的任何算数运算都会将运算应用到元素级:
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 + 2
array([[ 3., 4., 5.],
[ 6., 7., 8.]])
基本的索引和切片
一维数组的索引和切片很简单,大体上和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] = 11
arr
array([ 0, 1, 2, 3, 4, 11, 11, 11, 8, 9])
当将一个标量值赋值给一个切片时,该值会自动传播到整个选区。数组切片是原数组的视图,在视图上进行任何修改都会反应到原数组上:
arr_slice = arr[5:8]
arr_slice[1] = 123
arr
array([ 0, 1, 2, 3, 4, 11, 123, 11, 8, 9])
arr_slice[:] = 678
arr
array([ 0, 1, 2, 3, 4, 678, 678, 678, 8, 9])
注意:如果想要得到的是ndarray切片的一份副本而非视图,就需要显式地进行复制操作,如`arr[5:8].copy()`。
对于二维数组,各索引位置上的元素不再是标量,而是一个一维数组:
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
在N维数组中,如果省略了后面的索引,则返回对象会是一个N-1维的ndarray,它含有N维数组在某条轴上的全部数据。例如,在2×2×3的数组arr3d中:
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]是一个2×3的数组:
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]]])
切片索引
通过将整数索引和切片混合,可以得到低维度的切片:
arr2d
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
arr2d[1,:2]
array([4, 5])
arr2d[2,:1]
array([7])
“只有冒号”表示选取整个轴:
arr2d[:,2]
array([3, 6, 9])
此外,对切片表达式的赋值操作也会传播到整个选区:
arr2d[:2,1:] = 0
arr2d
array([[1, 0, 0],
[4, 0, 0],
[7, 8, 9]])
布尔型索引
假设我们有一个用于存储数据的数组以及一个存储姓名的数组:
names = np.array(['Bob'