欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Numpy使用02

程序员文章站 2024-01-31 08:22:22
...

numpy数组的运算

不用编写循环就可以实现对数据批量运算,Numpy用户称为:矢量化。大小相同的数组之间的任何算数运算都可以将运算应用到元素级:

import numpy as np
arr = np.array([[1,2,3],[3,4,5]])
arr
array([[1, 2, 3],
       [3, 4, 5]])
# 执行算术运算
arr * arr
array([[ 1,  4,  9],
       [ 9, 16, 25]])
arr ** arr
array([[   1,    4,   27],
       [  27,  256, 3125]], dtype=int32)
# 大小相同的数组之间的比较会产生布尔值数组
arr1 = np.array([[2,3,4],[3,2,1]])
arr1 > arr
array([[ True,  True,  True],
       [False, False, False]])

不同大小的数组之间的运算叫做广播

索引和切片

跟列表最重要的区别在于,数组切⽚是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上。

当你将⼀个标量值赋值给⼀个切⽚时(如arr[5:8]=12),该值会⾃动传播(也就说后⾯将会讲到的“⼴播”)到整个选区。

arr = np.arange(10)
arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr_slice = arr[5:8]
arr_slice
array([5, 6, 7])
arr_slice[:] = 64
arr
array([ 0,  1,  2,  3,  4, 64, 64, 64,  8,  9])
arr_slice
array([64, 64, 64])

由于NumPy
的设计⽬的是处理⼤数据,所以你可以想象⼀下,假如NumPy
坚持要将数据复制来复制去的话会产⽣何等的性能和内存问题。

对于⾼维度数组,能做的事情更多。在⼀个⼆维数组中,各索引位置上的元素不再是标量⽽是⼀维数组:

arr2 = np.array([[[1,2,3],
                  [2,3,4]],
                 [[3,4,5],
                  [4,5,6]]])
arr2.shape
(2, 2, 3)
# 可以对各个元素进行递归访问,也可以使用索引列表来进行访问
arr2[0][0][0]
1
arr2[0,0,0]
1
arr2[0]
array([[1, 2, 3],
       [2, 3, 4]])
data = np.random.randn(5, 5)
data.mean()
0.3587944495686687
data.std()
0.8490873700187817

布尔型索引

names = np.array(['xiao','zeng','zhang','liu','lili'])
names
array(['xiao', 'zeng', 'zhang', 'liu', 'lili'], dtype='<U5')
names == 'xiao'
array([ True, False, False, False, False])
# 这个产生的布尔数组可以用于数组索引
data[names == 'xiao']
array([[ 0.70902   ,  0.12835622, -0.10731375,  0.17201327, -1.12689436]])

布尔型数组的⻓度必须跟被索引的轴⻓度⼀致。此外,还可以将布尔型数组跟切⽚、整数混合使⽤:

# 选取names == 'xiao'的行,同时索引了列
data[names == 'xiao', 2:]
array([[-0.10731375,  0.17201327, -1.12689436]])

要选择除"Bob"以外的其他值,既可以使⽤不等于符号(!=),也可以通过~对条件进⾏否定:

names != 'xiao'
array([False,  True,  True,  True,  True])
data[~(names != 'xiao')]
array([[ 0.70902   ,  0.12835622, -0.10731375,  0.17201327, -1.12689436]])

通过布尔型索引选取数组中的数据,将总是创建数据的副本,即使返回⼀模⼀样的数组也是如此。

通过布尔型数组设置值是⼀种经常⽤到的⼿段。为了将data中的所有负值都设置为0,我们只需:

data[data < 0] = 0
data
array([[0.70902   , 0.12835622, 0.        , 0.17201327, 0.        ],
       [0.06219114, 1.23528266, 0.        , 0.5344566 , 1.05603675],
       [1.05532396, 1.99560409, 1.44444832, 0.44811772, 0.        ],
       [1.6811567 , 0.        , 0.4331555 , 0.        , 0.73461594],
       [0.        , 0.88400999, 0.10480376, 0.65949918, 0.44154703]])

花式索引

花式索引(Fancy indexing)是⼀个NumPy术语,它指的是利⽤整数数组进⾏索引。

