Python—Numpy基础学习

前导

准备

  • NumPy的安装准备工作
  • Python学习,具体参阅Python教程
  • 为运行示例,需安装matplotlib库

学习目标

  • 了解NumPy中一维数组,二维数组和n维数组之间的区别
  • 了解如何在不使用for循环的情况下将一些线性代数运算应用于n维数组
  • 了解n维数组的轴和形状属性



基础

简单介绍

​ NumPy的主要对象是齐次多维数组。它是由非负整数的元组索引的所有类型相同的元素(通常为数字)表。在NumPy中,维度称为

​ 例如:3D空间上的点的坐标[1,2,1]只有一个轴。该轴上有3个元素,所以我们说它的长度为3.

​ 在下图所示的中,数组有2个轴:第一个轴的长度为2,第二个轴的长度为3(可以理解为2行3列的向量)

[[1.,0.,0.],
[0.,1.,2.]]

NumPy的数组类别称为ndarray。

ndarray 对象是用于存放同类型元素的多维数组。

ndarray 中的每个元素在内存中都有相同存储大小的区域。

创建一个ndarray只需调用Numpy的array函数即可:

numpy.array(object, dtype = None, copy = True, order = None, subok = False, ndmin = 0)

参数说明:

名称 描述
object 数组或嵌套的数列
dtype 数组元素的数据类型,可选
copy 对象是否需要复制,可选
order 创建数组的样式,C为行方向,F为列方向,A为任意方向(默认)
subok 默认返回一个与基类类型一致的数组
ndmin 指定生成数组的最小维度

ndarray对象的重要的属性如下:

  • ndarray.ndim

  数组的轴(维度)数

  • ndarray.shape

   数组的维度。这是一个整数元组,指示每个维度中数组的大小。对于具有n行和m列的矩阵,shape将为(n,m)。因此,shape元组的长度就是轴数,ndim

  • ndarray.size

   数组元素的总数。这等于shape元素的乘积

  • ndarray.dtype

   一个对象,描述数组中元素的类型。可以使用标准Python类型创建或指定dtype。另外,NumPy提供了自己的类型。numpy.int32,numpy.int16和numpy.float64是一些示例。

  • ndarray.itemsize

   数组中每个元素的大小(以字节为单位)。例如,类型为float64元素的数组具有itemsize 8(= 64/8),而其中类型为complex32中的元素具有itemsize 4(= 32/8)。等同于ndarray.dtype.itemsize。

  • ndarray.data

   包含数组实际元素的缓冲区。通常,我们不需要使用此属性,因为我们将使用索引工具访问数组中的元素。

举个栗子

import numpy as np
a = np.arange(15).reshape(3,5)
a




数组创建

  • 使用array函数
import numpy as np
a = np.array([2,3,4])
a
a.dtype
  • 常见的出错在于,错误的使用多个参数调用数组,而不是使用单个序列作为参数。例如:
a = np.array(1,2,3,4)       #错误的

a = np.array([1,2,3,4])		#正确的
  • array将[序列,序列]转换为二维数组,将[序列,序列,序列]转换为三维数组,以此类推...
b = np.array([(1,2,3),(4,5,6)])		#这是一个二维数组
b
  • 数组类型可以在创建时明确的指出
c = np.array([1,2],[3,4],dtype=complex)		#指定数组的类型为complex
c
  • Numpy的一些内置函数
#zeros()    将指定维度的数组全部置0,默认数据类型为float64,可以设置参数改变类型
a = np.zeros((3,4))
print(a)
print(a.dtype)

#ones()     将指定维度的数组全部置1,默认数据类型为float64,可以设置参数改变类型
b = np.ones((2,3,4),dtype = np.int16)
print(b)
print(b.dtype)

#empty()     将指定维度的数组初始化为没有特定意义的随机值(内容是随机的并取决于内存状态的数组),默认数据类型为float64,可以设置参数改变类型

c = np.empty((2,3))
print(c)
print(c.dtype)
  • 更多内置函数

arrayzeroszeros_likeonesones_likeemptyempty_likearangelinspacenumpy.random.Generator.randnumpy.random.Generator.randnfromfunctionfromfile




