文章目录
1 引言
Python 用列表(list)保存一组值,可当做数组使用,但由于列表的元素可以是任何对象,因此列表中保存的是对象的指针(引用),对于数值运算来说,这种结构显然是浪费内存和 CPU 时间的。
Python 的 array 模块,与 list 不同,能直接保存数值,但是不支持多维数组,没有各种运算函数,因此也不适合做数值运算。
NumPy 是为数值计算而生的,为 Python 带来了真正的多维数组功能,并且提供了十分丰富的对数组进行处理和运算的函数集。其对常用的数学函数进行数组化,使这些数学函数能直接对数组进行运算,将本来需要在 Python 中进行的循环运算,转移到高效的库函数中,充分利用这些函数能明显地提高程序的运算速度。
其提供了两种基本对象:ndarray 和 ufunc。ndarray 是存储单一数据类型的多维数组,ufunc 是能够对数组进行处理的函数。
2 ndarray
demo = np.array([[0,1,2],[3,4,5],[6,7,8]], dtype=np.float32)
下图为数组 demo 的内存结构:
(dtype, dim count, dimensions, strides, data)
strides: 每个轴上相邻两个元素的地址差
通过切片下标得到的新数组是原始数组的视图,data 是共享的,但是新数组的 strides 属性和 data 属性会发生变化。
数据存储区域(data)保存着数组中所有元素的二进制数据,dtype 对象定义了如何将二进制数据转化为可用的值。
2.1 shape
可以通过 shape 获取数组的形状,也就是数组各个轴的大小。
shape 是一个描述数组各个轴长度的元组(tuple),更改 shape 属性并不是对数组进行转置,而只是改变每个轴的大小,数组元素在内存中的位置并没有改变。
import numpy as np
a = np.array([[1,2,3,4], [4,6,7,8], [9,10,11,12]])
# =========== shape =============
a.shape == (3,4) # (3,4)
a.shape = 4,3 # 更改轴属性,并不是对数组进行转置,而只是改变每个轴的大小,数组元素在内存中的位置并没有改变。
a.shape = 2, -1 # -1 表示自动计算此轴的长度,为 (2,,)
b = a.reshape((2, -1)) # a.reshape(2, -1), 创建新的数组,a 保持不变;
a[0][0] = 100 # 内存数据并没有发生变更!
b[0][0] == 100 # true
a.dtype == 'int32' # true
# =========== typeDict =============
np.typeDict['int32'] == np.int32 # true
np.typeDict['d'] == np.float64 # true
np.typeDict['double'] == np.float64 # true
np.typeDict['float'] == np.float64 # true
set(np.typeDict.values()) # 所有的数据类型
2.2 创建数组
可以通过传入特定的 [] 来创建 ndarray
a = np.array([1, 2, 3, 4])
b = np.array((5, 6, 7, 8))
c = np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]])
除此之外,还有其他更高效的创建数组的方式。
- arange() 类似于 range(), 通过制定 (start, stop, step) 创建表示等差数列的一维数组,不包含终值。
- linspace() 通过制定 (start, stop, num) 创建表示等差数列的一维数组,可以通过 endpoint 参数指定是否包含终值。
- logspace() 与 linespace() 相似,创建等比数列数组。
- empty(),zeros(),ones(): 创建指定形状和类型的数组
- fromstring(),frombuffer(),fromfile(): 从字节流或文件中创建数组
- fromfunction() : 通过特定函数生成特定形状的数组
# =========== arange,logspace,linespace =============
np.arange(0, 1, 0.1) # [ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 ]
np.linspace(0, 1, 10) # [ 0. , 0.11111111, 0.22222222, 0.33333333, 0.44444444, 0.55555556, 0.66666667, 0.77777778, 0.88888889, 1. ]
np.linspace(0, 1, 10, endpoint=False) # [ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 ]
np.logspace(0, 2, 5, base=10) # [ 1. , 3.16227766, 10. , 31.6227766 , 100. ]
# =========== empty,zeros,ones =============
np.empty((2,3),np.int)
np.zeros(4, np.float)
np.ones((3,4)) # defalut is np.float
# =========== fromstring,frombuffer,fromfile =============
s = "abcdefgh"
np.fromstring(s, dtype=np.int8) # [ 97, 98, 99, 100, 101, 102, 103, 104]
# =========== fromfunction =============
np.fromfunction(lambda x, y: x*y, (2,2)) # [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
2.3 元素存取与切片
切片:(start, stop, step)
- 与列表不同,切片后的数组只是原数组的视图,共享内部数据与列表不同,切片后的数组只是原数组的视图,共享内部数据
- 支持整数列表、整数数组和布尔数组等高级下标存取方法,使用列表作为下标得到的数组不和原始数组共享数据
- 多维数组与一维数组在数据存储上没有区别
# =========== slice =============
a = np.arange(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a[3:5] # a[3], a[4] 不包括 a[5]
a[:-1] # 排除最后一个数据
a[1:3] = 22,22 # [0, 22, 22, 3, 4, 5, 6, 7, 8, 9]
a[::2] # [0, 2, 4, 6, 8]
a[8::-2] # [8, 6, 4, 2, 0]
# =========== view =============
b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a1 = a[3:5]
a1[0] = 88
a1[0] == a[3] # True
b1 = b[3:5]
b1[0] = 88
b1[0] == b[3] # False
# =================== 整数列表,整数数组,布尔数组 ============
c = np.arange(10, 1, -1) # [10, 9, 8, 7, 6, 5, 4, 3, 2]
c[[1,1,1,1]] # [10, 10, 10, 10]
c[[0,1,2]] = 99,99,99 # [99, 99, 99, 7, 6, 5, 4, 3, 2]
# =================== 多维数组切片 ===============
d = np.fromfunction(lambda x,y: 10*x + y, (6,6)) # d = np.arange(0, 60, 10).reshape(-1, 1) + np.arange(0, 6)
"""
array([[ 0., 1., 2., 3., 4., 5.],
[ 10., 11., 12., 13., 14., 15.],
[ 20., 21., 22., 23., 24., 25.],
[ 30., 31., 32., 33., 34., 35.],
[ 40., 41., 42., 43., 44., 45.],
[ 50., 51., 52., 53., 54., 55.]])
"""
d[0, 3:5] # [3, 4]
d[4:, 4:] # [[44, 45], [54, 55]]
d[:, 2] # [2, 12, 22, 32, 42, 52]
d[2::2, ::2] # [[20, 22, 24], [40, 42, 44]]
2.4 slice 对象
在[]中可以使用以冒号隔开的两个或三个整数表示切片,但是单独生成切片对象时需要使用 slice(start, stop, step) 创建,省略的参数使用 None,如 slice(None, None, 2)。
NumPy 提供了 s_ 对象来创建下标切片
np.s_[::2, 2:] # (slice(None, None, 2), slice(2, None, None))
2.5 结构数组
NumPy 数组中可以存储自定义的结构,首先要通过 np.dtype 定义结构的类型。
personttype = np.dtype({
'names': ['name', 'age'],
'formats': ['S32', 'i']
}, align=True)
a = np.array([("Zhang",32),("Wang",24)], dtype=persontype)
类似于 c 语言中的 struct
struct person
{
char name[32];
int age;
}
3 ufunc 运算
ufunc :univeral function,它是一种能对数组中每个元组进行操作的函数。NumPy 内置的许多 ufunc 函数都是 c 语言级别实现的,因此它们的计算速度非常快。
3.1 四则运算
a = np.arange(5) # [0, 1, 2, 3, 4]
b = np.arange(5, 0, -1) # [4, 3, 2, 1, 0]
c1 = a + b # [5, 5, 5, 5, 5]
c2 = np.empty(5) # [0, 0, 0, 0, 0]
c2 = np.add(a, b, c2) # c2 == [5, 5, 5, 5, 5]
对于复杂的运算表达式,会因为产生大量的中间结果而降低程序的运算效率。比如 x=a*b+c
, 相当于:
t = a * b
x = t + c # 产生临时数组 t
del t
可以通过分解为两个表达式,来减少一次内存分配:
x = a * b
x += c
3.2 比较与布尔运算
python 中原生的布尔运算使用 and, or, not 等关键字,无法被重载!因此数组的布尔运算只能通过相应的 ufunc 函数进行,这些函数的名称都以 “logical_” 开头。
np.logical_and, np.logical_not, np.logical_or, np.logical_xor
np.any(), np.all()
3.3 自定义 ufunc 函数
通过 frompyfunc() 将一个计算单个元素的函数转换为 ufunc 函数,这样就可以方便地进行数组计算了。
def mfun(v, arg1, arg2):
return v*arg1 + arg2
a = np.arange(5) # [0, 1, 2, 3, 4]
u_mfun = np.frompyfunc(mfun, 3, 1)
u_mfun(a, 2, 2) # [2, 4, 6, 8, 10]
frompyfunc(func, nin, nout) : func 为原函数,nin 为 func 函数的参数个数,nout 为 func 函数的返回值个数