universal function
, 或 ufunc
, 是用来在ndarray
中实现element-wise
操作的。
可以认为这个ufunc
可以把一些简单的函数做快速的向量化封装,输入时一个以上的标量,输出也是一个以上的标量。
很多ufuncs
都是点对点的变换,像sqrt
或exp
:
import numpy as np
arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.sqrt(arr)
array([ 0. , 1. , 1.41421356, 1.73205081, 2. ,
2.23606798, 2.44948974, 2.64575131, 2.82842712, 3. ])
np.exp(arr)
array([ 1.00000000e+00, 2.71828183e+00, 7.38905610e+00,
2.00855369e+01, 5.45981500e+01, 1.48413159e+02,
4.03428793e+02, 1.09663316e+03, 2.98095799e+03,
8.10308393e+03])
这些函数叫做一元通用函数(unary ufuncs
)。其他一些函数,比如add
或maximum
,需要两个数组(binary ufuncs
),并返回一个数组作为结果:
x = np.random.randn(8)
y = np.random.randn(8)
x
array([ 0.18373557, -1.82728347, -0.11149882, -1.34286776, -1.09016986,
1.63308 , 1.05205535, -0.32746706])
y
array([-0.42410809, 1.89603273, -1.13649816, -0.98559379, -0.16827718,
0.52828569, 1.57543351, 1.50045399])
np.maximum(x, y)
array([ 0.18373557, 1.89603273, -0.11149882, -0.98559379, -0.16827718,
1.63308 , 1.57543351, 1.50045399])
这里mamimum
点对点的比较x和y中的元素。
尽管不常见,但ufunc
也能返回多个数组。例如modf
,这是一个向量版的divmod
(python
内建函数),modf
会返回小数部分和整数部分:
本函数是实现a除以b,然后返回商与余数的元组。如果两个参数a,b都是整数,那么会采用整数除法,结果相当于(a//b, a % b)。如果a或b是浮点数,相当于(math.floor(a/b), a%b)。
arr = np.random.randn(7) * 5
arr
array([ 1.51538382, -0.75054846, 0.02863286, 8.74026861, -3.44529124,
-9.18401768, -0.68469611])
remainder, whole_part = np.modf(arr)
remainder
array([ 0.51538382, -0.75054846, 0.02863286, 0.74026861, -0.44529124,
-0.18401768, -0.68469611])
whole_part
array([ 1., -0., 0., 8., -3., -9., -0.])
ufunc
能接受一个可选参数作为输出,这样可以直接更改原有的数组:
arr
array([ 1.51538382, -0.75054846, 0.02863286, 8.74026861, -3.44529124,
-9.18401768, -0.68469611])
np.sqrt(arr) # 没有改变原有的arr
/Users/xu/anaconda/envs/py35/lib/python3.5/site-packages/ipykernel/__main__.py:1: RuntimeWarning: invalid value encountered in sqrt
if __name__ == '__main__':
array([ 1.23100927, nan, 0.16921248, 2.95639453, nan,
nan, nan])
np.sqrt(arr, arr) # 改变了原有的arr
/Users/xu/anaconda/envs/py35/lib/python3.5/site-packages/ipykernel/__main__.py:1: RuntimeWarning: invalid value encountered in sqrt
if __name__ == '__main__':
array([ 1.23100927, nan, 0.16921248, 2.95639453, nan,
nan, nan])
arr
array([ 1.23100927, nan, 0.16921248, 2.95639453, nan,
nan, nan])
Numpy
能从磁盘直接存储和加载数据,不论是文本格式还是二进制模式。这里我们只考虑Numpy
的二进制模式,因为大多数用户更喜欢用pandas
或其他工具来加载text
或tabular
数据。
np.save
和np.load
。数组会以未压缩的原始二进制模式被保存,后缀为.npy
:
import numpy as np
arr = np.arange(10)
np.save('../examples/some_array', arr)
即使保存的时候没有加后缀,也会被自动加上。可以用np.load
来加载数组:
np.load('../examples/some_array.npy')
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
用np.savez
能保存多个数组,还可以指定数组对应的关键字,不过是未压缩的npz
格式:
np.savez('../examples/array_archive.npz', a=arr, b=arr)
加载.npz
文件的时候,得到一个dict object
:
arch = np.load('../examples/array_archive.npz')
arch['b']
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
可以用np.savez_compressed
来压缩文件:
np.savez_compressed('../examples/array_compressed.npz', a=arr, b=arr)
在MATLAB
里,*
代表矩阵乘法。但是在numpy
里,*
表示element-wise prodct
。要想做到矩阵乘法,要用多函数dot
:
import numpy as np
x = np.array([[1., 2., 3.], [4., 5., 6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])
x
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
y
array([[ 6., 23.],
[ -1., 7.],
[ 8., 9.]])
x.dot(y)
array([[ 28., 64.],
[ 67., 181.]])
x.dot(y)
等同于np.dot(x, y)
:
np.dot(x, y)
array([[ 28., 64.],
[ 67., 181.]])
一个二维数据和一个一维数组的矩阵乘法,得到一个一维数组:
np.dot(x, np.ones(3))
# 这里应该是用狂了boradcasting,x中的每一行与[1, 1, 1]点对点乘积后求和
array([ 6., 15.])
@
作为一个中缀计算符(鬼知道这是什么东西),也能实现矩阵乘法:
x @ np.ones(3)
array([ 6., 15.])
np.linalg
能用来做矩阵分解,以及比如转置和求秩之类的事情:
from numpy.linalg import inv, qr
# X = np.round(np.random.randn(5, 5), 3) # 这里我们用np.round控制小数点后的位数,看起来更舒服一些
X = np.random.randn(5, 5)
X
array([[ 0.0761557 , -0.34138565, -0.56325926, 1.7854 , 1.23440008],
[ 1.46787829, 1.73130465, 1.03519282, 1.11137573, -0.05928319],
[-0.95508009, -1.35350494, -1.43415583, -0.28499706, 0.32739284],
[ 0.83307271, 1.89349058, 0.94116452, 0.32347353, 0.22236912],
[-1.20661273, 0.4531822 , 0.47635565, -1.69312137, -0.34497803]])
mat = X.T.dot(X)
np.round(mat, 2)
array([[ 5.22, 4.84, 3.06, 4.35, 0.3 ],
[ 4.84, 8.74, 5.92, 1.55, -0.7 ],
[ 3.06, 5.92, 4.56, 0.05, -1.18],
[ 4.35, 1.55, 0.05, 7.48, 2.7 ],
[ 0.3 , -0.7 , -1.18, 2.7 , 1.8 ]])
np.round(inv(mat), 2)
array([[ 12.14, -6.15, 3.85, -11.22, 14.95],
[ -6.15, 4.85, -4.47, 5.7 , -8.57],
[ 3.85, -4.47, 5.23, -3.78, 6.71],
[-11.22, 5.7 , -3.78, 10.77, -14.55],
[ 14.95, -8.57, 6.71, -14.55, 20.95]])
np.round(mat.dot(inv(mat)), 2)
array([[ 1., 0., 0., 0., -0.],
[-0., 1., 0., -0., -0.],
[ 0., -0., 1., -0., 0.],
[-0., 0., 0., 1., 0.],
[ 0., 0., -0., 0., 1.]])
q, r = qr(mat)
np.round(r, 2)
array([[ -8.89, -10.37, -6.57, -7.16, -0.77],
[ 0. , -5.5 , -4.79, 5.41, 3.04],
[ 0. , 0. , -0.71, 1.85, 1.54],
[ 0. , 0. , 0. , -0.75, -0.53],
[ 0. , 0. , 0. , 0. , 0.03]])
X.T.dot(X)
计算的是X和X的转置的矩阵乘法。
numpy.random
提供了很多生成随机数的函数,可以选择生成符合某种概率分布的随机数。比如我们可以用normal
得到一个4 x 4
的,符合标准正态分布的数组:
import numpy as np
samples = np.random.normal(size=(4, 4))
samples
array([[ 0.5382462 , -0.79452471, -0.07993797, 0.72243531],
[ 0.87180676, 1.61663011, -0.62169955, 1.73880636],
[ 1.88294218, 0.07432438, 1.63474848, 0.23519213],
[ 0.92847885, -0.45791646, 0.63965317, -0.23654448]])
相对的,python
内建的random
模块一次只能生成一个样本。在生成大量样本方法,numpy.random
是非常快的:
from random import normalvariate
N = 1000000
%timeit sample = [normalvariate(0, 1) for _ in range(N)]
1 loop, best of 3: 1.25 s per loop
%timeit np.random.normal(size=N)
10 loops, best of 3: 49.1 ms per loop
之所以称之为伪随机数,是因为随机数生成算法是根据seed
来生成的。也就是说,只要seed
设置一样,每次生成的随机数是相同的:
np.random.seed(1234)
当然,这个seed
是全局的,如果想要避免全局状态,可以用numpy.random.RandomState
来创建一个独立的生成器:
rng = np.random.RandomState(1234)
rng.randn(10)
array([ 0.47143516, -1.19097569, 1.43270697, -0.3126519 , -0.72058873,
0.88716294, 0.85958841, -0.6365235 , 0.01569637, -2.24268495])