输出数组

  • 简单介绍下输出数组时的规则:

    • 最后一个轴从左到右打印,
    • 倒数第二个从上到下打印,
    • 其余的也从上到下打印,每个切片之间用空行隔开。
  • 如果数组太大无法打印,那么Numpy会自动跳过数组的中心部分,仅输出边角点

print(np.arange(10000))

print(np.arange(10000).reshape(100,100))
  • 要禁用此行为并强制NumPy打印整个数组,可以使用更改打印选项set_printoptions
import sys
np.set_printoptions(threshold=sys.maxsize)       # 需要导入sys库
print(np.arange(10000).reshape(100,100))



基本运算


算术运算同样也适用于数组运算

import numpy as np
a = np.array([20,30,40,50])

b = np.arange(4)
b
c = a - b
c
10*np.sin(a)
a<35

与许多矩阵语言不同,乘积运算符*在NumPy中是按元素及进行操作。

在NumPy中可以使用@运算符(Python版本>=3.5)或用dot函数执行矩阵乘积

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

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

A*B
A@B
A.dot(B)

某些操作(如+=和*=),是修改现有数组,而不是创建一个新数组

rg = np.random.default_rng(1)     # 创建默认的随机数生成的示例
a = np.ones((2,3), dtype=int)     # Int类型的全1向量
b = rg.random((2,3))              #  默认为浮点数
a *= 3
a
b += a
b
a += b        #浮点类型数组b不会自动转换为整型

当使用不同类型的数组进行操作时,结果数组的类型对应于更通用或更精确的数组(一种称为向上转换的行为)。

类似于上述案例中int类型数组a可以向上转型为float类型

而float类型的数组b却不能转型为数组int类型a


许多一元运算(例如求和)都作为ndarray该类的方法实现

a = rg.random((2,3))
a
a.sum()
a.min()
a.max()

默认情况下,这些操作适用于数组,就好像它是数字列表一样,而不管其形状如何。但是,通过指定axis 参数,您可以沿数组的指定轴应用操作(通常axis=0表示指定列,axis=1指定行)

b = np.arange(12).reshape(3,4)
b
b.sum(axis=0)      #求每一列的和
b.min(axis=1)      #求每一行的最小值
b.cumsum(axis=1)      #每一行的累积和



通用函数

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

更多函数

allanyapply_along_axisargmaxargminargsortaveragebincountceilclipconjcorrcoefcovcrosscumprodcumsumdiffdotfloorinnerinvertlexsortmaxmaximummeanmedianminminimumnonzeroouterprodreroundsortstdsumtracetransposevarvdotvectorizewhere




索引,切片和迭代

一维数组可以被索引,切片和迭代,就像列表和其他Python序列一样

#索引
a = np.arange(10)**3         #幂运算
a
#切片
a[2:5]      #区间为[2,5)  ,  左闭右开
#切片
#array[pos1:pos2:step] = val  指从pos1到pos2-1,没隔两个元素设置为val
#例如:
#  a
# array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729], dtype=int32)
a[:6:2] = 1000              #相当于a[0:6:2]    区间[0,6)     左闭右开
a
a[::-1]         #逆转数组
#遍历迭代
for i in a:
    print(i**2)

多维数组每个轴可以有一个索引。这些索引以元组给出,并用逗号分割

def f(x,y):
    return 10*x+y
b = np.fromfunction(f,(5,4),dtype=int)     #调用f函数,生成5行4列的数组,参数是数组的下标,类型为Int
b
b[2,3]
b[0:5,1]        #每行的下标为1的值
b[:,1]          #等效与上面这个案例
b[1:3,:]        #输出第2行和第三行的每一列

如果提供的索引数少于轴数,则丢失的索引数将视为完整切片:

b[-1]            #未指明列的分片情况,则视为每一列,等效于b[-1,:]

括号中的表达式b[i]被视为i 后跟:表示所需数量的剩余轴实例。

NumPy还允许您使用写为 b[i,...]。

点(...)表示为许多冒号,根据需要,以产生一个完整的索引元组。

例如,如果x是具有5个轴的数组,则

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

