Python_NumPy_库

364 阅读8分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

基础

NumPy提供的主要对象就是同数据类型的多维数组(homogeneous multidimensional array)。他实际上可以被理解成一张表元素 通常是数字 所有元素具有相同的类型 这些元素可以使用由正整数构成的元组(tuple)来索引 。在NumPy中,各个维度被称为轴(axes)。轴的总数被称为秩(rank)

举例子,三维空间中的一个点的坐标是[1 , 2, 1],它就是一个rank是1的一个数组。因为它只有一个axis,这个axis的长度是3. [[1., 0., 0.], [0., 1., 2.]] 该数组的rank是2(两个维度),第一个维度的长度是2(2行),第二个的维度是3(3列).

NumPy的数组叫做ndarray。同时也有个别名array。numpy.array和 Python标准库中的array.array完全不是一个东西,后者仅仅支持一维数组提供的功能较少。ndaary对象比较重要的属性有:

ndarray.ndim 数组的axes的数目

ndarray.shape 数组的形状,如一个 n*m 的矩阵,它的shape就是(n,m)。而且len(ndarray.shape)==ndarray.ndim

ndarray.size 数组中所有元素的个数。它实际上是ndarray.shape中的所有值的乘积。

ndarray.dtype 用于描述数组中元素类型的对象。我们可以使用Python标准类型来创建或指定dtype。不过NumPy也提供了一些它自己的类型,比如:numpy.int32,numpy.int16,numpy.float64 。。。

ndarray.itemsize 数组中单个元素以字节计的大小。比如类型是float64,则itemsize就是8=(64/8)。它跟ndarray.dtype.itemsize是等价的。

ndarray.data 存储数组中实际的数据。通常我们不会直接用到这个属性,因为当我们需要访问数据时几乎总是提供的索引功能。

import numpy as np
a = np.arange(15).reshape(3,5)
a.shape
(3, 5)
a.ndim
2
a.dtype.name, a.itemsize
('int32', 4)
type(a)
numpy.ndarray
b = np.array([6,7,8])
b
array([6, 7, 8])
type(b)
numpy.ndarray

创建数组

比如:通过array,我们可以从普通的Python列表或元组直接创建。元素的类型根据原类型推断。

a = np.array([2,3,4])
a
array([2, 3, 4])
b = np.array([1.2, 3.5, 5.1])
b.dtype
dtype('float64')

一个常见的错误是,把数组内容当做参数直接传给array,而不是作为一个列表或元组:

a = np.array(1, 2, 3, 4) #错误

a = np.array([1, 2, 3, 4]) #正确

# array将两层嵌套序列转成二维数组,三层嵌套序列转成三维数组,以此类推:
b = np.array([(1.5, 2, 3), (4, 5, 6)])
b
array([[1.5, 2. , 3. ],
       [4. , 5. , 6. ]])

数组类型在创建时也可以指定

c = np.array([ [1,2], [3, 4]], dtype=complex)
c
array([[1.+0.j, 2.+0.j],
       [3.+0.j, 4.+0.j]])

需要固定形状的数组,但在定义数组时还没有加载数据。 NumPy为我们提供了一些包含占位符的数组创建函数。这样我们就可以避免使用动态数组(因为动态数组加载耗时)

  • zereos函数可以创建所有元素均为0的数组
  • ones函数可以创建所有元素均为1的数组
  • empty函数创建的数组,其元素都是随机的!!!

默认情况下,创建的dtype都是float64

np.zeros(  (3,4)  ) #   它的参数并不是数据,而是数组的形状(shape)
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,4) )
array([[0.00000000e+000, 0.00000000e+000, 0.00000000e+000,
        0.00000000e+000],
       [0.00000000e+000, 7.92481296e-321, 3.11525958e-307,
        1.24610723e-306]])
np.arange(10, 30, 5)
array([10, 15, 20, 25])
np.arange(0,2,0.5)
array([0. , 0.5, 1. , 1.5])

因为浮点数的有限精度问题,arrange返回长度可能无法预知。最好使用linspace,它可以指定所需要的数组长度,而不需要设定步长:

