小言_互联网的博客

numpy——基础篇

525人阅读  评论(0)

相关文章

numpy——进阶篇



简介

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>>的。如果想让求和后的纬度保持不变,则可以通过设置keeodimsTrue来实现。

 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_CONTIGUOUSF_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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场