迭代过多维数组相对于第一轴线完成

# b
# array([[ 0,  1,  2,  3],
#        [10, 11, 12, 13],
#        [20, 21, 22, 23],
#        [30, 31, 32, 33],
#        [40, 41, 42, 43]])
for row in b:
    print(row)

但是,如果要对数组中的每个元素执行操作,则可以使用flat属性,该属性是 数组中所有元素的 迭代器

for element in b.flat:
    print(element)



操纵形状

改变数组的形状

数组的形状是由沿每个轴的元素数确定

a = np.floor(10*rg.random((3,4)))       #np.floor 是将浮点数向下取整
print(a)
print(a.shape,a.dtype)

数组的形状可以使用各种命令来更改。

请注意,以下三个命令均返回修改后的数组,但不更改原始数组:

a.ravel()			#返回一个一维数组
a.reshape(6,2)        #改变数组a为6行2列
a.T     #a的转置

ndarray.resize方法修改了数组本身:

#a修改前:
#array([[9., 7., 5., 2.],
#       [1., 9., 5., 1.],
#       [6., 7., 6., 9.]])
a.resize((2,6))
a
#a修改后:

如果在改变形状时将维度改为-1,则会自动计算其他尺寸:

a.reshape(3,-1)      #指定转换为3行,系统自动计算列数

更多函数:

ndarray.shapereshaperesizeravel


堆叠在一起的不同数组

几个数组可以沿不同的轴堆叠在一起:

a = np.floor(10*rg.random((2,2)))
print('a=','\n',a)
b = np.floor(10*rg.random((2,2)))
print('b=','\n',b)
np.vstack((a,b))
np.hstack((a,b))

函数column_stack将1维数组作为列向量堆叠成2维数组

只有当作用与2维数组时是与hstack是等效的

from numpy import newaxis
np.column_stack((a,b))			#结果等效于上个案例中np.hstack((a,b))
a = np.array([4.,2.])
b = np.array([3.,8.])
print(np.column_stack((a,b)),'\n')
print(np.hstack((a,b)))               #两并不相同
a[:,newaxis]         #将a视为一个二维数组,newaxis字面意思就是新的轴
c = np.array([[1,2],[3,4]])
c[:,:,newaxis]
np.column_stack((a[:,newaxis],b[:,newaxis]))
np.hstack((a[:,newaxis],b[:,newaxis]))

另外,无论输入数组是怎样的,函数row_stack都等效于函数vstack。

实际上,row_stack别名是vstack

print(np.column_stack is np.hstack)
print(np.row_stack is np.vstack)

通常,对于二维以上(不包括二维)的数组,hstack沿着他们的第二个轴堆叠,vstack沿着他们的第一个轴堆叠

a = np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
b = np.array([[[9,10],[11,12]],[[13,14],[15,16]]])
print(a.shape)
print(np.hstack((a,b)).shape)
np.hstack((a,b))
a = np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
b = np.array([[[9,10],[11,12]],[[13,14],[15,16]]])
print(a.shape)
print(np.vstack((a,b)).shape)
np.vstack((a,b))

注意
在复杂情况下,r_ 和 c_ 杜宇通过一个轴将数字叠加创建数组时很有用。

他们允许使用(“:”)

np.r_[1:9,1,2,3]
np.c_[np.array([1,2,3]), np.array([4,5,6])]

​ 当使用数组作为参数时,r_和c_在默认行为上类似于vstack和hstack,但是允许一个可选参数给出要连接的轴的编号。

More Information

hstack, vstack, column_stack, concatenate, c_, r_


将一个数组拆分为几个较小的数组

使用hsplit,您可以沿数组的水平轴拆分数组

方法是指定要返回的形状相同的数组的数量,或者指定要在其后进行划分的列

a = np.floor(10*rg.random((2,12)))
a
np.hsplit(a,3)        #一分为三
np.hsplit(a,(3,4))      #分别在第三列和第四列后分离

vsplit沿着垂直的轴拆分分

并且array_split循序指定沿着哪个轴拆分




副本和视图