np.linspace(0, 2, 9)
array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  ])
from numpy import pi
x = np.linspace(0,  2*pi, 100)  # 对于要采样很多点的时候特别有用
f = np.sin(x)
f
array([ 0.00000000e+00,  6.34239197e-02,  1.26592454e-01,  1.89251244e-01,
        2.51147987e-01,  3.12033446e-01,  3.71662456e-01,  4.29794912e-01,
        4.86196736e-01,  5.40640817e-01,  5.92907929e-01,  6.42787610e-01,
        6.90079011e-01,  7.34591709e-01,  7.76146464e-01,  8.14575952e-01,
        8.49725430e-01,  8.81453363e-01,  9.09631995e-01,  9.34147860e-01,
        9.54902241e-01,  9.71811568e-01,  9.84807753e-01,  9.93838464e-01,
        9.98867339e-01,  9.99874128e-01,  9.96854776e-01,  9.89821442e-01,
        9.78802446e-01,  9.63842159e-01,  9.45000819e-01,  9.22354294e-01,
        8.95993774e-01,  8.66025404e-01,  8.32569855e-01,  7.95761841e-01,
        7.55749574e-01,  7.12694171e-01,  6.66769001e-01,  6.18158986e-01,
        5.67059864e-01,  5.13677392e-01,  4.58226522e-01,  4.00930535e-01,
        3.42020143e-01,  2.81732557e-01,  2.20310533e-01,  1.58001396e-01,
        9.50560433e-02,  3.17279335e-02, -3.17279335e-02, -9.50560433e-02,
       -1.58001396e-01, -2.20310533e-01, -2.81732557e-01, -3.42020143e-01,
       -4.00930535e-01, -4.58226522e-01, -5.13677392e-01, -5.67059864e-01,
       -6.18158986e-01, -6.66769001e-01, -7.12694171e-01, -7.55749574e-01,
       -7.95761841e-01, -8.32569855e-01, -8.66025404e-01, -8.95993774e-01,
       -9.22354294e-01, -9.45000819e-01, -9.63842159e-01, -9.78802446e-01,
       -9.89821442e-01, -9.96854776e-01, -9.99874128e-01, -9.98867339e-01,
       -9.93838464e-01, -9.84807753e-01, -9.71811568e-01, -9.54902241e-01,
       -9.34147860e-01, -9.09631995e-01, -8.81453363e-01, -8.49725430e-01,
       -8.14575952e-01, -7.76146464e-01, -7.34591709e-01, -6.90079011e-01,
       -6.42787610e-01, -5.92907929e-01, -5.40640817e-01, -4.86196736e-01,
       -4.29794912e-01, -3.71662456e-01, -3.12033446e-01, -2.51147987e-01,
       -1.89251244e-01, -1.26592454e-01, -6.34239197e-02, -2.44929360e-16])

array,zeros,zeros_likee,ones_like,empty,empty_like,arange,linspace,numpy,random ...

可以在notebook里面直接np.array?阅读,也可以去NumPy的管方文档

打印数组

打印出来非常类似于嵌套列表

a = np.arange(6)  #  一维
print(a)
[0 1 2 3 4 5]
b = np.arange(12).reshape(3,4) # 二维
print(b)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
c = np.arange(24).reshape(2,3,4) # 三维
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]]]

如果数组太长,NumPy会自动忽略中间部分的数据,只打印首位:

我们可以通过设置set_printoptions选项来关闭功能: np.set_printoptions(threshold='nan')

print(np.arange(100000))
[    0     1     2 ... 99997 99998 99999]
print(np.arange(10000).reshape(100,100))
[[   0    1    2 ...   97   98   99]
 [ 100  101  102 ...  197  198  199]
 [ 200  201  202 ...  297  298  299]
 ...
 [9700 9701 9702 ... 9797 9798 9799]
 [9800 9801 9802 ... 9897 9898 9899]
 [9900 9901 9902 ... 9997 9998 9999]]

基础操作

在数组上进行的算术操作都是元素级别的(elementwise)。

a = np.array([20, 30, 40, 50])
b = np.arange(4) # 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])

在线性代数中,乘号*一般表达的是矩阵乘法,但在NumPy中它表示元素级别的乘法。

矩阵乘法在NumPy中使用dot:

A = np.array([ [1, 1],
             [0, 1]] )

B = np.array( [[2,  0],
               [3, 4]])

A*B  # 元素级乘法
array([[2, 0],
       [0, 4]])
A.dot(B) # 矩阵乘法
array([[5, 4],
       [3, 4]])
np.dot(A,B)
array([[5, 4],
       [3, 4]])
a = np.ones((2, 3), dtype = int)
b = np.random.random((2, 3))
a *=3
a
array([[3, 3, 3],
       [3, 3, 3]])
b +=a
b
array([[3.62220496, 3.85951178, 3.62499834],
       [3.51389196, 3.57738806, 3.47357054]])
a +=b
---------------------------------------------------------------------------

UFuncTypeError                            Traceback (most recent call last)

