Numpy包的学习

Numpy 是 Numerical python 的简称,是高性能计算和数据分析的基础包。其实它 本身并没有提供多么高级的数据结构和分析功能,但它是很多高级工具(如 pandas)构建的基础,在结构和操作上具有统一性,因此理解 Numpy 的数组及面向数组的计算有助于更加高效地使用诸如 pandas 之类的工具。

Numpy 的核心内容是它的多维数组对象——ndarray(N-Dimensions Array),整个包几乎都是围绕这个对象展开。

ndarray:多维数组对象

Numpy 的 ndarray 提供了一种将同质数据块解释为多维数组对象的方式。同质,表示数组的元素必须都是相同的数据类型(如 int,float 等);解释,表示 ndarray 的数据块其实是线性存储的,并通过额外的元信息解释为多维数组结构。

import pandas as pd
array1 = np.array([[ 0.9526, -0.246 , -0.8856], 
				   [ 0.5639, 0.2379, 0.9104]])
array1
	## array([[ 0.9526, -0.246 , -0.8856],
	## 		[ 0.5639,  0.2379,  0.9104]])
array1 * 10
	## array([[ 9.526, -2.46 , -8.856],
	##        [ 5.639,  2.379,  9.104]])
array1 + array1
	## array([[ 1.9052, -0.492 , -1.7712],
	##        [ 1.1278,  0.4758,  1.8208]])

上面是一个 2×3 的矩阵。在使用类似 2×3×4…这种格式表示多维数组的结构时,从左向右的数字对应表示由表及里的维度,或称为轴,按索引给轴编号后可称为“轴 0”、“轴 1”和“轴2”等)。

array1.shape
	## (2, 3)
array1.dtype
	## dtype('float64')
array1.strides
	## (24, 8)

这个矩阵的形状(shape)是(2,3)或 2×3,即它有 2 个长度为 3 的一维数组;它的 dtype 是 float64 表示它的单位元素是占 8 字节的浮点型;跨度(strides)元组指的是在某一维度下为了获取到下一个元素需要“跨过”的字节数。可见跨度是可以由 形状+dtype 来确定的。显然这种同质的静态数据结构在进行数值运算时效率要比 Python 内建的可以混杂动态类型的列表要快得多。

ndarray对象的创建

创建数组的方法是使用array( )函数。它接受一切序列对象,产生一个包含所传递的数据的新ndarry数组,维度视序列的嵌套深度而定。首先,列表就是一个很好的用于转换的候选。

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)

数组的 dtype 会由系统自动推定,除非你显式传递一个参数进去。系统一般会默认使用 int32 或 float64。 除 array() 之外,还有许多函数来创建新的数组。

数组构建函数

函数 描述
array 转换输入数据(列表,数组或其它序列类型)为ndarray,可以推断一个dtype或明确的设置一个dtype,默认拷贝输入数据
asarray 将输入转换为 ndarray,若输入本身是 ndarray 就不拷贝
arange 同内建的range函数,但不返回的不是列表而是一个一维的ndarray
ones, ones_like 根据提供的shape和dtype产生一个全1的数组;ones_like使用另一个数组为参数,产生一个shape和dtype都相同的数组
zeros, zeros_like 同ones和ones_like,但是生成一个全0的数组
empty, enpty_like 创建新数组,但只分配内存空间不赋值
eye, identity 创建一个NxN的单位方阵(对角线上为1,其余为0)

例如,zeros 和 ones 使用给定的长度或形状分别的创建0‘s 和 1‘s数组。

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.empty((2, 3, 2))
	## array([[[ 4.94065646e-324, 4.94065646e-324],
	##         [ 3.87491056e-297, 2.46845796e-130],
	##         [ 4.94065646e-324, 4.94065646e-324]],
	##        [[ 1.90723115e+083, 5.73293533e-053],
	##         [ -2.33568637e+124, -6.70608105e-012],
	##         [ 4.42786966e+160, 1.27100354e+025]]])