​ 副本是一个数据的完整的拷贝,如果我们对副本进行修改,它不会影响到原始数据,物理内存不在同一位置。

​ 视图是数据的一个别称或引用,通过该别称或引用亦便可访问、操作原有数据,但原有数据不会产生拷贝。如果我们对视图进行修改,它会影响到原始数据,物理内存在同一位置。

在操作数组时,有时会将其数据复制到新数组中,有时不复制。

对于初学者来说,这通常会引起混乱。

有以下三种情况:无复制、视图或浅复制、副本或深复制

无复制

简单分配不会复制对象或其数据

a = np.array([[ 0,  1,  2,  3],
              [ 4,  5,  6,  7],
              [ 8,  9, 10, 11]])

b = a        #没有创建新的对象
b is a       #简单理解为a,b都指向了同一片区域,a、b只是ndarray对象的两个不同名字而已

Python将可变对象作为引用传递,因此函数调用不会复制。

def f(x):
    print(id(x))
id(a)               #id是一个对象的唯一的标识符
f(a)

视图或浅复制

不同的数组对象可以共享相同的数据。view方法创建一个具有相同数据的新数组对象。

# a
# a = np.array([[ 0,  1,  2,  3],
#               [ 4,  5,  6,  7],
#               [ 8,  9, 10, 11]])
c = a.view()
c
c is a
c.base is a       #c与a具有相同的视图
c = c.reshape((2,6))
a.shape                      #修改c的形状并不能改变a
c[0,4] = 1024
a                   #修改c中的某个值可以修改a   
s = a[ : ,1:3]      
s[:] = 10         #s[:]是s对象的view,s与s[:]有区别,请注意
a

副本或深复制

copy方法对数组及其数据进行完整复制

d = a.copy()            #相当于新建了一个带有新数据的新数组
print(d is a)
print(d.base is a)      # d与a不共享任何事情 
d[0,0]=99999
print(a)

如果不再需要原始数组,有时应该在切片后调用复制。

例如,假设a是一个巨大的中间结果,最终结果b只包含a的一小部分,那么在使用切片构造b时需要做一个深度复制:

a = np.arange(int(1e8))
b = a[:100].copy()
del a  # a的内存可以被释放

如果使用b = a[:100],则a被b引用,即使del a被执行,a也会存在内存中。

函数和方法概述




不太基础

广播规则

广播允许通用函数以一种有意义的方式处理不具有完全相同形状的输入。

广播的第一个规则是,如果所有输入数组的维数不相同,则会在较小阵列的形状前面重复加上一个“1”,直到所有阵列的维数相同。

广播的第二个规则确保沿某一特定维度大小为1的数组表现得好像它们具有沿该维度形状最大的数组的大小。假设“广播”数组的数组元素沿该维度的值相同。

在应用广播规则后,所有阵列的大小必须匹配。更多详情请参阅广播




高级索引和索引技巧

NumPy提供了比常规Python序列更多的索引功能。

如前所述,除了通过整数和切片建立索引之外,还可以通过整数数组和布尔值数组对数组进行索引。

用索引数组建立索引

a = np.arange(12)**2        
i = np.array([1,1,3,8,5])      #下标数组
a[i]
j = np.array([[3,4],[7,9]])		#二维索引
a[j]

当索引数组a是多维的时,一个索引数组指的是a的第一个维。

下面的示例通过使用调色板将标签图像转换为彩色图像来显示这种行为。

 palette = np.array([[0, 0, 0],         # black
                     [255, 0, 0],       # red
                     [0, 255, 0],       # green
                     [0, 0, 255],       # blue
                     [255, 255, 255]])  # white
 image = np.array([[0, 1, 2, 0],        # 每一个值都对应着palette中的一个颜色
                   [0, 3, 4, 0]])       
# 例如:0-black,1-red,2-green,3-blue,4-white
#image中的第一行[0, 1, 2,0],对应着palette中的四行
#        [  0,   0,   0],
#        [255,   0,   0],
#        [  0, 255,   0],
#        [  0,   0,   0]
 palette[image]

我们还可以为多个维度提供索引。每个维度的索引数组必须具有相同的形状。