Input In [33], in <cell line: 1>()
----> 1 a +=b


UFuncTypeError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int32') with casting rule 'same_kind'
print(a.sum(), a.min(), a.max())
a
b  = np.arange(12).reshape(3,4)
b
b.sum(axis=0) # 每一列的和
b.min(axis=1) # 每一行的最小值
b.cumsum(axis=1) # 每一行当累加和

通用函数(Univeersal Functions)

NumPy提供了一些常用的数学函数,如:sin,cos,exp等。在NumPy中,它们被称为“通用函数”(ufunc)。在NumPy中,这些函数的作用在数组上时,都是作用在每个单独的元素上,并产生一个新的数组。

B = np.arange(3)
B
np.exp(B)
np.sqrt(B)
C = np.array([2., -1., 4.])
np.add(B,C)

索引,切片以及遍历

一维数组非常类似于Pyton的序列(list,tuple),它们可以被索引(index),被切片(slice),被遍历(iterate)。

a = np.arange(10)**3
a
a[2]
a[2:5]
a[:6:2] =  -1000 
a
a[::-1]
for i in  a:
    print(i**(1/3.0))

多维数组每一个轴(axis)均有一个索引(index)。这些索引使用","(半角逗号)分隔,以元组(tuple)的形式表现:

def f(x, y):
    return  10*x + y
b = np.fromfunction(f,(5, 4),dtype=int)
b
b[2,3]
b[0:5,1] #  第二列,0,1,2,3,4行
b[:,1] #第二列,所用行
b[1:3, :]  #  第1,2行,所有列

另外,NumPy还有...这种写法,它会自动判断所需要填补的索引: 比如,我们现在有一个拥有五个轴(axis)的数组:

  • x[1, 2, ...]等价于x[1,2,:,:,:]
  • x[...,3]等价于 x[:,:,:,:,3]
  • x[4,...,5,:]等价于 x[4,:,:,5,:]
c =  np.array( [[[ 0, 1, 2],
                [  10,12, 13]],
               [[100, 101, 102],
               [110, 112,  113]]])
c.shape
c[1,...]
c[...,2]

遍历是从第一轴开始的:

for row  in  b:
    print(row)
for element in  b.flat:   # 打平访问
    print(element,end=",")

形状操作

修改一个数组的形状

a  =  np.floor(10*np.random.random( (3, 4)))
a
a.shape
a   

在NunPy中有很多方法可以改变一个数组的形状。 请注意,下面三种方式均返回一个修改后的数组,但又不改变原来的数组:

a.ravel() #  返回打平的数组的形状
a.reshape(6,2)  # 返回目标形状的数组
a.T #  返回它的转置
a
a.resize((2,6))
a
a.reshape(3,-1)

将不同的数组堆叠(stacking)起来

多个数组可以沿着不同的轴堆叠起来:

a =  np.floor(10*np.random.random((2,2)))
b =  np.floor(10*np.random.random((2,2)))
print(a)
print(b)
np.vstack((a,b))
np.hstack((a,b))
c = np.array([3,3])
np.column_stack((a,c))

将一个数组切分成多个

使用split,我们可以将一个数组沿水平方向进行切分。切分时可以指定:

  • 目标数组个数---将自动计算切分数组的形状
  • 切分列下标---将在指定的列下标进行切分
a = np.floor(10*np.random.random((2, 12)))
a
np.hsplit(a,3)
np.hsplit(a, (3,4)) #  在第三列第四列切分

拷贝(copy)与视图(view)

根本不拷贝

简单的赋值不会发生任何拷贝行为:

a  =  np.arange(12)
b  = a # 不会新建对象
b is a # a 和 b 是对同一个   ndarray对象的两个名字
b.shape =  3,4 # 同时会修改a的形状
a.shape

Python以引用的方式传递对象,所以函数调用不会产生拷贝:

def  f(x):
    print( id(x))
print(id(a))
f(a)

视图和浅拷贝

不同的数据对象可以共享相同的数据。view方法新建一个数组对象,但仍然使用相同的数据。

c = a.view()
c is a
c.base is a # c仅仅是a中数据的一个视图

c.flags.owndata
c.shape = 2,6 # a的形状不会改变
a
c[0,  4] =   1234 # a的数据会改变
a
c

对一个数组进行切片会返回它的视图:

s =  a[:,  1:3]
s[:] = 10
a

深拷贝

copy()方法会对数据及其数据做一次完整的拷贝。

d  =  a.copy()  
d is a
d
d[0, 0] = 999
a
d
palettle = np.array([ [ 0,0]])