相关文章
简介
NumPy是Python中科学计算的基础包。它是一个Python库,提供多维数组对象,各种派生对象(如掩码数组和矩阵),以及用于数组快速操作的各种API,有包括数学、逻辑、形状操作、排序、选择、输入输出、离散傅立叶变换、基本线性代数,基本统计运算和随机模拟等等。
使用
我们仅需要简单的通过import numpy as np
就可以使用numpy了。
import numpy as np
np.eye(4)
#output
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])
为什么要用numpy?
如果我们希望两个列表对应项相加,则我们需要这样做,使用Python列表这样的代码是冗余的,而使用numpy则大大减少了代码的冗余。
#使用Python列表
a = [1,2,3,4]
b = [4,5,6,7,8]
out = []
for i,j in zip(a,b):
out.append(i+j)
print(out)
#output
[ 5, 7, 9, 11]
#使用numpy
import numpy as np
a = np.array([1,2,3,4])
b = np.array([4,5,6,7])
print(a+b)
#output
array([ 5, 7, 9, 11])
而且Python自带的list
在使用中也更加耗时。不难看出使用numpy将大大提高代码的运行速度。
#使用Python自带的数据类型
a = list(range(1000000))
%timeit sum(a)
#output
72.3 ms ± 2.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
#使用numpy
import numpy as np
a = list(range(1000000))
a_array = np.array(a)
%timeit np.sum(a_array)
#output
1.06 ms ± 33.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
为什么numpy运行速度会很快?
在Python list中,list存放的是每一项数据的索引,通过这个索引找到相应的内存地址,才能知道这个数值是多少。这就像是你想开一个party,但是你手里只有给个人的地址,你需要进入他们的房间。但numpy不同的是把所有的元素都组织到一个屋子,也就是一个数据结构中,同一个缓存中。所以这些元素在内存中是相邻的。他们都有相同的类型,numpy就能对这些数字进行非常快速的计算。因为不用做类型检查,它不需要申请获取这些元素拥有所有对象。
Beware of type coercion(提防强制数据类型)
numpy对传入的数据类型是有强管制的。这样做的好处就是numpy的行为比较可预测。缺点就是不灵活。因此如果你发现数据被改变时,不要惊讶
# 传入浮点,整型,字符串
arr = np.array([1.9,2,'asd','a'])
# output
array(['1.9', '2', 'asd', 'a'], dtype='<U32')
# 传入浮点,整型
arr = np.array([1,2.2,3,4.9])
# output
array([1. , 2.2, 3. , 4.9])
# 传入整数、浮点、负数
arr = np.array([1,2.2,3,4.5+5j])
# output
array([1. +0.j, 2.2+0.j, 3. +0.j, 4.5+5.j])
基础操作
现在让我们一起探讨一下numpy的基础操作,为了简便,一下代码涉及的一些数值,现已给出。
import numpy as np
a = np.array(['a','s','d','f'])
b = np.array([1,2,3,4])
c = np.array([4,5,6,7])
d = np.eye(4)
读取、修改
# 读取一维数组指定值
a[1]
# output
's'
#读取二维数组指定值
d[1,1]
# output
1.0
# 读取一行
d[1]
#output
array([0., 1., 0., 0.])
# 读取一列(之所以不用d[,2]是因为numpy是按行存储的)
d[:,2]
#output
array([0., 0., 1., 0.])
#修改二维数组
d[2,3] = 5
# output
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 5.],
[0., 0., 0., 1.]])
加、减、乘、除、
b + c
#output
array([ 5, 7, 9, 11])
b - c
#output
array([-3, -3, -3, -3])
b * c
#output
array([ 4, 10, 18, 28])
b / c
#output
array([0.25 , 0.4 , 0.5 , 0.57142857])
b ** c
#output
array([ 1, 32, 729, 16384], dtype=int32)
逻辑运算
In [4]: A
Out[4]: array([False, True, True, True, False, True, False])
In [5]: B
Out[5]: array([ True, True, False, True, False, False, False])
# 与
In [6]: np.logical_and(A,B)
Out[6]: array([False, True, False, True, False, False, False])
# 或
In [7]: np.logical_or(A,B)
Out[7]: array([ True, True, True, True, False, True, False])
# 非
In [8]: np.logical_not(A)
Out[8]: array([ True, False, False, False, True, False, True])
# 异或
In [9]: np.logical_xor(A,B)
Out[9]: array([ True, False, True, False, False, True, False])
shape(各维度长度)
shape返回一个元组,列出每个维度的数组长度。
a.shape
#output
(1,)
d.shape
#output
(4, 4)
ndim(维度)
a.ndim
#output
1
d.ndim
#output
2
dtype(类型)
我们可以通过dtype来查看numpy数组元素的数据类型。
a.dtype
#output
dtype('<U2')
d.dtype
#output
dtype('int32')
指定数据类型
由于numpy会强制数据类型,因此,如果想指定数据类型的话可以这样操作。
arr = np.array([1, 2.2, 3, 4.9],dtype = 'int32')
# output
array([1, 2, 3, 4])
# 如果遇到无法转换,则会报错
arr = np.array([1. , 2.2, 3. , 'a'],dtype = 'int32')
ValueError: invalid literal for int() with base 10: 'a'
修改数据类型
numpy数据类型转换需要调用方法astype()
,不能直接修改dtype
。调用astype
返回数据类型修改后的数据,但是源数据的类型不会变。
arr = np.array([1 , 2.2, 3, 4.9])
a = arr.astype(int)
# output
array([1, 2, 3, 4])
a = arr.astype(np.int64)
# output
array([1, 2, 3, 4], dtype=int64)
a = arr.astype(np.str)
# output
array(['1.0', '2.2', '3.0', '4.9'], dtype='<U32')
如果直接修改dtype则不会转换,而是直接切分,因此会导致数据出错。
arr = np.array([1 , 2.2, 3, 4.9])
arr.dtype
#oupput
dtype('float64')
#将原来每个64bits的浮点数 硬生生切成32bits的整型
arr.dtype = np.int32
#output
array([ 0, 1072693248, -1717986918, 1073846681, 0,
1074266112, -1717986918, 1075026329])
itemsize(最大元素的字节数)
a.itemsize
#output
4
b.itemsize
#output
4
nbytes(总元素字节数)
a.nbytes
# output
16
b.nbytes
# output
16
fill(填充)
以指定的元素填充数组。
a.fill('a')
#output
array(['a', 'a', 'a', 'a'], dtype='<U1')
reshape(重塑)
在不改变原数据的情况下,重新按指定形状生成数组
>>> a = np.arange(1,26)
>>> a
array([ 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])
# 元素不匹配时,则会报错
>>> a.reshape(5,6)
ValueError: cannot reshape array of size 25 into shape (5,6)
>>> a.reshape(5,5)
array([[ 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]])
如果只是单纯的想要n
列,对于行数并无要求,或者只是单纯的对行数有要求,对列并无要求,则可以使用负数来进行替代。这里的复数numpy
理解为未指定的。
In [17]: a.reshape(-1,2)
Out[17]:
array([[ 0, 10],
[20, 30],
[40, 50]])
In [18]: a.reshape(-2,2)
Out[18]:
array([[ 0, 10],
[20, 30],
[40, 50]])
In [19]: a.reshape(-2,1)
Out[19]:
array([[ 0],
[10],
[20],
[30],
[40],
[50]])
sum
sxis
对于sum
中有一个axis
的参数,假设默认为None
,现设a
是一个形状为(2,3,2)的数组。则
- axis=0(-3)时,则将a在第0维(倒数第3维)上求和,得到一个(3,2)的数组
- axis=1(-2)时,则将a在第1维(倒数第2维)上求和,得到一个(2,2)的数组
- axis=2(-1)时,则将a在第2维(倒数第1维)上求和,得到一个(2,3)的数组
a = np.arange(12).reshape(2,3,2)
# output
array([[[ 0, 1],
[ 2, 3],
[ 4, 5]],
[[ 6, 7],
[ 8, 9],
[10, 11]]])
a_0 = a.sum(axis = 0)
a_1 = a.sum(axis = 1)
a_2 = a.sum(axis = 2)
# output 分别为a_0, a_1, a_2
array([[ 6, 8],
[10, 12],
[14, 16]])
array([[ 6, 9],
[24, 27]])
array([[ 1, 5, 9],
[13, 17, 21]])
keepdims
在sum
函数中,参数keepdims
默认是<<no value>>
的。如果想让求和后的纬度保持不变,则可以通过设置keeodims
为True
来实现。
b = a.sum(axis = 2,keepdims = True)
# output
array([[[ 1],
[ 5],
[ 9]],
[[13],
[17],
[21]]])
b.shape
# output
(2, 3, 1)
initinal
通过initial
可以设置初始值。
a.sum()
# output
66
a.sum(initial = 10)
# output
76
其他相似函数
函数名 | 含义 |
---|---|
prod | 返回数组的或给定轴上数组元素的乘积。(空数组的乘积为1) |
min/max | 返回数组的最小(大)值或给定轴的最小(大)值。 |
argmin/argmin | 返回沿轴的最小(大)值的索引。(无initial和keepdims) |
max | 返回数组的最大值或给定轴的最大值。 |
ptp | 沿轴的值范围(max-min, 无initial) |
mean | 返回数组的均值或给定轴的均值。 |
std/var | 计算沿指定轴的标准偏差(方差)。(其中自由度ddof=0为总体标准差(总体方差),ddof=1为样本标准差(样本方差)。无initial) |
any/all | any是元组元素只要有一个为真才为真,all是所有元素为真才为真。(无initial) |
flags(返回内存信息)
flags
返回ndarray
对象的内存信息,包含以下属性:
属性 | 描述 |
---|---|
C_CONTIGUOUS | 数据在一个单一的C风格的连续字段中 |
F_CONTIGUOUS | 数据在一个单一的Fortran风格的连续字段中 |
OWNDATA | 数据拥有它所使用的内存(True)或从另一个对象中借用它(False) |
WRITEABLE | 数据区域可以被写入(True),设置为False时为只读。 |
ALIGNED | 数据和所有元素都适当对其到硬件上(True为对齐) |
WRITEBACKIFCOPY | UPDATEIFCOPY 已弃用,由 WRITEBACKIFCOPY 取代; |
UPDATEIFCOPY | 这个数组是其它数组的一个副本,当这个数组被释放时,原数组的内容将被更新 |
C_CONTIGUOUS和F_CONTIGUOUS
C_CONTIGUOUS
和F_CONTIGUOUS
分别表示C风格和Fortran风格。这是两种不同的编程语言,在C语言中数组的存储风格是按行存储,而在Fortran语言中则是按列存储。==numpy数组是按行存储。==既然如此那为什么又设计到了两种不同风格的判别呢?
- 一维数组,
对于一维数组,不管按行存储还是按列存储内存中的顺序都是不变的,因此既符合C_CONTIGUOUS
又符合F_CONTIGUOUS
。
数组a
的存储方式如下图所示:
a = np.array([1,2,3,4])
a.flags
# output
C_CONTIGUOUS : True
F_CONTIGUOUS : True
···
- 二维数组
当然,对于二维数组就不一样了,我们可以看到数组b
在内存中是按C风格(行优先)存储的。所以符合C_CONTIGUOUS
不符合F_CONTIGUOUS
。内存中的存储方式如下图左,现在我们将b
转置赋给c
。如果为c
单独开辟一段内存是不划算的,numpy做的是给你一个相同内存缓冲区的一个视图,尽量不创建一个副本。对于c
来说,c
的C风格相当于是b
的Fortran风格。因此只需要在b
上创建一个视图,并标为Fortran风格。便可作为c
。
b = np.array([[1,2,3,4],[2,5,1,4]])
# output
C_CONTIGUOUS : True
F_CONTIGUOUS : False
···
c = b.T
# output
In [7]: c.flags
Out[7]:
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA
因为b
是直接创建的,因此b
上的数据拥有所属的内存,而c
相当于是b
上的一个视图,因此数据没有所属的内存
b.flags
# outpit
···
OWNDATA : True
···
c.flags
# output
OWNDATA : False
类x数组
函数 | 解释 |
---|---|
empty_like | 返回形状和类型与给定数组相同的新数组。(值为内存中的原垃圾值) |
ones_like | 返回形状与类型与给定数组相同的数组。(1填充) |
zeros_like | 返回形状与类型与给定数组相同的数组。(0填充) |
a = np.range(36).reshape(6,6)
np.empty_like(a)
np.ones_like(a)
np.zeros_like(a)
# output
array([[ 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0],
[-2, 0, -2, -1, 0, 0]])
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],
[1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1]])
array([[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]])
where
where(条件,[x,y])
返回根据条件从x或y中选择的元素。
举例
a = np.array([[1,2],[3,4]])
b = np.array([[9,8],[7,6]])
mask = np.array([[1,0],[0,1]],dtype = bool)
np.where(mask,a,b)
# output
array([[1, 8],
[7, 4]])
索引
现给出两个数组,下面所有操作均在其上。
>>> import numpy as np
>>>#负索引 -6 -5 -4 -3 -2 -1
···#正索引 0, 1, 2, 3, 4, 5
··· a = np.array([10, 5, 7, 9, 8, 4])
>>> b = np.array([[ 1, 2, 9, 4, 3],
[ 0, 5, 3, 5, 1],
[ 8, 3, 2, 4, 7]])
切片
每当做切片时,numpy做的是给你一个相同内存缓冲区的一个试图。所以numpy大多数情况下尽量不创建一个副本,它不赋值数据,只是指向内存中的同一个位置,所以这意味着对一个巨大数组做切片操作很廉价。除了一些关于形状和维度数量的元数据外。
一维切片
numpy数组切片和python基本上是一样的。
# 正索引从零开始,len(a)-1结束
# 负索引从-len(a)开始,-1结束
>>> a
array([10, 5, 7, 9, 8, 4])
>>> a[1:3]
array([5, 7])
>>> a[:3]
array([10, 5, 7])
>>> a[-4:]
array([7, 9, 8, 4])
>>> a[:-3]
array([10, 5, 7])
# 只能正向索引,否则返回为空
# 无法从倒数第4个到正数第2个
>>> a[-4:2]
array([], dtype=int32)
# 可以从正数第2个到倒数第1个
>>> a[2:-1]
array([7, 9, 8])
#每n间隔n-1个取一个
>>> a[::3]
array([10, 9])
>>> a[::2]
array([10, 7, 8])
# 间隔反取
>>> a[::-2]
array([4, 9, 5])
# 区间跳转 (开始:结束:间隔)
>>> b = np.arange(10)
>>> b
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b[1:7:2]
array([1, 3, 5])
>>> b[7:1:-2]
array([7, 5, 3])
多维切片
以二维数组为例,先在行切片,找到指定行后再列切片。你可以简单理解为在不同维度切片后重叠区域。
# 灰色区域
>>> b[1,3:5]
array([5, 1])
# 橙色区域
>>> b[0:2,0:2]
array([[1, 2],
[0, 5]])
# 蓝色区域
>>> b[:,2]
array([9, 3, 2])
# 间隔选取
>>> b[::2,1::2]
array([[2, 4],
[3, 4]])
花式索引
虽然切片已经能够满足大多数的情况,但如果让你不定长去索引numpy中的几个数组呢?你会怎么做?这里将介绍花式索引。
通过位置索引
在numpy的数组可以通过列表批量传入索引值来进行索引。
a = np.arange(8)
indices = [1,2,-2]
b = a[indices]
# output
array([1, 2, 6])
# 也可以直接传入行和列(对应位置组成点)
a = np.arange(36).reshape([6,6])
# putout
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, 32, 33, 34, 35]])
b = a[[0,1,2,3,4],[1,2,3,4,5]]
# output
array([ 1, 8, 15, 22, 29])
布尔索引
我们也可以通过布尔的真假值来进行索引。
a = np.arange(10)
b = a < 5
# output 返回的是一个bool数组
array([True, True, True, True, True, False, False, False, False, False])
a[a < 5] = 0
print(a)
# output
array([0, 0, 0, 0, 0, 5, 6, 7, 8, 9])
# 注意这里的bool个数应该和a对等
c = np.array([0,1,0,0,0,1,0,1,0,1],dtype = bool)
a[c]
# output
array([1, 5, 7, 9])
# 值得注意的是,只有0才为False
In [1]: c = np.array([1,2,3,4,0,0,-5],dtype = bool)
Out[2]: array([ True, True, True, True, False, False, True])
- 想一下能不能用
a > 3 and a < 5
来找出数组a中小于5大于3的元素呢?我可以看一下
a > 3 and a < 5
# output
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
具有多个元素的数组的真值是不明确的。oh,原来数组a
元素中判断的结果有真有假,而and
左右两边只能是确定的,要么是真要么是假,当两边都为真的时候才为真。那么错误提示中的建议使用any()
和all()
又是什么呢?
# any()意思是只要有一个为真,这个数组就为真
(a < 5).any()
# output
True
#all()意思是所有为真才为真
(a < 9).all()
(a < 10).all()
#output
False True
嗯哼,好像并没有解决问题。这里我们引入一个几种位操作。
操作符 | 含义 |
---|---|
& | and |
丨 | or |
~ | not |
^ | xor |
因此我们可以通过&
来实现对数组实现按位运算,这就像numpy中的“+”,“-”一样。
# 之所以使用括号是因为位运算符优先级高于比较运算符
(a > 3) & (a < 5)
# output
array([False, False, False, False, True, False, False, False, False, False])
# 因此这样我们就可以进行bool索引
a[(a > 3) & (a < 5)]
# output
array([4])
- 如何得到满足条件的索引值?
a = np.array([[1,2,3,4],[2,5,9,7],[5,2,3,4]])
np.nonzero(a < 3)
# output 既分别是(0,0),(0,1),(1,0),(2,1)四个点
(array([0, 0, 1, 2], dtype=int64), array([0, 1, 0, 1], dtype=int64))
#上面的输出方式如果看不习惯我们可以反转一下就比较清楚了
np.transpose(np.nonzero(a < 3))
# output
array([[0, 0],
[0, 1],
[1, 0],
[2, 1]], dtype=int64)
- 下面我们通过一个联系来进一步熟悉花式索引。
【1.】输出下列表格中各颜色区域的元素。
a = np.arange(36).reshape([6,6])
# 黄色区域
y = a[[0,1,2,3,4],[1,2,3,4,5]]
# 红色区域
mask = np.array([1,0,1,0,0,1],dtype = bool)
r = a[mask,2]
# 蓝色区域
b = a[3:,[0,2,5]]
【2.】输出上表中能被3整除的数
这里需要说明的是,在通过以对应位置输出时,由于引入Nan(表空缺,浮点型),所以原始数据不可避免的被强制改变成浮点型。
# 以一维数组形式输出
mask = a % 3 ==0
a[mask]
# output
array([ 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33])
# 以对应位置输出
# 方法一,使用emoty_like函数
b = np.empty_like(a,dtype = np.float)
b.fill(np.nan)
b[mask] = a[mask]
#output
array([[ 0., nan, nan, 3., nan, nan],
[ 6., nan, nan, 9., nan, nan],
[12., nan, nan, 15., nan, nan],
[18., nan, nan, 21., nan, nan],
[24., nan, nan, 27., nan, nan],
[30., nan, nan, 33., nan, nan]])
# 方法二,使用where函数
mask = a % 3 == 0
np.where(mask,a,np.nan)
# output
array([[ 0., nan, nan, 3., nan, nan],
[ 6., nan, nan, 9., nan, nan],
[12., nan, nan, 15., nan, nan],
[18., nan, nan, 21., nan, nan],
[24., nan, nan, 27., nan, nan],
[30., nan, nan, 33., nan, nan]])
切片与花式索引
值得注意的是花式索引给出的是一个 副本 而切片给出的是一个 视图
a = np.arange(10)
num1 = a[(a > 2) & (a < 8)]
num2 = a[3,8]
# output num1和num2均为
array([3, 4, 5, 6, 7])
# 修改num1
num1[2] = 9
# output num1改变,a未改变
array([3, 4, 9, 6, 7])
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 修改num2
num2[2] = 9
# output num2和a均改变
array([3, 4, 9, 6, 7])
array([0, 1, 2, 3, 4, 9, 6, 7, 8, 9])
计算规则
- Rule 1:首先检查多维数组之间的操作是否正确匹配形状。
- Rule 2:数学运算符将元素逐个应用于值(+,-,*,/log,exp…)。
- Rule 3:归约运算适用于整个数组,除非指定了坐标轴(mean,std,skew,sum…)。
- Rule 4:除非明确忽略,否则缺失值会传播(nanmean,nansum,…)。
这里我们主要详细讲一下规则一
每次你在数组之间进行操作时,numpy做的第一件事就是检查它们的形状是否匹配,这称为“广播规则”。广播的初衷就是要节约时间和不必要的空间,没有必要分配更多的内存,只需要重复相同的数据。
这里举几个被认为是匹配形状的例子。
形状完全一致
在最简单的情况下,如下两个形状(3,2)
的数组,假设我们把它们加在一起,形状显然匹配,因为它们是相同的形状。得到的结果是数组元素对应位置逐个相加。
形状不一致
另一种是兼容的数组,对于左边a
形状为(3,2)
,右边b
形状(2,)
的数组,则numpy会认为从 右侧对齐。然后广播数组b
依次与a
的每一行的元素进行操作。此时numpy并不需要额外的内存来复制b
将他们达到完全相同的形状。
# (3,2)、 (2,) 右侧对齐为(2) 广播3次
a = np.arange(1,7).reshape(3,2)
b = np.array([5,6])
# output a+b
array([[ 6, 8],
[ 8, 10],
[10, 12]])
# (2,3,2,2)、 (2,2) 右侧对齐为(2,2) 广播2*3次
a = np.arange(24).reshape(2,3,2,2)
b = np.array([[1,1],[1,1]])
# output a+b
array([[[[ 1, 2],
[ 3, 4]],
[[ 5, 6],
[ 7, 8]],
[[ 9, 10],
[11, 12]]],
[[[13, 14],
[15, 16]],
[[17, 18],
[19, 20]],
[[21, 22],
[23, 24]]]])
转载:https://blog.csdn.net/qq_36733722/article/details/102710405