a = np.arange(12).reshape(3,4)
a
i = np.array([[0,1],[1,2]])          #对应a数组的横下标
j = np.array([[2,1],[3,3]])          #对应a数组的纵下标
a[i,j]                               #i,j必须要有相同的形状,因为要一一对应
#对应结果a[0,2] = 2,a[1,1]=5......
#2根据广播规则扩展为
#[2,2]
#[2,2]
a[i,2]
# :相当于
#[[0,0],[0,0]]
#[[1,1],[1,1]]
#[[2,2],[2,2]]
# j 根据广播规则扩展为
#[[2,1],[3,3]]
#[[2,1],[3,3]]
#[[2,1],[3,3]]
a[:,j]

您还可以将索引与数组一起用作分配给以下对象的目标:

a = np.arange(5)
print(a)
a[[1,3,4]]=0
print(a)

但是,当索引列表包含重复项时,分配将完成几次,而留下最后一个值:

a = np.arange(5)
a[[0,0,2]]=[1,2,3]    #执行两次对a[0]的赋值,最后一次a[0]=2
a

用布尔数组建立索引

当我们使用(整数)索引数组对数组进行索引时,我们将提供要选择的索引列表。对于布尔索引,方法是不同的。

我们显式选择数组中需要哪些项,不需要哪些项。

对于布尔索引,可以想到的最自然的方法是使用形状与原始数组相同的布尔数组:

a = np.arange(12).reshape(3,4)
b = a>4
b
a[b]     #返回的是一维数组

这个属性在分配时也同样有用

a[b] = 0        所有大于4的都置为0
a

您可以看下面的示例,看看如何使用布尔索引来生成Mandelbrot集的图像:

import numpy as np
import matplotlib.pyplot as plt
def mandelbrot( h,w, maxit=20 ):
    """返回大小(h,w)的曼德尔布罗特分形图像"""
    y,x = np.ogrid[ -1.4:1.4:h*1j, -2:0.8:w*1j ]   #横纵坐标,ogrid函数:取[-1.4,1.4),个数为h*1j
    #print(x.shape)   (1,400)
    #print(y.shape)   (400,1)
    c = x+y*1j
    z = c
    #print(c.shape)    (400,400)   c[i,j]=x[i,j]+y[i,j]
    divtime = maxit + np.zeros(z.shape, dtype=int)
    for i in range(maxit):
        z = z**2 + c
        diverge = z*np.conj(z) > 2**2            # 谁是发散的,(conj函数用于返回复数的共轭值)
        div_now = diverge & (divtime==maxit)  # 谁现在发散,(div_now是一个(400,400)的布尔值)
        #print(i,div_now)
        divtime[div_now] = i                  # note when
        z[diverge] = 2                        # 避免分散过多
    
    return divtime
plt.imshow(mandelbrot(400,400))

使用布尔值建立索引的第二种方式与整数索引更相似;

对于数组的每个维度,我们提供一个一维布尔数组来选择所需的切片:

a = np.arange(12).reshape(3,4)
print(a)
b1 = np.array([False,True,True])
b2 = np.array([True,False,True,False])
a[b1,:]
a[:,b2]
a[b1,b2]         #好奇怪,不大懂,候补

注意,一维布尔数组的长度必须与要切片的维度(或轴)的长度一致。在前面的示例中,b1的长度为3 (a中的行数),而b2(长度为4)适合于索引a的第二个轴(列)。


ix_()函数

ix_函数可用于组合不同的向量,得出多个向量的笛卡尔积的映射关系。

例如,如果你想计算所有的a+b*c:

a = np.array([2,3,4,5])
b = np.array([8,5,4])
c = np.array([5,4,6,8,3])
ax,bx,cx = np.ix_(a,b,c)
print(ax,ax.shape,'\n')
print(bx,bx.shape,'\n')
print(cx,cx.shape,'\n')
result = ax+bx*cx
print(result.shape)
result
print(result[3,2,4])
print(a[3]+b[2]*c[4])

另一种方式

与普通的ufunc.reduce相比,这种reduce的优点在于它利用了广播规则 ,以避免创建一个参数数组,该参数数组的大小乘 以输出的数量乘以向量的数量。