arr = np.empty((8,4))
arr
array([[6.95248686e-310, 6.95248686e-310, 1.03320693e-311,
        6.95248686e-310],
       [1.03320693e-311, 1.03320693e-311, 6.95248686e-310,
        1.03320693e-311],
       [6.95248686e-310, 6.95248686e-310, 1.03320693e-311,
        1.03320693e-311],
       [1.03320693e-311, 6.95248686e-310, 1.03320693e-311,
        6.95248686e-310],
       [1.03320693e-311, 6.95248686e-310, 1.03320693e-311,
        1.03320693e-311],
       [6.95248686e-310, 6.95248686e-310, 6.95248686e-310,
        1.03320693e-311],
       [6.95248686e-310, 1.03320693e-311, 6.95248686e-310,
        1.03320693e-311],
       [6.95248686e-310, 1.03320693e-311, 1.03320693e-311,
        1.03320693e-311]])
for i in range(8):
    arr[i] = i

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

以特定顺序选取⾏⼦集,只需传⼊⼀个⽤于指定顺序的整数列表或ndarray即可:

arr[[4, 3, 0, 6]]
array([[4., 4., 4., 4.],
       [3., 3., 3., 3.],
       [0., 0., 0., 0.],
       [6., 6., 6., 6.]])
arr[[-3, -5, -7]]
array([[5., 5., 5., 5.],
       [3., 3., 3., 3.],
       [1., 1., 1., 1.]])

⼀次传⼊多个索引数组会有⼀点特别。它返回的是⼀个⼀维数组,其中的元素对应各个索引元组

arr = np.arange(32).reshape(8,4)
arr
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15],
       [16, 17, 18, 19],
       [20, 21, 22, 23],
       [24, 25, 26, 27],
       [28, 29, 30, 31]])
arr[[1, 5, 7, 2], [0, 3, 1, 2]]
array([ 4, 23, 29, 10])

最终选出来的元素是:(1, 0), (5, 3), (7, 1), (2, 2).

不论数组是多少维的, 花式索引总是一维的。

# 可能上述结果与自己想象中的不一样,但实际上,可以通过如下方法,得到你要的
arr[[1, 5, 7, 2]][:,[0, 3, 1, 2]]
array([[ 4,  7,  5,  6],
       [20, 23, 21, 22],
       [28, 31, 29, 30],
       [ 8, 11,  9, 10]])

数组的转置操作

转置操作是重塑的一种特殊形式, 他返回的是源数据的视图(不会进行任何的复制操作)

数组不仅仅有transpose方法, 还有一个.T属性

arr = np.arange(15).reshape(3, 5)

arr
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
# 转置操作
arr.T
array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])
# 再进行矩阵计算的时候, 会经常使用到这个操作。

# 计算内积

np.dot(arr.T, arr)
array([[125, 140, 155, 170, 185],
       [140, 158, 176, 194, 212],
       [155, 176, 197, 218, 239],
       [170, 194, 218, 242, 266],
       [185, 212, 239, 266, 293]])

高维数组转置

对于⾼维数组,transpose需要得到⼀个由轴编号组成的元组才能对这些轴进⾏转置(⽐较费脑⼦):

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,它其实就是进⾏轴对换⽽已。

# ndarray还有一个swapaxes方法,他需要一对轴编号
arr
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],

       [[ 8,  9, 10, 11],
        [12, 13, 14, 15]]])
# swapaxes也是返回源数据的视图(不会进⾏任何复制操作)
arr.swapaxes(1, 2)
array([[[ 0,  4],
        [ 1,  5],
        [ 2,  6],
        [ 3,  7]],

       [[ 8, 12],
        [ 9, 13],
        [10, 14],
        [11, 15]]])

通用函数

通⽤函数(即ufunc)是⼀种对ndarray中的数据执⾏元素级运算的函数。

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])
x = np.random.randn(8)
y = np.random.randn(8)

np.maximum(x, y)
array([ 1.03157835, -0.29230895,  2.77475606,  1.55604276, -0.91644659,
        2.08410919,  0.81804035,  1.11546013])

numpy.maximum计算了x和y中元素级别最大的元素

np.square(2)
4

NumPy数组可以将许多种数据处理任务表述为简洁的数组表达式(否则需要编写循环)。⽤数组表达式代替循环的做法,通常被称为⽮量化。⼀般来说,⽮量化数组运算要⽐等价的纯Python⽅式快上⼀两个数量级(甚⾄更多),尤其是各种数值计
算。