在 pandas 中尽量不要使用 np.empty(),这个函数创建的数组里面是有值的,除非你确定创建的这个数组能被完全赋值,否则后面运算起来很麻烦,这些“空值”的布尔类型是 True,而且 dropna() 方法删不掉。想创建空的 Series ,可以使用 Series(np.nan,index=???)。

arange 是python内建 range 函数的数组版本。

np.arange(15)
	## array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])

ndarray 对象的属性

.reshape(shape)

此方法用于改变数组的形状。虽然我觉得既然 ndarray 对象的数据块都是线性存储的,按说调用 .reshape() 方法的话只需要改一下数据头中的 shape 属性就可以了啊,但实际上不是这样子的!a.reshape(shape, order=’C’) 方法会返回一个新数组,而不是直接改变调用者的形状

foo = np.arange(9)
bar = foo.reshape((3,3))
bar
	## array([[0, 1, 2],
	##        [3, 4, 5],
	##        [6, 7, 8]])
foo
	## array([0, 1, 2, 3, 4, 5, 6, 7, 8])

.astype(dtype)

这是一个用于转换数组 dtype 的方法,从前面的 ndarray 数据结构可以猜到,这种转换必然需要创建一个新数组。如果转换过程因为某种原因而失败了,就会引发一个 TypeError 异常。另外,如 np.int32() 这样把 dtype 当做函数来用也是可行的,但更推荐 .astype() 方法。

bar.astype(float)
	## array([[ 0.,  1.,  2.],
	##        [ 3.,  4.,  5.],
	##        [ 6.,  7.,  8.]])

本例中使用 Python 内建的 float 当做 dtype 传了进去,也是可行的哦,当对数据大小不敏感时就可以这么做。

.transpose(*axes)

转置方法返回的是原数组的视图(不复制)。

foo = np.arange(8).reshape(2,4)
foo
	## array([[0, 1, 2, 3],
	##        [4, 5, 6, 7]])
foo.transpose()
	## array([[0, 4],
	##        [1, 5],
	##        [2, 6],
	##        [3, 7]])
foo.T
	## array([[0, 4],
	##        [1, 5],
	##        [2, 6],
	##        [3, 7]])

因为对多维数组比较麻烦,这里就只先举个例子。

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]]])

数组的 .T 属性是轴对换的快捷方式。一般在计算矩阵点积时比较方便:np.dot(arr,att.T)。嗯,简单的乘法(星号)是广播运算,点积需要使用 dot() 函数。

.sort()

ndarray 的 .sort(axis=-1, kind=’quicksort’, order=None) 方法可用于给数组在指定轴向上排序。比如一个 (4,3,2)的数组,它的对应轴向分别为(2,1,0),方法默认的 axis=-1 代表最外层维度,如 “表” 里的 “行”。

a = np.array([[1,4], [3,1]])
a
	## array([[1, 4],
	##        [3, 1]])
np.sort(a,0)
	## array([[1, 1],
	##        [3, 4]])
np.sort(a,1)
	## array([[1, 4],
	##        [1, 3]])

这里使用了外部函数 np.sort() 是为了在演示过程中不会影响到原数组。np.sort() 函数总是返回一份拷贝,而 .sort() 方法则会更改原数组。

统计方法

ndarray 对象还有一些统计方法,可以对整个数组或某个轴向上的数据进行统计计算(轴向数字越大代表的维度越高,从 0 开始计数)。这些方法同时也可以当做顶级函数使用。例如:

arr = np.arange(12).reshape(3,4)
arr
	## array([[ 0,  1,  2,  3],
	##        [ 4,  5,  6,  7],
	##        [ 8,  9, 10, 11]])
arr.sum()
	## 66
np.sum(arr)
	## 66
arr.mean(0)
	## array([ 4.,  5.,  6.,  7.])