def ufunc_reduce(ufct, *vectors):
    vs = np.ix_(*vectors)
    r = ufct.identity
    for v in vs:
        r = ufct(r,v)
    return r
ufunc_reduce(np.add,a,b,c)

用字符串索引

结构化数组是ndarray,其数据类型是由一些更简单的数据类型组成的,这些简单数据类型被组织为一系列命名字段

x = np.array([('Rex', 9, 81.0), ('Fido', 3, 27.0)],
              dtype=[('name', 'U10'), ('age', 'i4'), ('weight', 'f4')])
x

更多请参阅结构化数组




线性代数

简单的数组操作

有关更多信息,请参见numpy文件夹中的linalg.py

import numpy as np
a = np.array([[1.0,2.0],[3.0,4.0]])
a.transpose()         #转置
np.linalg.inv(a)      #矩阵求逆
np.linalg.det(a)      #求行列式
u = np.eye(2)    #单位矩阵
u
j = np.array([[0.0, -1.0], [1.0, 0.0]])
j@j               #矩阵相乘
image-20201107222906525
np.trace(u)    #矩阵的迹
#在线性代数中,一个n×n矩阵A的主对角线(从左上方至右下方的对角线)上各个元素的总和被称为矩阵A的迹(或迹数),一般记作tr(A)。
y = np.array([[5.],[7,]])
np.linalg.solve(a,y)      # 调用solve函数求解线性方程
solve函数有两个参数a和b。a是一个N*N的二维数组,而b是一个长度为N的一维数组,solve函数找到一个长度为N的一维数组x,使得a和x的矩阵乘积正好等于b,数组x就是多元一次方程组的解

np.linalg.eig(j)       #计算一个方阵的特征值和右特征向量。
#numpy.linalg.eig(a)[source]
#参数:
#a(…,M, M)数组
#为其计算特征值和正确特征向量的矩阵
#返回值
#w(…,M)数组
#特征值,每个根据它的多重性重复。特征值不一定是有序的。得到的数组将是复杂类型,除非虚部为零,在这种情况下,它将被转换为实类型。当a为实时,所得到的特征值将为实(0虚部)或以共轭对出现

#v(…,M, M)数组
#归一化(单位“length”)特征向量,使列v[:,i]为特征值w[i]对应的特征向量。



技巧与窍门

”自动“重塑

a = np.arange(30)
b = a.reshape((2,-1,3))   #-1指“Whatever is needed”
print(b,'\n',b.shape)

向量堆叠

我们如何根据一组等大小的行向量构造一个二维数组?

在MATLAB中这很简单:如果x和y是相同长度的两个向量,你只需要做m=[x;y]。

在NumPy中,它通过函数column_stack、dstack、hstack和vstack来工作,这取决于要进行堆叠的维度。例如:

x = np.arange(0,10,2)
y = np.arange(5)
m = np.vstack([x,y])
print(m)
n = np.hstack([x,y])
print(n)

直方图

应用于数组的 NumPy histogram函数返回一对向量:数组的直方图和bin edges的向量。

注意:matplotlib还有一个用于构建直方图的函数(在Matlab中称为hist),它与NumPy中的函数不同。

主要的区别是pylab.hist自动绘制直方图,而numpy.histogram只生成数据。

import numpy as np
rg = np.random.default_rng(1)       #设置随机种子
import matplotlib.pyplot as plt
#正态分布
mu,sigma = 2,0.5     #参数
v = rg.normal(mu,sigma,10000)#满足正态分布的10000个数
plt.hist(v,bins=50,density=1)   #bins:直方图的宽度,density:密度
(n,bins)=np.histogram(v,bins=50,density=True)
plt.plot(.5*(bins[1:]+bins[:-1]),n)

本文翻译自Numpy官方文档
如果发现问题或有疑问,欢迎下方留言


利益相关
更多资源欢迎关注公众号“北方向北”(或扫描右侧二维码
点个大拇指支持支持,感谢

posted @ 2021-01-24 20:05  云北海  阅读(202)  评论(0编辑  收藏  举报