points = np.arange(-5, 5, 0.01) # 1000 equally spaced points
xs, ys = np.meshgrid(points, points)
ys
array([[-5.  , -5.  , -5.  , ..., -5.  , -5.  , -5.  ],
       [-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99],
       [-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98],
       ...,
       [ 4.97,  4.97,  4.97, ...,  4.97,  4.97,  4.97],
       [ 4.98,  4.98,  4.98, ...,  4.98,  4.98,  4.98],
       [ 4.99,  4.99,  4.99, ...,  4.99,  4.99,  4.99]])
z = np.sqrt(xs ** 2 + ys ** 2)
z
array([[7.07106781, 7.06400028, 7.05693985, ..., 7.04988652, 7.05693985,
        7.06400028],
       [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
        7.05692568],
       [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
        7.04985815],
       ...,
       [7.04988652, 7.04279774, 7.03571603, ..., 7.0286414 , 7.03571603,
        7.04279774],
       [7.05693985, 7.04985815, 7.04278354, ..., 7.03571603, 7.04278354,
        7.04985815],
       [7.06400028, 7.05692568, 7.04985815, ..., 7.04279774, 7.04985815,
        7.05692568]])
import matplotlib.pyplot as plt
plt.imshow(z, cmap=plt.cm.gray); plt.colorbar()
plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
Text(0.5, 1.0, 'Image plot of $\\sqrt{x^2 + y^2}$ for a grid of values')

Numpy使用02

用于布尔数组的方法

arr = np.random.randn(100)

(arr > 0).sum() # 统计正数值的个数
47

any()和all()方法

any()用于检测数组中是否存在一个或者多个True, all()用于检测数组中所有的值是否都是True

bools = np.array([False, False, True, True, False])

bools.any()
True
bools.all()
False

排序

Numpy数组可以通过sort方法进行排序

print(np.random.randn(10).sort())
None
arr = np.random.randn(10)
arr.sort()
arr
array([-1.9227681 , -0.87893899, -0.69759408, -0.39209399,  0.75957655,
        0.97596031,  1.00763634,  1.05315874,  1.09735182,  1.46630886])

多维度数组可以再任何一个轴上进行排序,将轴编号传递给sort

arr = np.random.randn(5, 3)
arr
array([[-0.05231919, -0.64989375,  0.72685241],
       [-0.4338584 , -0.73782085, -0.30604027],
       [ 0.45229578,  0.12069083,  0.65669421],
       [-0.57799688, -0.93550434, -1.99844601],
       [ 1.59897264,  0.5119599 , -1.93736217]])
arr.sort(1)
arr
array([[-0.64989375, -0.05231919,  0.72685241],
       [-0.73782085, -0.4338584 , -0.30604027],
       [ 0.12069083,  0.45229578,  0.65669421],
       [-1.99844601, -0.93550434, -0.57799688],
       [-1.93736217,  0.5119599 ,  1.59897264]])

*⽅法np.sort返回的是数组的已排序副本,⽽就地排序则会修改数组本身。计算数组分位数最简单的办法是对其进⾏排序,然后选取特定位置的值:

唯一化

针对一维数组的基本集合运算,最长用的就是:np.unique,他用于找出数组中的唯一值,同时返回已经排序的结果

arr = np.array((1, 1, 3, 4, 5, 5, 5, 6, 6, 7))
np.unique(arr)
array([1, 3, 4, 5, 6, 7])
sorted(set(arr))
[1, 3, 4, 5, 6, 7]

函数np.in1d⽤于测试⼀个数组中的值在另⼀个数组中的成员资格,返回⼀个布尔型数组:

values = np.array([6, 0, 0, 3, 2, 5, 6])
np.in1d(values, [2, 3, 6])
array([ True, False, False,  True,  True, False,  True])

用于数组的文件输入输出

NumPy能够读写磁盘上的⽂本数据或⼆进制数据。np.save和np.load是读写磁盘数组数据的两个主要函数。默认情
况下,数组是以未压缩的原始⼆进制格式保存在扩展名为.npy的⽂件中的:

arr = np.arange(10)

np.save('some_array', arr)
np.load('some_array.npy')
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

通过np.savez可以将多个数组保存到⼀个未压缩⽂件中,将数组以关键字参数的形式传⼊即可:

np.savez('array_archive.npz', a = arr, b = arr)

加载.npz⽂件时,你会得到⼀个类似字典的对象,该对象会对各
个数组进⾏延迟加载:

arch = np.load('array_archive.npz')
# 使用numpy.savez_compressed.npz()
np.savez_compressed('array_compressed.npz', arr)