arr.mean(1)
	## array([ 1.5,  5.5,  9.5])
arr.mean(2)
	## Traceback (most recent call last):
	##   File "<pyshell#35>", line 1, in <module>
	##     arr.mean(2)
	##     items *= arr.shape[ax]
	## IndexError: tuple index out of range

基本的数组统计方法

统计方法 描述
sum 求和
mean 均值
std,var 标准差和方差
min,max 最小值和最大值
argmin,argmax 最小值和最大值的索引
cumsum 累积和
cumprod 累积积

索引和切片

ndarray 的索引和切片语法与 Python 的列表相似。都是通过如 [0],[0:5:2] 这样的方括号和 冒号来完成。比较不同之处在于为了方便对多维数组切片,ndarray 对象还支持使用逗号间隔的多维切片方法:[0,3],[0,3:9:2]。

  • 普通索引
foo = np.arange(12)
foo
	## array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
foo[:5]
	## array([0, 1, 2, 3, 4])
foo[:5]=0
foo
	## array([ 0,  0,  0,  0,  0,  5,  6,  7,  8,  9, 10, 11])
bar = foo[:5]
bar[0] = 1024
bar
	## array([1024,    1,    2,    3,    4])
foo
	## array([1024,  0,  0,  0,  0,  5,  6,  7,  8,  9, 10, 11])

注意这里,为了节省内存,对 ndarray 的切片操作获得的都是对原数组的引用,因此对该引用的更改操作都会反映到原数组上,这也是与Python 的列表的一个重要的区别。如果你是使用Numpy的新手,这一点会使你感到惊讶『真的惊讶到我了』,尤其当你使用过其它数组编程语言,它们非常热衷于拷贝数据。请记住,Numpy是设计用来处理大数据的情况,你可以想象如果Numpy坚持使用拷贝数据将会出现的性能和内存问题。如果你想复制出一段副本,就应当使用 .copy() 方法。

bar = foo[:5].copy()
bar[:] = 1
foo
	## array([1024,  0,  0,  0,  0,  5,  6,  7,  8,  9, 10, 11])

也许你会对这里的 foo[:] 感兴趣,这代表切全部的片,不可以使用 foo = 1 这样的赋值语句,这等于给 foo 重新指向一个新的内存地址,而非对切片元素进行操作。

前面提到的使用逗号在多维度下的切片方法。

foo = np.arange(12).reshape(3,4)
foo
	## array([[ 0,  1,  2,  3],
	##        [ 4,  5,  6,  7],
	##        [ 8,  9, 10, 11]])
foo[0,1]
	## 1
foo[0,::2]
	## array([0, 2])

这种切片方法可以看做是一种语法糖『表示不懂』,因为最标准的对多维数组的切片方法应该是下面这样子的,包括 Python 原本对嵌套列表的切片方法也是这样子的:

foo
	## array([[ 0,  1,  2,  3],
	##        [ 4,  5,  6,  7],
	##        [ 8,  9, 10, 11]])
foo[0][1]
	## 1
foo[0][::2]
	## array([0, 2])

即 foo[0,1] 与 foo[0][1] 效果相同,这种实现可以节省时间,但不如原始方法更直观一点。只要记住对多维数组的单层切片总是切的最外层维度这点,操作起来就不容易乱。 例如,在多维数组中,如果你省略了后面的索引,返回的对象将会是一个较低维的ndarray,它包括较高维度的所有数据。因此,在 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 的数组。

  • 布尔型索引

布尔型索引指的是使用一个布尔型数组而非 [::] 作为切片手段,操作会将被切片对象中对应于布尔型数组中 True 元素位置的元素返回,并总是返回一个新的副本。

foo = np.arange(12).reshape(3,4)
bar = foo.copy()
bar%2==0
	## array([[ True, False,  True, False],
	##        [ True, False,  True, False],
	##        [ True, False,  True, False]], dtype=bool)
