索引是定位一维或多维数组中的单个或多个元素的行为模式。切片是返回一维或多维数组中的单个或多个相邻元素的视图,目的是引用或赋值。NumPy
数组对象的内容可以通过索引或切片来访问和修改。
import numpy as np
a = np.arange(9)
# 最后一个元素
print(a[-1])
# 返回第2到第5个元素
print(a[2:5])
# 返回第0到底7个元素,步长为3
print(a[:7:3])
# 返回逆序的数组
print(a[::-1])
对于多维数组操作,NumPy
数组比Python
的列表更加灵活、强大。假设有一栋楼,共2层,每层的房间都是3行4列,那我们可以用一个三维数组来保存每个房间的居住人数(也可以是房间面积等其他数值信息)
import numpy as np
# 2层3行4列
a = np.arange(24).reshape(2, 3, 4)
print(a)
# 虽然可以这样索引
print(a[1][2][3])
# 但这样才是规范的用法
print(a[1, 2, 3])
# 1层的所有房间,等价于a[0]或a[0,...]
print(a[0,:,:])
# 所有楼层所有排的第1到第3列
print(a[:, :,1:3])
# 2层每一行的最后一个房间
print(a[1:,:, -1])
从上面的代码中可以看出,对多维数组索引或切片得到的结果的维度不是确定的。另外还有一点需要特别提醒:切片返回的数组不是原始数组的副本,而是指向与原始数组相同的内存区域。数组切片不会复制内部数组数据,只是产生了原始数据的一个新视图
import numpy as np
a = np.arange(12).reshape(3, 4)
print(a)
# 数组b是数组a的切片
b = a[1:, 2:]
print(b)
# 改变数组b的值,也会同时影响数组a
b[:, :] = 99
print(b)
print(a)
上面的代码中,数组b是数组a的切片,当改变数b的元素时,数组a也同时发生了改变。这就证明了切片返回的数组不是一个独立数组,而是指向原始数组相同的内存区域
NumPy
之所以拥有极高的运算速度,除了并行、广播和矢量化等技术因素外,其数组存储顺序和数组视图相互独立也是一个很重要的原因。改变数组结构的操作通常不会改变所操作的数组本身的存储顺序,只是生成了一个新的视图。np.resize()
函数是个例外,它不返回新的视图,而是真正改变了数组的存储顺序
函数 | 功能 |
---|---|
ndarray.reshape() | 按照指定的结构(形状)返回数组的新视图,不改变原数组 |
ndarray.ravel() | 返回多维数组一维化的视图,不改变原数组 |
ndarray.transpose() | 返回行变列的视图,不改变原数组 |
ndarray.resize() | 按照指定的结构(形状)改变原数组,无返回值 |
numpy.roolaxis() | 翻滚轴,返回新的视图,不改变原数组 |
import numpy as np
a = np.arange(12)
# reshape()函数返回数组a的一个新视图,但不会改变数组a
b = a.reshape((3, 4))
print(a.shape)
print(b is a)
print(b.base is a)
# resize()函数没有返回值,但真正改变了数组a的结构
a.resize([4, 3])
# 返回多维数组一维化的视图,但不会改变原数组
print(a.ravel())
# 返回行变列的视图,但不会改变原始图
print(a.transpose())
# 返回行变列的视图,等价于transpose()函数
print(a.T)
# 翻滚轴,1轴变0轴
print(np.rollaxis(a, 1, 0))
翻滚轴函数有一个很容易理解的应用,就是用它来实现图像的通道分离。下面的代码生成了一个宽为800像素、高为600像素的彩色随机噪声图,使用翻滚轴函数可以将其分离成RGB三个颜色通道。
from PIL import Image
import numpy as np
img = np.random.randint(0, 256, (600, 800, 3), dtype=np.uint8)
print(img.shape)
# 将图像数据分离成RGB三个颜色通道
r, g, b = np.rollaxis(img, 2, 0)
print(r.shape, g.shape, b.shape)
Image.fromarray(img).show()
NumPy
数组一旦创建不能再改变其元素数量。如果要动态改变数组元素数量,只能通过合并或拆分的方法生成新的数组。NumPy
仍然保留了append()
函数,只不过这个函数不再是数组的函数,而是升级到最外层的NumPy
命名空间了,并且该函数的功能不再是追加元素,而是合并数组
import numpy as np
print(np.append([[1, 2, 3]], [[4, 5, 6]]))
print(np.append([[1, 2, 3]], [[4, 5, 6]], axis=0))
print(np.append([[1, 2, 3]], [[4, 5, 6]], axis=1))
hstack()
水平合并函数,vstack()
垂直合并函数,dstack()
深度合并函数
import numpy as np
a = np.arange(4).reshape(2, 2)
b = np.arange(4, 8).reshape(2, 2)
# 水平合并
print(np.hstack((a, b)))
# 垂直合并
print(np.vstack((a, b)))
# 深度合并
print(np.dstack((a, b)))
stack()
函数使用axis轴参数指定合并的规则
import numpy as np
a = np.arange(60).reshape(3, 4, 5)
b = np.arange(60).reshape(3, 4, 5)
print(a.shape, b.shape)
print(np.stack((a, b), axis=0).shape)
print(np.stack((a, b), axis=1).shape)
print(np.stack((a, b), axis=2).shape)
print(np.stack((a, b), axis=3).shape)
import numpy as np
a = np.arange(16).reshape(2, 4, 2)
# 水平方向拆分成2部分
print(np.hsplit(a, 2))
# 垂直方向拆分成2部分
print(np.vsplit(a, 2))
# 深度方向拆分成2部分
print(np.dsplit(a, 2))
改变数组结构返回的是原数组的一个新视图,而不是原数组的副本。浅复制(view)和深复制(copy)则是创建原数组的副本,但二者之间也有细微差别:浅复制(view)是共享内存,深复制(copy)是独享内存
import numpy as np
a = np.arange(6).reshape((2, 3))
b = a.view()
print(b is a)
print(b.base is a)
print(b.flags.owndata)
c = a.copy()
print(c is a)
print(c.base is a)
print(c.flags.owndata)
NumPy
数组有两个排序函数,一个是sort()
,另一个是argsort()
。sort()
函数返回输入数组的排序副本,argsort()
函数返回的是数组值从小到大的索引号,这两个函数的参数完全一致
numpy.sort(arr, axis=-1, kind='quicksort', order=None)
numpy.argsort(arr, axis=-1, kind='quicksort', order=None)
# arr: 要排序的数组
# axis: 轴,指定排序的轴,默认值-1,表示没有指定排序轴,返回结果将沿着最后的轴排序
# kind: 排序方法,默认为"quickstart"(快速排序),其他选项还有"mergesort"(归并排序)和"heapsort"(堆排序)
# order: 指定用于排序的字段,前提是数组包含该字段
import numpy as np
a = np.random.random((2, 3))
print(a)
# 返回行内从小到大排序的索引号(列排序),相当于axis=1(最后的轴)
print(np.argsort(a))
# 返回行内从小到大排序的一个新数组(列排序)
print(np.sort(a))
# 返回列内从小到大排序的一个新数组(行排序)
print(np.sort(a, axis=0))
这里约定查找是返回数组中符合条件的元素的索引号,或返回和数组具有相同结构的布尔型数组,元素符合条件在布尔型数组中对应True,否则对应False。
最大值和最小值查找(下面的代码演示了返回数组中最大值和最小值的索引号,如果是多维数组,这个索引号是数组转成一维之后的索引号)
import numpy as np
a = np.random.random((2, 3))
print(a)
# 最大值的索引号
print(np.argmax(a))
# 最小值的索引号
print(np.argmin(a))
非零元素查找(下面代码演示了返回数组中非零元素的索引号)
import numpy as np
a = np.random.randint(0, 2, (2, 3))
print(a)
print(np.nonzero(a))
使用逻辑表达式查找(下面的代码演示了使用逻辑表达式查找符合条件的元素,返回结果是一个和原数组结构相同的布尔型数组,元素符合条件在布尔型数组中对应True,否则对应False)
import numpy as np
a = np.arange(10).reshape((2, 5))
print(a)
print((a>3)&(a<8))
使用where条件查找(np.where()
函数返回数组中满足给定条件的元素和索引号,其结构为元组,元组的第k个元素对应符合条件的元素在数组k轴上的索引号。这句话可以简单理解为,一维数组返回一个元素的元组,二维数组返回两个元素的元组,以此类推。np.where()
函数还可以用于替换符合条件的元素
import numpy as np
a = np.arange(10)
print(a)
print(np.where(a<5))
a = a.reshape((2, -1))
print(a)
print(np.where(a<5))
# 满足条件的元素不变,其他元素乘10
print(np.where(a<5, a, 10*a))
筛选是返回符合条件的元素。筛选条件有三种表示方式,一是使用np.where()
函数返回的Python元组,二是使用逻辑表达式返回的布尔型数组,三是使用整形数组
import numpy as np
a = np.random.random((3, 4))
print(a)
# 返回大于0.5的元素(使用np.where()函数返回的Python元组)
print(a[np.where(a>0.5)])
# 返回大于0.3且小于0.7的元素(使用逻辑表达式返回的布尔型数组)
print(a[(a>0.3)&(a<0.7)])
# 返回整形数组指定的项(使用整型数组)
print(a[np.array([2, 1])])
a = a.ravel()
# 返回整型数组指定的项(使用整型数组)
print(a[np.array([3, 5, 7, 11])])
# 返回整型数组指定的项(使用整型数组)
print(a[np.array([[3, 5], [7, 11]])])
一般而言,灰度图像每个像素的值域范围是[0, 255]
。假如用于表现不同灰度的字符集是['','.','-','+','=','*','#','@']
,从''
到'@'
表示从白到黑的8个灰度等级。我们需要将每个像素的灰度值分段转换成相应的字符。例如,灰度值小于32的像素用@
表示,大于或等于32且小于64的像素用#
表示,依次类推直至大于或等于224的像素用''
表示
import numpy as np
img = np.random.randint(0, 256, (5, 10), dtype=np.uint8)
print(img)
# 将256级灰度值转为8级灰度值
img = (img/32).astype(np.uint8)
print(img)
# 灰度级字符集
chs = np.array([' ', '.', '-', '+', '+', '*', '#', '@'])
# 用整形数组筛选数组元素
print(chs[img])
数据文件格式 | 存储数据格式 | 读写函数 |
---|---|---|
CSV | 通用格式 | loadtxt() 、savetxt() |
NPY/NPZ | 单个数组/多个数组 | load() 、save() 、savez() |
import numpy as np
a = np.random.random((15, 5))
# 将数组a保存成CSV格式的数据文件
np.savetxt('demo.csv', a, delimiter=',')
# 打开CSV格式的数据文件
data = np.loadtxt('demo.csv', delimiter=',')
print(data.shape, data.dtype)
import numpy as np
# 存储单个数组文件名
single_arr_fn = 'single_arr.npy'
# 存储多个数组文件名
multi_arr_fn = 'multi_arr.npz'
lon = np.linspace(10, 90, 9)
lat = np.linspace(20, 60, 5)
# 用save()函数把经度数组保存成.npy文件
np.save(single_arr_fn, lon)
# 接着用load()函数读出来
lon = np.load(single_arr_fn)
# 保存两个数组到一个文件
np.savez(multi_arr_fn, longitude=lon, latitude=lat)
# 用load()函数把这个.npz文件读成一个结构
data = np.load(multi_arr_fn)
# 查看所有的数组名
print(data.files)
# 使用data[数组名]就可以取得想要的数据
print(data['longitude'])