线性代数

x.dot(y)相当于 np.dot(x, y)

numpy.linalg中有⼀组标准的矩阵分解运算以及诸如求逆和⾏列
式之类的东⻄。它们跟MATLAB和R等语⾔所使⽤的是相同的⾏
业标准线性代数库,如BLAS、LAPACK、Intel MKL(Math
Kernel Library,可能有,取决于你的NumPy版本)等:

from numpy.linalg import inv, qr
x = np.random.randn(5, 5)
mat = x.T.dot(x)
inv(mat)
array([[ 3.78993038, -2.31907465, -1.89085455, -0.23658529, -0.79725393],
       [-2.31907465,  2.38770406,  0.8332225 ,  0.4303317 ,  0.23327152],
       [-1.89085455,  0.8332225 ,  1.42981316,  0.15209649,  0.87887263],
       [-0.23658529,  0.4303317 ,  0.15209649,  0.40301234,  0.16143467],
       [-0.79725393,  0.23327152,  0.87887263,  0.16143467,  0.83002176]])
mat.dot(inv(mat))
array([[ 1.00000000e+00,  1.65398246e-17, -8.02190954e-16,
        -1.16581206e-16, -2.47483443e-16],
       [ 4.25597955e-16,  1.00000000e+00, -2.73011620e-16,
        -3.88963944e-17,  1.44400437e-16],
       [ 4.35129850e-16, -4.92848896e-16,  1.00000000e+00,
         1.01451209e-16,  3.11993913e-16],
       [-3.68142027e-16,  2.95272796e-16,  2.50284243e-16,
         1.00000000e+00, -2.98913253e-17],
       [-8.74528484e-16, -4.73167621e-16, -2.22459730e-16,
        -6.75494951e-17,  1.00000000e+00]])
# qr分解操作
q, r = qr(mat)
r
array([[ -5.69298997,  -2.92024922, -10.50739866,   1.57730475,
          6.53703433],
       [  0.        ,  -1.82064519,   3.9333861 ,   3.85676364,
         -4.96368589],
       [  0.        ,   0.        ,  -1.21360022,   0.73417752,
          1.85954433],
       [  0.        ,   0.        ,   0.        ,  -1.90606856,
          0.54424417],
       [  0.        ,   0.        ,   0.        ,   0.        ,
          0.67768289]])

伪随机数生成

numpy.random模块对Python内置的random进⾏了补充,增加了
⼀些⽤于⾼效⽣成多种概率分布的样本值的函数。例如,你可以
⽤normal来得到⼀个标准正态分布的4×4样本数组:

samples = np.random.normal(size=(4,4))
samples
array([[-0.81886679, -0.61556353,  1.3763956 , -1.28563408],
       [-0.68575698, -1.34565189,  1.66916661, -0.36784602],
       [-0.19165034,  0.83448014, -1.52353481,  0.63099716],
       [ 0.65169991, -1.10422473,  2.0830342 ,  0.48158532]])

python的内置模块一次产生一个样本值, 下面进行效率比较

from random import normalvariate
N = 1000000
%timeit samples = [normalvariate(0, 1) for _ in range(N)]
%timeit np.random.normal(size=N)
687 ms ± 1.74 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
24.4 ms ± 141 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

这些都是伪随机数,是因为它们都是通过算法基于随机数
⽣成器种⼦,在确定性的条件下⽣成的。可以⽤NumPy的
np.random.seed更改随机数⽣成种⼦:

np.random.seed(1234)

numpy.random的数据生成使用了全局的随机种子。要避免全局状态,可以使用random.RandomState,创建一个与他隔离的随机数生成器。

rng = np.random.RandomState(1234)
rng.randn(10)
array([-0.20264632, -0.65596934,  0.19342138,  0.55343891,  1.31815155,
       -0.46930528,  0.67555409, -1.81702723, -0.18310854,  1.05896919])

随机漫步

模拟随机漫步来说明如何运⽤数组运算。先来看⼀个简
单的随机漫步的例⼦:从0开始,步⻓1和-1出现的概率相等。

# 通过内置的random模块以纯python的方式实现1000步的随机漫步
import random
position = 0
walk = [position]
steps = 1000
for i in range(steps):
    step = 1 if random.randint(0, 1) else -1
    position += step
    walk.append(position)
# 随机漫步值生成的折线图
plt.plot(walk[:])

Numpy使用02

相关标签: 数学计算库numpy