foo[bar%2==0]
	## array([ 0,  2,  4,  6,  8, 10])

本例中一个值得注意之处在于 bar%2==0 这个表达式,在 Python 的标准语法中对一个列表和一个整型应用取余操作是非法的,你必须使用循环(如 for)遍历列表的单个元素才行。但 numpy 很贴心地通过广播解决了这个问题,吊不吊! 此外,在布尔索引中,你还可以使用 != 或 - 对条件表达式取反,使用 & (and) 和 | (or)来结合多个布尔条件。

  • 花式索引

花式索引(fancy indexing)是一个 Numpy 术语,它指的是利用整数数组进行索引,这里的整数数组起到了index的作用。 如同布尔索引,花式索引也总是拷贝数据到一个新的数组。

foo = np.empty((8,4),int)
for i in range(8):
	foo[i] = i
foo
	## 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]])
foo[[7,2,5]]
	## array([[7, 7, 7, 7],
	##        [2, 2, 2, 2],
	##        [5, 5, 5, 5]])
foo[[7,2,5],[0,2,2]]
	## array([7, 2, 5])

上例中 foo[[7,2,5],[0,2,2]] 处两个列表索引之间的逗号,所起的作用与上面普通索引处的作用相同,均为在更低一级维度上索引之用。

在花式索引中,使用负数表示从结尾选择行。

通用函数

通用函数(即 ufunc)是一种对 ndarray 执行元素级运算的函数。通用函数依据参数的数量不同,可分为一元(unary)函数和二元(binary)函数。(参数一般都是 ndarray 对象)。

一元函数

函数 描述
abs,fabs 整数、浮点、复数的绝对值,对于非复数,可用更快的 fabs
sqrt 平方根,等于 arr**0.5
square 平方,等于 arr**2
exp 以 e 为底的指数函数
log,log10,log2,log1p 以 e 为底的对数函数
sign 计算各元素的正负号,1(正),0(零),-1(负)
ceil 计算大于等于该值的最小整数
floor 计算小于等于该值的最大整数
rint round int,四舍五入到整数
modf 将数组的整数和小数部分以两个独立数组的形式返回
isnan 返回一个 “哪些值是 NaN” 的布尔型数组
isfinite,isinf 返回是否是有穷(无穷)的布尔型数组
cos,cosh,sin,sinh,tan,tanh 普通和双曲型三角函数
arccos,arccosh…等同上 反三角函数
logical_not 计算各元素 not x 的真值,等于 -arr
unique 计算元素唯一值并返回排序后的结果

二元函数

函数 描述
add 加法,+
subtract 减法,-
multiply 乘法,*
divide,floor_divide 除法和地板除,/ 和 //
power 乘方,**
maximum,fmax 元素级最大值,fmax 将忽略 NaN
minimum,fmin 同上
mod 取模,%
copysign 将第二数组元素的符号复制给第一数组
greater(equal),less(_equal),(not)equal 字面意义,返回布尔数组
logical_and,logical_or,logical_xor 字面意义,返回布尔数组

三元函数

这里的三元函数只有一个,而且不是接受 3 个数组参数的意思。它其实是一个条件运算函数,即 foo if cond else bar 这个表达式的 numpy 版——where(condition, [x, y])

arr = np.arange(12).reshape(3,4)
arr
	## array([[ 0,  1,  2,  3],
	##        [ 4,  5,  6,  7],
	##        [ 8,  9, 10, 11]])
np.where(arr%2==0,1,0)
	## array([[1, 0, 1, 0],
	##        [1, 0, 1, 0],
	##        [1, 0, 1, 0]])

- - - - -

参考资料

  1. Python 数据分析基础包:Numpy
  2. NumPy Basics: Arrays and Vectorized Computation
Previous     Next Xu Kuang /
Published under (CC) BY-NC-SA in categories 技术篇  tagged with python  numpy