Python数组计算Numpy学习

  • Numpy是高性能科学计算和数据分析的基础包。它是Pandas等其他各种工具的基础。
  • Numpy的主要功能:
    • ndarray,一个多维数组结构,高效且节省空间
    • 无需循环对整租数据进行快速运算的数学函数
    • 线性代数、随机数生成和傅里叶变换功能
  • 安装方法:pip install numpy
  • 引用方式:import numpy as np
import numpy as np

1 为什么用numpy?

  • 已知若干家跨国公式市值,要转换成人民币。
  • 已知购物车中没见商品的价格与商品件数,求总金额。
import random
a = [random.uniform(100.0, 200.0) for i in range(50)] #假设已知50家公司市值
x = 6.8 #1美元=6.8元人民币
# 转换成人民币,列表——循环
b = []
for each in a:
    b.append(each*x)
# 转换成人民币,数组——“*”
a = np.array(a)
a*x
array([ 730.78383062, 1266.95759982, 1202.64841256,  882.12355645,
       1255.37319355,  927.69530679, 1089.31578981,  851.26580847,
        680.30306304,  966.82340851,  969.51576098, 1095.96837026,
       1101.48122346, 1010.03673821, 1073.41921089,  700.4467694 ,
       1097.75806355,  862.10937297,  903.10356786, 1057.29028114,
        822.0017179 ,  922.88508406,  928.66456531,  815.24430559,
       1094.17646796, 1303.49318904, 1258.45713279, 1009.19231422,
        837.66719864, 1352.56495047, 1102.87391356, 1021.19972831,
        828.94449599,  734.1796759 ,  910.74805825,  833.52171339,
       1006.65962949, 1153.82177692,  840.12565375,  883.89012906,
        911.18972367, 1163.87157797, 1297.27690181,  866.4149191 ,
        998.73568422,  839.90252464, 1349.68761075, 1048.43649518,
        826.44027257, 1196.19842047])
a = [random.uniform(10.0,20.0) for i in range(50)]
b = [random.randint(1,10) for i in range(50)]
a = np.array(a) #s转换成数组
b = np.array(b)
(a * b).sum()# a×b并求和
4563.797452684265
my_arr = np.arange(10000)
%time for _ in range(10): my_arr = my_arr * 2
Wall time: 442 µs
my_list = list(range(10000))
%time for _ in range(10): my_list = my_list * 2
Wall time: 134 ms

「numpy 数组」效率是「列表」效率的 51.5 (154/2.99) 倍左右。如果元素全是数值型变量 (numerical variable),那么 numpy 数组明显是个很好的数据结构。

2 Numpy基础

2.1 创建ndarray:np.arrray(array_like)

  • 数组与列表的区别:
    • 数组对象内的元素类型必须相同
    • 数组大小不可改变

数组 (array) 是相同类型的元素 (element) 的集合所组成数据结构 (data structure)。numpy 数组中的元素用的最多是「数值型」元素,平时我们说的一维、二维、三维数组长下面这个样子 (对应着线、面、体)。四维数组很难被可视化。

在这里插入图片描述

注意一个关键字 axis,中文叫「轴」,一个数组是多少维度就有多少根轴。由于 Python 计数都是从 0 开始的。

  • 第 1 维度 = axis 0
  • 第 2 维度 = axis 1
  • 第 3 维度 = axis 2

在这里插入图片描述

分析上图各个数组的在不同维度上的元素:

  • 一维数组:轴 0 有 3 个元素
  • 二维数组:轴 0 有 2 个元素,轴 1 有 3 个元素
  • 三维数组:轴 0 有 2 个元素 (2 块),轴 1 有 2 个元素,轴 2 有 3 个元素
  • 四维数组:轴 0 有 2 个元素 (2 块),轴 1 有 2 个元素 (2 块),轴 2 有 2 个元素,轴 3 有 3 个元素

带着上面这个对轴的认识,接下来我们用代码来创建 numpy 数组,有三种方式:

  • 按步就班的 np.array() 用在列表和元组上
  • 定隔定点的 np.arange() 和 np.linspace()
  • 一步登天的 np.ones(), np.zeros(), np.eye() 和 np.random.random()

按部就班法

给了「列表」和「元组」原材料,用 np.array() 包装一下便得到 numpy 数组。

l = [1,2,3,4,5] # 给定一个列表
np.array(l) #用np.array()包装一下
array([1, 2, 3, 4, 5])
t = (3.5, 5, 2, 8, 4.2) # 给定一个元组
np.array(t) # 用np.array()包装一下
array([3.5, 5. , 2. , 8. , 4.2])
np.array([2, 4, 6, 8]) # 2步并一步
array([2, 4, 6, 8])
a = np.array(range(10)) # 用range()函数生成一个迭代器,也可以转换成数组
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.array(range(10), dtype='float') # np.array()函数还有其他一些参数,比如dtype定义数组的类型
array([0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])

定点隔离法

更常见的两种创建 numpy 数组方法:

  • 定隔的 arange:固定元素大小间隔

  • 定点的 linspace:固定元素个数

  • arange():是range的numpy版,支持浮点数

  • linspace():类似arange(),第三个参数为数组长度

arange

函数 arange 的参数为起点 , 终点 , 间隔

arange(start , stop , step)

其中 stop 必须要有,start 和 step 没有的话默认为 1。对着这个规则看看上面各种情况的输出。

print( np.arange(8) ) # 创建一个0~7,步长是1的数组
print( np.arange(2,8) ) # 创建一个2~7,步长是1的数组
print( np.arange(2,8,2)) # 创建一个2~7,步长是2的数组
[0 1 2 3 4 5 6 7]
[2 3 4 5 6 7]
[2 4 6]

:用函数 print 打印 numpy 数组就没有 array() 的字样了,只用其内容,而且元素之间的「逗号」也没有了。

np.arange(10)#与range()功能类似
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
np.arange(2,10)
array([2, 3, 4, 5, 6, 7, 8, 9])
np.arange(2,10,3)
array([2, 5, 8])
np.arange(2,10,0.3)#np.arange()的步长可以是小数,但是range()的步长只能是整数
array([2. , 2.3, 2.6, 2.9, 3.2, 3.5, 3.8, 4.1, 4.4, 4.7, 5. , 5.3, 5.6,
       5.9, 6.2, 6.5, 6.8, 7.1, 7.4, 7.7, 8. , 8.3, 8.6, 8.9, 9.2, 9.5,
       9.8])
linspace

函数 linspace 的参数为起点 , 终点 , 点数

linspace (start , stop , num)

其中 start 和 stop 必须要有,num 没有的话默认为 50。对着这个规则看看上面各种情况的输出。

print( np.linspace(2,6,3) )
print( np.linspace(3,8,11) )
[2. 4. 6.]
[3.  3.5 4.  4.5 5.  5.5 6.  6.5 7.  7.5 8. ]
np.linspace(0,10,10)#第3个参数是分成多少份,0~10包含首位,其实应该是11个数字,平均分成十份,所以每一份是1.111111
array([ 0.        ,  1.11111111,  2.22222222,  3.33333333,  4.44444444,
        5.55555556,  6.66666667,  7.77777778,  8.88888889, 10.        ])
np.linspace(0,10,11)
# linspace()是前后都包括了
# 分成了11份(包括0)
# 常用于画函数图像
array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.])
import matplotlib.pyplot as plt
x = np.linspace(-10,10,10000)
y = x**2
plt.plot(x,y)
plt.show()

在这里插入图片描述

一步登天法

  • zeros():指定形状和dtype创建全0数组
  • ones():指定形状和dtype创建全1数组
  • empty():指定形状和dtype创建全空数组(随机值)
  • eye():根据指定边长和dtype创建单位矩阵
  • random():指定形状的随机数组
# 创建10个全是0的数组
np.array([0]*10)
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
np.zeros(10) # 创建全0数组返回浮点数
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
a = np.zeros(10) 
a.dtype
dtype('float64')
np.zeros(10, dtype='int')# 增加数据类型参数,创建整数型数组
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
np.zeros((3,2,3))# 传入一个元组,创建多维的全0数组
array([[[0., 0., 0.],
        [0., 0., 0.]],

       [[0., 0., 0.],
        [0., 0., 0.]],

       [[0., 0., 0.],
        [0., 0., 0.]]])
np.zeros([3,2,3], dtype='int') #传入一个列表,也可以创建多维数组
array([[[0, 0, 0],
        [0, 0, 0]],

       [[0, 0, 0],
        [0, 0, 0]],

       [[0, 0, 0],
        [0, 0, 0]]])
a = np.ones(10)
a
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
a = np.empty(10)#创建一个随机的空数组,结果不确定是多少,只是在内存中随便找出来一些数。
a
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])

:创建数组是先在内存里面申请一定数量的位置,然后把值改成相应的数据,而np.empty()只有第一个步骤。

np.eye(10)# 生成单位矩阵
array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]])

:对于函数 eye(),它的参数就是一个标量,控制矩阵的行数或列数。此外还可以设定 eye() 里面的参数 k:

  • 默认设置 k = 0 代表 1 落在对角线上
  • k = 1 代表 1 落在对角线右上方,2表示再往上,以此类推…
  • k = -1 代表 1 落在对角线左下方,-2表示再往下,以此类推…
np.eye(10,k=2) 
# 创建一个1在对角线上方2行的矩阵。
# k是命名关键字参数,传入的时候必须带上“k=”。
array([[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])
np.random.random([5,5])
array([[0.72054583, 0.67448027, 0.66421402, 0.36378909, 0.13223313],
       [0.27456813, 0.38267353, 0.59353541, 0.08862996, 0.0995975 ],
       [0.86223106, 0.07478303, 0.55406452, 0.2625297 , 0.32851007],
       [0.06433207, 0.4936195 , 0.40713614, 0.98245258, 0.81350439],
       [0.48143435, 0.13835127, 0.53523663, 0.18398142, 0.64982   ]])

2.2 数组的性质

Python 里面「万物皆对象」,numpy 数组也不例外,那么我们来看看数组有什么属性 (attributes) 和方法 (methods)。

一维数组

arr = np.array([3.5, 5, 2, 8, 4.2]) # 按步就班的用np.array()生成一个列表
arr
array([3.5, 5. , 2. , 8. , 4.2])
dir(arr)
['T',
 '__abs__',
 '__add__',
 '__and__',
 '__array__',
 '__array_finalize__',
 '__array_function__',
 '__array_interface__',
 '__array_prepare__',
 '__array_priority__',
 '__array_struct__',
 '__array_ufunc__',
 '__array_wrap__',
 '__bool__',
 '__class__',
 '__complex__',
 '__contains__',
 '__copy__',
 '__deepcopy__',
 '__delattr__',
 '__delitem__',
 '__dir__',
 '__divmod__',
 '__doc__',
 '__eq__',
 '__float__',
 '__floordiv__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__gt__',
 '__hash__',
 '__iadd__',
 '__iand__',
 '__ifloordiv__',
 '__ilshift__',
 '__imatmul__',
 '__imod__',
 '__imul__',
 '__index__',
 '__init__',
 '__init_subclass__',
 '__int__',
 '__invert__',
 '__ior__',
 '__ipow__',
 '__irshift__',
 '__isub__',
 '__iter__',
 '__itruediv__',
 '__ixor__',
 '__le__',
 '__len__',
 '__lshift__',
 '__lt__',
 '__matmul__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__or__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rand__',
 '__rdivmod__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rfloordiv__',
 '__rlshift__',
 '__rmatmul__',
 '__rmod__',
 '__rmul__',
 '__ror__',
 '__rpow__',
 '__rrshift__',
 '__rshift__',
 '__rsub__',
 '__rtruediv__',
 '__rxor__',
 '__setattr__',
 '__setitem__',
 '__setstate__',
 '__sizeof__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '__xor__',
 'all',
 'any',
 'argmax',
 'argmin',
 'argpartition',
 'argsort',
 'astype',
 'base',
 'byteswap',
 'choose',
 'clip',
 'compress',
 'conj',
 'conjugate',
 'copy',
 'ctypes',
 'cumprod',
 'cumsum',
 'data',
 'diagonal',
 'dot',
 'dtype',
 'dump',
 'dumps',
 'fill',
 'flags',
 'flat',
 'flatten',
 'getfield',
 'imag',
 'item',
 'itemset',
 'itemsize',
 'max',
 'mean',
 'min',
 'nbytes',
 'ndim',
 'newbyteorder',
 'nonzero',
 'partition',
 'prod',
 'ptp',
 'put',
 'ravel',
 'real',
 'repeat',
 'reshape',
 'resize',
 'round',
 'searchsorted',
 'setfield',
 'setflags',
 'shape',
 'size',
 'sort',
 'squeeze',
 'std',
 'strides',
 'sum',
 'swapaxes',
 'take',
 'tobytes',
 'tofile',
 'tolist',
 'tostring',
 'trace',
 'transpose',
 'var',
 'view']
print( 'The type is', type(arr) ) # 数组的数据类型
print( 'The dimension is', arr.ndim ) # 数组有几个维度
print( 'The length of array is', len(arr) ) # 数组的长度
print( 'The number of elements is', arr.size ) # 数组的元素个数
print( 'The shape of array is', arr.shape ) #  数组的形状
print( 'The stride of array is', arr.strides ) # 即在某一维度下为了获取到下一个元素需要「跨过」的字节数
print( 'The type of elements is', arr.dtype ) # 数组元素类型
The type is <class 'numpy.ndarray'>
The dimension is 1
The length of array is 5
The number of elements is 5
The shape of array is (5,)
The stride of array is (8,)
The type of elements is float64
  • type:数组类型,当然是 numpy.ndarray
  • ndim:维度个数是 1
  • len():数组长度为 5 (注意这个说法只对一维数组有意义)
  • size:数组元素个数为 5
  • shape:数组形状,即每个维度的元素个数 (用元组来表示),只有一维,元素个数为 5,写成元组形式是 (5,)
  • strides:跨度,即在某一维度下为了获取到下一个元素需要「跨过」的字节数 (用元组来表示),float64 是 8 个字节数 (bytes),因此跨度为 8
  • dtype:数组元素类型,是双精度浮点 (注意和 type 区分)
数组元素的数据类型
  • 布尔型:bool_
  • 整型:int_ int8 int16 int32 int54
  • 无符号整数:uint8 uint16 uint32 uint54
  • 浮点型:float_ float16 float32 float64
  • 复数型:complex_ complex64 complex128
a = np.array(range(10))
a.dtype# 返回数据类型
dtype('int32')
数组常用属性和方法
  • T 数组的转置(一维数组不能转置)
  • size 数组元素的个数
  • ndim 数组的维数
  • shape 数组的维度大小(返回元组形式)
  • dtype 数组元素的数据类型
a.size# 返回元素个数
10
a = np.array([[1,2,3],[4,5,6]])
a
array([[1, 2, 3],
       [4, 5, 6]])
a.size
6
a.shape
(2, 3)
a = np.array([[[1,2,3],[4,5,6]],[[1,2,3],[4,5,6]]])
a.shape
(2, 2, 3)
a = np.array([[1,2,3],[4,5,6]])
a.T
array([[1, 4],
       [2, 5],
       [3, 6]])
_.T
array([[1, 2, 3],
       [4, 5, 6]])
a = np.array(range(10))
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a.T
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

2.3 数组的存载

数组的「保存」和「加载」,我知道它们没什么技术含量,但是很重要。假设你已经训练完一个深度神经网络,该网络就是用无数参数来表示的。比如权重都是 numpy 数组,为了下次不用训练而重复使用,将其保存成 .npy 格式或者 .csv 格式是非常重要的。

numpy 自身的 .npy 格式

用 np.save 函数将 numpy 数组保存为 .npy 格式。

np.save( “文件名”,数组 )
arr_disk = np.arange(8)
np.save("arr_disk", arr_disk)
arr_disk
array([0, 1, 2, 3, 4, 5, 6, 7])

arr_disk.npy 保存在 Jupyter Notebook 所在的根目录下。要加载它也很简单,用 np.load( “文件名” ) 即可

np.load('arr_disk.npy')
array([0, 1, 2, 3, 4, 5, 6, 7])

文本 .txt 格式

用 np.savetxt 函数将 numpy 数组保存为 .txt 格式。

np.savetxt( “文件名.txt”,数组 )
arr_text = np.array([[1., 2., 3.], [4., 5., 6.]])
np.savetxt("arr_from_text.txt", arr_text)

arr_from_text.txt 保存在 Jupyter Notebook 所在的根目录下,打开看里面确实存储着 [[1,2,3], [4,5,6]]

np.loadtxt( “文件名.txt” ) 即可加载该文件

np.loadtxt("arr_from_text.txt")
array([[1., 2., 3.],
       [4., 5., 6.]])

文本 .csv 格式

使用np.genfromtxt函数即可读取csv文件。

np.genfromtxt( "文件名",分隔符 )
np.genfromtxt("arr_from_csv.csv",  delimiter=",")
array([[1., 2., 3.],
       [4., 5., 6.]])

2.4 数组索引

获取数组是通过索引 (indexing) 和切片 (slicing) 来完成的,

  • 切片是获取一段特定位置的元素,切片写法是 arr[start : stop : step]
  • 索引是获取一个特定位置的元素,索引写法是 arr[index]

正规索引

虽然切片操作可以由多次索引操作替代,但两者最大的区别在于

  • 切片得到的是原数组的一个视图 (view) ,修改切片中的内容会改变原数组
  • 索引得到的是原数组的一个复制 (copy),修改索引中的内容不会改变原数组

一维数组

# 一维数组索引
a = np.arange(10)
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a[0]
0
# 一维数组切片
a = np.arange(15)
a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
a[:4]
array([0, 1, 2, 3])
a[4:]
array([ 4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
n1 = a[0] # 索引得到的是一个复制
n1 = 20 # 改变这个n1的值,不会影响a
a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
b = np.arange(15)
b
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
n2 = b[:4] #相当于做了一个视图
n2[0] = 20 # 改变视图中的其他一个元素,将会改变原来的b
b
array([20,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])

在这里插入图片描述

c = list(range(15)) # 如果创建一个列表
n3 = c[0] # 对列表进行索引
n3 = 20 # 改变索引,不会影响原来的列表
c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
n4 = c[:1] # 对列表进行切片
n4[0] = 20 # 改变切片中的一个元素,同样也不会影响原来的列表
c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

二位数组

索引

情况一:用 arr2d[2] 来索引第三行,更严格的说法是索引「轴 0」上的第三个元素。

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
arr2d[2]
array([7, 8, 9])

情况二:用 arr2d[0][2] 来索引第一行第三列

arr2d[0][2]
3

情况三:用 arr2d[0, 2] 索引第一行第三列

arr2d[0,2]
3
切片

情况一:用 arr2d[:2] 切片前两行,更严格的说法是索引「轴 0」上的前两个元素。

arr2d[:2] 
array([[1, 2, 3],
       [4, 5, 6]])

情况二:用 arr2d[:, [0,2]] 切片第一列和第三列

arr2d[:,[0,2]] 
array([[1, 3],
       [4, 6],
       [7, 9]])

情况三:用 arr2d[1, :2] 切片第二行的前两个元素

arr2d[1, :2]
array([4, 5])

情况四:用 arr2d[:2, 2] 切片第三列的前两个元素

arr2d[:2, 2]
array([3, 6])
# 二维数组切片
a = np.arange(15).reshape((3,5))
a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
a[0:2, 0:2]
array([[0, 1],
       [5, 6]])
a[::2,1::2]
array([[ 1,  3],
       [11, 13]])
# 如果想要创建一个新的数组,使用copy()函数。
a = np.arange(10)
e = a[:4].copy()
e[0] = 20
e
array([20,  1,  2,  3])
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

布尔索引

布尔索引,就是用一个由布尔 (boolean) 类型值组成的数组来选择元素的方法。

  • 假设我们有阿里巴巴 (BABA),脸书 (FB) 和京东 (JD) 的
    • 股票代码 code 数组
    • 股票价格 price 数组:每行记录一天开盘,最高和收盘价格。
code = np.array(['BABA', 'FB', 'JD', 'BABA', 'JD', 'FB'])
price = np.array([[170,177,169],[150,159,153],
                  [24,27,26],[165,170,167],
                  [22,23,20],[155,116,157]])
price
array([[170, 177, 169],
       [150, 159, 153],
       [ 24,  27,  26],
       [165, 170, 167],
       [ 22,  23,  20],
       [155, 116, 157]])

假设我们想找出 BABA 对应的股价,首先找到 code 里面是 ‘BABA’ 对应的索引 (布尔索引),即一个值为 True 和 False 的布尔数组。

# 用该索引可以获取 BABA 的股价:
code == 'BABA'
price[ code == 'BABA' ]
array([[170, 177, 169],
       [165, 170, 167]])
# 用该索引还可以获取 BABA 的最高和收盘价格:
price[ code == 'BABA', 1: ]
array([[177, 169],
       [170, 167]])
# 同时获取 JD 和 FB 的股价:
price[ (code == 'FB')|(code == 'JD') ] # 一定要用“&”“|”,不能用“and”“or”
array([[150, 159, 153],
       [ 24,  27,  26],
       [ 22,  23,  20],
       [155, 116, 157]])
a = [random.randint(0, 10) for i in range(20)]
a
[9, 8, 2, 1, 4, 4, 0, 10, 6, 6, 3, 9, 3, 4, 4, 9, 2, 0, 1, 1]
# 找出一个数组中所有大于5的数
list(filter(lambda x : x > 5, a))
[9, 8, 10, 6, 6, 9, 9]
a = np.array(a)
a[a > 5]
array([ 9,  8, 10,  6,  6,  9,  9])
# 找出一个数组中所有大于5的偶数
a[(a > 5) & (a % 2 == 0)]
array([ 8, 10,  6,  6])

花式索引

a = np.arange(20)
a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19])
a[[1,3,5,7]]
array([1, 3, 5, 7])
a = np.arange(20).reshape((4,5))
a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
a[0, 2:4]
array([2, 3])
a[0, a[0]>2]
array([3, 4])
a[[1,3],[1,3]]# 取的是[1,1]和[3,3]位置上的值
array([ 6, 18])
a[1::2,1::2]
array([[ 6,  8],
       [16, 18]])
a[[1,3],:][:,[1,3]]
array([[ 6,  8],
       [16, 18]])

2.5 ndarray-批量运算

  • 数组和标量之间的运算
    • a+1
    • a*3
    • 1//a
    • a**5
    • a>5
  • 同样大小数组之间的运算
    • a+b
    • a/b
    • a**b
    • a%b
    • a==b
a = np.arange(10)
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a + 1
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])
a * 3
array([ 0,  3,  6,  9, 12, 15, 18, 21, 24, 27])
3 / a
c:\users\ibm\appdata\local\programs\python\python37\lib\site-packages\ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
  """Entry point for launching an IPython kernel.





array([       inf, 3.        , 1.5       , 1.        , 0.75      ,
       0.6       , 0.5       , 0.42857143, 0.375     , 0.33333333])
b = np.arange(10,20)
b
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
a + b
array([10, 12, 14, 16, 18, 20, 22, 24, 26, 28])
a * b
array([  0,  11,  24,  39,  56,  75,  96, 119, 144, 171])
a > b
array([False, False, False, False, False, False, False, False, False,
       False])
a[0] = 10
a > b
array([False, False, False, False, False, False, False, False, False,
       False])

2.6 Numpy-通用函数

  • 通用函数:能同时对数组中所有元素进行运算的函数
  • 常见通用函数:
    • 一元函数:abs,sqrt, exp, log, ceil, floor, rint, trunc, modf, isnan, isinf, cos, sin, tan
    • 二元函数:add, substract, multiply, divide, power, mod, maximun, minimun
a = np.arange(-5, 5)
np.abs(a)
array([5, 4, 3, 2, 1, 0, 1, 2, 3, 4])
a = np.arange(10)
np.sqrt(a)
array([0.        , 1.        , 1.41421356, 1.73205081, 2.        ,
       2.23606798, 2.44948974, 2.64575131, 2.82842712, 3.        ])
a = 1.6
int(a)#向0取整
1
round(a)#向0四舍五入
2
a = -1.6
int(a)
-1
round(a)
-2
import math
math.floor(a)#向下取整
-2
math.ceil(a)# 向上取整
-1
a = np.arange(-5.5, 5.5)
a
array([-5.5, -4.5, -3.5, -2.5, -1.5, -0.5,  0.5,  1.5,  2.5,  3.5,  4.5])
np.floor(a)
array([-6., -5., -4., -3., -2., -1.,  0.,  1.,  2.,  3.,  4.])
np.ceil(a)
array([-5., -4., -3., -2., -1., -0.,  1.,  2.,  3.,  4.,  5.])
np.round(a)
array([-6., -4., -4., -2., -2., -0.,  0.,  2.,  2.,  4.,  4.])
np.trunc(a)#相当于单个数值时的int()
array([-5., -4., -3., -2., -1., -0.,  0.,  1.,  2.,  3.,  4.])
np.rint(a)# 相当于np.round()
array([-6., -4., -4., -2., -2., -0.,  0.,  2.,  2.,  4.,  4.])
np.modf(a)#把整数部分和小数部分分开
(array([-0.5, -0.5, -0.5, -0.5, -0.5, -0.5,  0.5,  0.5,  0.5,  0.5,  0.5]),
 array([-5., -4., -3., -2., -1., -0.,  0.,  1.,  2.,  3.,  4.]))

浮点数特殊值

  • nan(Not a number):不等于任何浮点数(nan!=nan)
  • inf(infinity):比任何浮点数都大
  • Numpy中创建特殊值:np.nan,np.inf
  • 在数据分析中,nan常被用做表示数据缺失值
float('nan')#把字符串nan转换成浮点数不会报错
nan
a = np.nan
a
nan
a != np.nan
True
a = np.arange(5)
b = a/a
b
c:\users\ibm\appdata\local\programs\python\python37\lib\site-packages\ipykernel_launcher.py:2: RuntimeWarning: invalid value encountered in true_divide
  





array([nan,  1.,  1.,  1.,  1.])
np.nan in b
False
np.isnan(b)
array([ True, False, False, False, False])
b[~(np.isnan(b))]
array([1., 1., 1., 1.])

2.7 Numpy数学和统计方法

  • sum 求和
  • mean 求平均数
  • min 求最小值
  • max 求最大值
  • std 求标准差
  • var 求方差
  • argmin求最小值索引
  • argmax求最大值索引
a = np.array([3,4,5,6,7])
a
array([3, 4, 5, 6, 7])
a.sum()
25
a.mean()
5.0
a = np.arange(0,10,0.2)
a
array([0. , 0.2, 0.4, 0.6, 0.8, 1. , 1.2, 1.4, 1.6, 1.8, 2. , 2.2, 2.4,
       2.6, 2.8, 3. , 3.2, 3.4, 3.6, 3.8, 4. , 4.2, 4.4, 4.6, 4.8, 5. ,
       5.2, 5.4, 5.6, 5.8, 6. , 6.2, 6.4, 6.6, 6.8, 7. , 7.2, 7.4, 7.6,
       7.8, 8. , 8.2, 8.4, 8.6, 8.8, 9. , 9.2, 9.4, 9.6, 9.8])
a.mean()+a.std()
7.786173937932363
a.mean()-a.std()
2.0138260620676376
a.argmax()
49
a.argmin()
0

生成随机数

  • 随机函数在np.random子包内
    • rand给定形状产生随机数组(0到1之间的数)
    • randint给定形状生成随机整数
    • choice给定形状产生随机选择
    • shuffle与random模块里面的shuffle()功能一样
    • uniform给定形状产生随机数组
import random
random.random()#返回0~1之间的浮点数
0.33974840838056275
random.randint(0,10)
1
random.choice([1,3,4,5])
3
random.shuffle(a)# 改变的是原列表中元素的位置
a
[4, 3, 1, 2]
np.random.randint(0,10)
3
np.random.randint(0,10,10)
array([0, 9, 2, 5, 7, 8, 7, 5, 0, 9])
np.random.randint(0,10,(3,5))
array([[0, 5, 4, 3, 9],
       [9, 7, 7, 7, 3],
       [9, 1, 2, 4, 8]])
np.random.rand(10)
array([0.87378977, 0.33263006, 0.16150175, 0.64087174, 0.84878454,
       0.55151132, 0.13053042, 0.10932033, 0.73284465, 0.78065613])
np.random.rand(15).reshape((3,5))
array([[0.03878439, 0.98258217, 0.79012658, 0.25156537, 0.14919864],
       [0.83778259, 0.58277696, 0.19777845, 0.88624567, 0.52116526],
       [0.26123987, 0.549867  , 0.15060013, 0.19665386, 0.56762764]])
np.random.choice([1,2,3,4,5])
4
np.random.choice([1,2,3,4,5],10)
array([1, 2, 1, 2, 2, 3, 2, 4, 1, 3])
a = [1,2,3,4]
np.random.shuffle(a)# 改变的是原数组中元素的位置
a
[1, 2, 4, 3]
random.uniform(2.0,4.0)
2.766299467157591
np.random.uniform(2.0,4.0,10)
array([2.24885655, 2.96570215, 3.42231782, 2.32249887, 3.29880152,
       3.38426602, 2.22924905, 2.20250663, 3.53510689, 2.40333176])

2.8 数组的变形

四大类数组层面上的操作,具体有

  • 重塑 (reshape) 和打平 (ravel, flatten)
  • 合并 (concatenate, stack) 和分裂 (split)
  • 重复 (repeat) 和拼接 (tile)
  • 其他操作 (sort, insert, delete, copy)

重塑和打平

重塑 (reshape) 和打平 (ravel, flatten) 这两个操作仅仅只改变数组的维度

  • 重塑是从低维到高维
  • 打平是从高维到低维
重塑

用reshape()函数将一维数组 arr 重塑成二维数组。

arr = np.arange(12) # 构建一个一维数组
print( arr )
[ 0  1  2  3  4  5  6  7  8  9 10 11]
print( arr.reshape((4,3)) ) # 将一维数组转换成二位数组
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]

当你重塑高维矩阵时,不想花时间算某一维度的元素个数时,可以用「-1」取代,程序会自动帮你计算出来。比如把 12 个元素重塑成 (2, 6),你可以写成 (2,-1) 或者 (-1, 6)。

print( arr.reshape((2,-1)) )
print( arr.reshape((-1,6)) )
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]]
打平

用 ravel() 或flatten() 函数将二维数组 arr 打平成一维数组。

arr = np.arange(12).reshape((4,3)) # 创建一个二位数组
print( arr )
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
ravel_arr = arr.ravel()
print( ravel_arr )
[ 0  1  2  3  4  5  6  7  8  9 10 11]
flatten_arr = arr.flatten()
print( flatten_arr )
[ 0  1  2  3  4  5  6  7  8  9 10 11]

行主序和列主序

行主序 (row-major order) 指每行的元素在内存块中彼此相邻,而列主序 (column-major order) 指每列的元素在内存块中彼此相邻。

  • 默认行主序的有 C 语言(下图 order=‘C’ 等价于行主序)
  • 默认列主序的有 Fortran 语言(下图 order=‘F’ 等价于列主序)

在这里插入图片描述

在 numpy 数组中,默认的是行主序,即 order =‘C’。

arr = np.arange(12)
print( arr.reshape((4,3), order='F')) # 装变成列主序
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]

函数 ravel() 或 flatten() 的不同之处是

  • ravel() 按「行主序」打平时没有复制原数组,按「列主序」在打平时复制了原数组
  • flatten() 在打平时复制了原数组

用代码验证一下,首先看 flatten(),将打平后的数组 flatten 第一个元素更新为 10000,并没有对原数组 arr 产生任何影响 (证明 flatten() 是复制了原数组)

arr = np.arange(6).reshape(2,3)
print( arr )
[[0 1 2]
 [3 4 5]]
flatten_arr = arr.flatten()
print( flatten_arr )
[0 1 2 3 4 5]
flatten_arr[0] = 10000
print( arr )
[[0 1 2]
 [3 4 5]]
flatten_arr2 = arr.ravel()
print( flatten_arr2 )
[0 1 2 3 4 5]
flatten_arr2[0] = 10000
print( arr )
[[10000     1     2]
 [    3     4     5]]

合并和分裂

合并 (concatenate, stack) 和分裂 (split) 这两个操作仅仅只改变数组的分合

  • 合并是多合一
  • 分裂是一分多
合并

使用「合并」函数有三种选择

  • 有通用的 concatenate
  • 有专门的 vstack, hstack, dstack
  • 有极简的 r_, c_
arr1 = np.array([[1, 2, 3], [4, 5, 6]])
arr2 = np.array([[7, 8, 9], [10, 11, 12]])
np.concatenate([arr1, arr2], axis=0) # 传入有数组组成的列表,axis=0表示按行合并
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12]])
np.concatenate([arr1, arr2], axis=1) # 传入有数组组成的列表,axis=1表示按列合并
array([[ 1,  2,  3,  7,  8,  9],
       [ 4,  5,  6, 10, 11, 12]])

通用的东西是好,但是可能效率不高,NumPy 里还有专门合并的函数

  • vstack:v 代表 vertical,竖直合并,等价于 concatenate(axis=0)
  • hstack:h 代表 horizontal,水平合并,等价于 concatenate(axis=1)
  • dstack:d 代表 depth-wise,按深度合并,深度有点像彩色照片的 RGB 通道

在这里插入图片描述

print( np.vstack((arr1, arr2)),'\n' ) # 传入数组组成的元组,按行合并
print( np.hstack((arr1, arr2)),'\n' ) # 传入数组组成的元组,按列合并
print( np.dstack((arr1, arr2)) ) # 传入数组组成的元组,增加一个数据
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]] 

[[ 1  2  3  7  8  9]
 [ 4  5  6 10 11 12]] 

[[[ 1  7]
  [ 2  8]
  [ 3  9]]

 [[ 4 10]
  [ 5 11]
  [ 6 12]]]
arr3 = np.array([[13, 14, 15], [16, 17, 18]])
arr3
array([[13, 14, 15],
       [16, 17, 18]])
print( np.vstack((arr1, arr2, arr3)),'\n' ) # 按行合并
print( np.hstack((arr1, arr2, arr3)),'\n' ) # 按列合并
print( np.dstack((arr1, arr2, arr3)),'\n' ) # 增加的是最后一个维度,即axis=2,或axis=-1
print( np.dstack((arr1, arr2, arr3)).shape )
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]
 [13 14 15]
 [16 17 18]] 

[[ 1  2  3  7  8  9 13 14 15]
 [ 4  5  6 10 11 12 16 17 18]] 

[[[ 1  7 13]
  [ 2  8 14]
  [ 3  9 15]]

 [[ 4 10 16]
  [ 5 11 17]
  [ 6 12 18]]] 

(2, 3, 3)

还有一种更简单的在竖直和水平方向合并的函数,r_() 和 c_()。

print( np.r_[arr1,arr2], '\n' )
print( np.c_[arr1,arr2] )
[[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]] 

[[ 1  2  3  7  8  9]
 [ 4  5  6 10 11 12]]

分裂

使用「分裂」函数有两种选择

  • 有通用的 split
  • 有专门的 hsplit, vsplit
arr = np.arange(25).reshape((5,5))
print( arr )
[[ 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]]

和 concatenate() 函数一样,我们可以在 split() 函数里通过设定轴,来对数组沿着竖直方向分裂 (轴 0) 和沿着水平方向分裂 (轴 1)。

first, second, third = np.split(arr,[1,3])
print( 'The first split is','\n', first ,'\n')
print( 'The second split is','\n', second ,'\n')
print( 'The third split is','\n', third )
The first split is 
 [[0 1 2 3 4]] 

The second split is 
 [[ 5  6  7  8  9]
 [10 11 12 13 14]] 

The third split is 
 [[15 16 17 18 19]
 [20 21 22 23 24]]

split() 默认沿着轴 0 分裂,其第二个参数 [1, 3] 相当于是个切片操作,将数组分成三部分:

  • 第一部分:1 (即第 1 行)
  • 第二部分:1:3 (即第 2 到 3 行)
  • 第二部分:3: (即第 4 到 5 行)
first2, second2, third2 = np.split(arr,[2,3],axis=1)
print( 'The first split is','\n', first2 ,'\n')
print( 'The second split is','\n', second2 ,'\n')
print( 'The third split is','\n', third2 )
The first split is 
 [[ 0  1]
 [ 5  6]
 [10 11]
 [15 16]
 [20 21]] 

The second split is 
 [[ 2]
 [ 7]
 [12]
 [17]
 [22]] 

The third split is 
 [[ 3  4]
 [ 8  9]
 [13 14]
 [18 19]
 [23 24]]

vsplit() 和 split(axis=0) 等价,hsplit() 和 split(axis=1) 等价。

在这里插入图片描述

first, second, third = np.hsplit(arr,[1,3])
print( 'The first split is', first )
print( 'The second split is', second )
print( 'The third split is', third )
The first split is [[ 0]
 [ 5]
 [10]
 [15]
 [20]]
The second split is [[ 1  2]
 [ 6  7]
 [11 12]
 [16 17]
 [21 22]]
The third split is [[ 3  4]
 [ 8  9]
 [13 14]
 [18 19]
 [23 24]]

重复和拼接

重复 (repeat) 和拼接 (tile) 这两个操作本质都是复制

  • 重复是在元素层面复制
  • 拼接是在数组层面复制
重复

函数 repeat() 复制的是数组的每一个元素,参数有几种设定方法:

  • 一维数组:用标量和列表来复制元素的个数
  • 多维数组:用标量和列表来复制元素的个数,用轴来控制复制的行和列
arr = np.arange(3)
print( arr )
print( arr.repeat(3) ) # 标量参数 3 - 数组 arr 中每个元素复制 3 遍。
[0 1 2]
[0 0 0 1 1 1 2 2 2]
print( arr.repeat([2,3,4]) ) 
# 列表参数 [2,3,4] - 数组 arr 中每个元素分别复制 2, 3, 4 遍。
# 列表长度必须和数组长度一致
[0 0 1 1 1 2 2 2 2]
print( arr.repeat([2,3,0]) ) # 重复次数可以为0
[0 0 1 1 1]
arr2d = np.arange(6).reshape((2,3))
print( arr2d )
[[0 1 2]
 [3 4 5]]
print( arr2d.repeat(2, axis=0) )
# 标量参数 2 和轴 0 - 数组 arr2d 中每个元素沿着轴 0 复制 2 遍。
[[0 1 2]
 [0 1 2]
 [3 4 5]
 [3 4 5]]
print( arr2d.repeat([2,3,4], axis=1) )
# 列表参数 [2,3,4] 和轴 1 - 数组 arr2d 中每个元素沿着轴 1 分别复制 2, 3, 4 遍。
[[0 0 1 1 1 2 2 2 2]
 [3 3 4 4 4 5 5 5 5]]
拼接

函数 tile() 复制的是数组本身,参数有几种设定方法:

  • 标量:把数组当成一个元素,一列一列复制
  • 形状:把数组当成一个元素,按形状复制
arr2d = np.arange(6).reshape((2,3))
print( arr2d )
print( np.tile(arr2d,2) ) # 标量参数 2 - 数组 arr 按列复制 2 遍。
[[0 1 2]
 [3 4 5]]
[[0 1 2 0 1 2]
 [3 4 5 3 4 5]]
print( np.tile(arr2d, (2,3)) )
# 标量参数 (2,3) - 数组 arr 按形状复制 6 (2×3) 遍,并以 (2,3) 的形式展现。
# 标量参数 (2,3)的个数必须和数组的维度一致。
[[0 1 2 0 1 2 0 1 2]
 [3 4 5 3 4 5 3 4 5]
 [0 1 2 0 1 2 0 1 2]
 [3 4 5 3 4 5 3 4 5]]

排序、插入、删除和复制

排序

排序包括直接排序 (direct sort) 和间接排序 (indirect sort)。

arr = np.array([5,3,2,6,1,4])
print( 'Before sorting', arr )
arr.sort() # 排序,改变原数组中各元素的位置
print( 'After sorting', arr )
Before sorting [5 3 2 6 1 4]
After sorting [1 2 3 4 5 6]

sort()函数是按升序 (ascending order) 排列的,该函数里没有参数可以控制 order,因此想要按降序排列的数组,需要加一个arr[::-1]。

print( arr[::-1] )
[6 5 4 3 2 1]
arr = np.array([5,3,2,6,1,4])
arr2 = np.sort(arr) # 排序,不会改变原数组中各元素的位置
print('Before sorting', arr)
print('After sorting',arr2)
Before sorting [5 3 2 6 1 4]
After sorting [1 2 3 4 5 6]

此外也可以在不同的轴上排序,对于二维数组,在「轴 0」上排序是「跨行」排序,在「轴 1」上排序是「跨列」排序。

arr = np.random.randint( 40, size=(3,4) ) # 创建一个4*3的数字小于40的二位数组
print(arr)
arr.sort(axis=1) # 按列排序
print( arr )
[[23 11 14 29]
 [39 10 19 16]
 [34 14 31 38]]
[[11 14 23 29]
 [10 16 19 39]
 [14 31 34 38]]

有时候我们不仅仅只想排序数组,还想在排序过程中提取每个元素在原数组对应的索引(index),这时 argsort() 就派上用场了。

score = np.array([100, 60, 99, 80, 91])
idx = score.argsort()
print( idx )
[1 3 4 2 0]
  • 60,即 score[1] 排在第0位, 因此 idx[0] =1
  • 80,即 score[3] 排在第1 位, 因此 idx[1] =3
  • 91,即 score[4] 排在第2 位, 因此 idx[2] =4
  • 99,即 score[2] 排在第3 位, 因此 idx[3] =2
  • 100,即 score[0] 排在第4 位, 因此 idx[4] =0
# 用这个 idx 对 score 做一个「花式索引」得到即可得到排序后的数组
print( score[idx] )
# 即score([1, 3, 4, 2, 0])
[ 60  80  91  99 100]
arr = np.random.randint( 40, size=(3,4) )
print( arr )
[[38  8  6 14]
 [ 1 21 36 13]
 [19 19 21 21]]
# 对其第一行 arr[0] 排序,获取索引,在应用到所用行上。
idx = arr[0].argsort() 
arr[:, idx]
array([[ 6,  8, 14, 38],
       [36, 21, 13,  1],
       [21, 19, 21, 19]])
arr[:, arr[0].argsort()] # 将上述步骤合成一步
array([[ 6,  8, 14, 38],
       [36, 21, 13,  1],
       [21, 19, 21, 19]])

插入和删除

和列表一样,我们可以给 numpy 数组

  • 用insert()函数在某个特定位置之前插入元素
  • 用delete()函数删除某些特定元素
a = np.arange(6)
print( a )
print( np.insert(a, 1, 100) )
print( np.delete(a, [1,3]) )
[0 1 2 3 4 5]
[  0 100   1   2   3   4   5]
[0 2 4 5]

复制

用copy()函数来复制数组 a 得到 a_copy,很明显,改变 a_copy 里面的元素不会改变 a。

a = np.arange(6)
a_copy = a.copy()
print( 'Before changing value, a is', a )
print( 'Before changing value, a_copy is', a_copy )
a_copy[-1] = 99
print( 'After changing value, a_copy is', a_copy )
print( 'After changing value, a is', a )
Before changing value, a is [0 1 2 3 4 5]
Before changing value, a_copy is [0 1 2 3 4 5]
After changing value, a_copy is [ 0  1  2  3  4 99]
After changing value, a is [0 1 2 3 4 5]

2.9 数组的计算

本节介绍四大类数组计算,具体有

  • 1.元素层面 (element-wise) 计算
  • 2.线性代数 (linear algebra) 计算
  • 3.元素整合 (element aggregation) 计算
  • 4.广播机制 (broadcasting) 计算

元素层面计算

Numpy 数组元素层面计算包括:

  • 二元运算 (binary operation):加减乘除
  • 数学函数:倒数、平方、指数、对数
  • 比较运算 (comparison)
arr1 = np.array([[1., 2., 3.], [4., 5., 6.]])
arr2 = np.ones((2,3)) * 2
print( arr1 )
print( arr2 )
[[1. 2. 3.]
 [4. 5. 6.]]
[[2. 2. 2.]
 [2. 2. 2.]]
# 加、减、乘、除
print( arr1 + arr2 + 1 )
print( arr1 - arr2 )
print( arr1 * arr2 )
print( arr1 / arr2 )
[[4. 5. 6.]
 [7. 8. 9.]]
[[-1.  0.  1.]
 [ 2.  3.  4.]]
[[ 2.  4.  6.]
 [ 8. 10. 12.]]
[[0.5 1.  1.5]
 [2.  2.5 3. ]]
# 倒数、平方、指数、对数
print( 1 / arr1 )
print( arr1 ** 2 )
print( np.exp(arr1) )
print( np.log(arr1) )
[[1.         0.5        0.33333333]
 [0.25       0.2        0.16666667]]
[[ 1.  4.  9.]
 [16. 25. 36.]]
[[  2.71828183   7.3890561   20.08553692]
 [ 54.59815003 148.4131591  403.42879349]]
[[0.         0.69314718 1.09861229]
 [1.38629436 1.60943791 1.79175947]]
# 比较
print(arr1 > arr2)
print(arr1 > 3)
[[False False  True]
 [ True  True  True]]
[[False False False]
 [ True  True  True]]

从上面结果可知

  • 「数组和数组间的二元运算」都是在元素层面上进行的
  • 「作用在数组上的数学函数」都是作用在数组的元素层面上的。
  • 「数组和数组间的比较」都是在元素层面上进行的

但是在「数组和标量间的比较」时,python 好像先把 3 复制了和 arr1 形状一样的数组 [[3,3,3], [3,3,3]],然后再在元素层面上作比较。
这个复制标量的操作叫做「广播机制」,是 NumPy 里最重要的一个特点。

线性代数计算

在机器学习、金融工程和量化投资的编程过程中,因为运行速度的要求,通常会向量化 (vectorization) 而涉及大量的线性代数运算,尤其是矩阵之间的乘积运算。

但是,在 NumPy 默认不采用矩阵运算,而是数组 (ndarray) 运算。矩阵只是二维,而数组可以是任何维度,因此数组运算更通用些。

如果你非要二维数组 arr2d 进项矩阵运算,那么可以通过调用以下函数来实现:

  • A = np.mat(arr2d)
  • A = np.asmatrix(arr2d)
# 创建
arr2d = np.array([[1,2],[3,1]]) # 创建一个二维的数组
arr2d
array([[1, 2],
       [3, 1]])
A = np.asmatrix(arr2d) # 将二维的数组转换成矩阵
A
matrix([[1, 2],
        [3, 1]])

:创建数组 arr2d 和矩阵 A,注意它们的输出有 array 和 matrix 的关键词

A = np.mat(arr2d)
A
matrix([[1, 2],
        [3, 1]])
转置

数组用 arr2d.T 操作或 arr.tranpose() 函数,而矩阵用 A.T 操作,主要原因就是 .T 只适合二维数据。

arr2d.T
array([[1, 3],
       [2, 1]])
arr2d.transpose()
array([[1, 3],
       [2, 1]])
A.T
matrix([[1, 3],
        [2, 1]])
求逆

数组用 np.linalg.inv() 函数,而矩阵用 A.I 和 A**-1 操作。

np.linalg.inv(arr2d)
array([[-0.2,  0.4],
       [ 0.6, -0.2]])
A.I
matrix([[-0.2,  0.4],
        [ 0.6, -0.2]])
A**-1
matrix([[-0.2,  0.4],
        [ 0.6, -0.2]])
相乘

相乘是个很模棱两可的概念

  • 数组相乘是在元素层面进行;
  • 矩阵相乘要就是数学定义的矩阵相乘 (比如第一个矩阵的列要和第二个矩阵的行一样)。
arr = np.array([1,2]) # 创建一个一维数组
b = np.asmatrix(arr).T # 先转换成矩阵,再转置
print( arr.shape, b.shape )
(2,) (2, 1)

由上面结果看出, arr 的形状是 (2,),只含一个元素的元组只说明 arr 是一维,数组是不分行数组或列数组的。而 b 的形状是 (2,1),显然是列向量。

print('arr2d\n', arr2d) 
print('arr2d*arr\n', arr2d*arr ) # 一个2*2的数组,乘以一个2个元素的一维数组。
# arr2d每一行的第一个元素,乘以arr的第一个元素;arr2d每一行的第二个元素,乘以arr的第二个元素。
arr2d
 [[1 2]
 [3 1]]
arr2d*arr
 [[1 4]
 [3 2]]
print( A*b ) # 一个2*2的矩阵,乘以一个2*1的矩阵(列向量)。
# 1*1+2*2,3*1+1*2
[[5]
 [5]]

由上面结果可知,

  • 二维数组相乘一维数组得到的还是个二维数组,解释它需要用到「广播机制」,这是下节的重点讨论内容。现在大概知道一维数组 [1 2] 第一个元素 1 乘上 [1 3] 得到 [1 3],而第二个元素 2 乘上 [2 1] 得到 [4 2]。
  • 矩阵相乘向量的结果和我们学了很多年的线代结果很吻合。
print( arr2d*arr2d ) # 1*1,2*2,3*3,1*1
print( A*A ) # 1*1+2*3,1*2+2*1,3*1+1*3,3*2+1*1
[[1 4]
 [9 1]]
[[7 4]
 [6 7]]

由上面结果可知,

  • 虽然两个二维数组相乘得到二维数组,但不是根据数学上矩阵相乘的规则得来的,而且由元素层面相乘得到的。两个 [[1 2], [3,1]] 的元素相乘确实等于 [[1 4], [9,1]]。
  • 而矩阵相乘矩阵的结果和我们学了很多年的线代结果很吻合。

问题来了,那么怎么才能在数组上实现「矩阵相乘向量」和「矩阵相乘矩阵」呢?用点乘函数 dot()。

print( np.dot(arr2d,arr) ) 
print( np.dot(arr2d,arr2d) )
[5 5]
[[7 4]
 [6 7]]

结果对了,但还有一个小小的差异

  • 矩阵相乘列向量的结果是个列向量,写成 [[5],[5]],形状是 (2,1)
  • 二维数组点乘一维数组结果是个一维数组,写成 [5, 5],形状是 (2,)

点乘函数

通常我们也把 n 维数组称为张量,点乘左右两边最常见的数组就是

  • 向量 (1D) 和向量 (1D)
  • 矩阵 (2D) 和向量 (1D)
  • 矩阵 (2D) 和矩阵 (2D)

例一:np.dot(向量, 向量) 实际上做的就是内积,即把两个向量每个元素相乘,最后再加总。点乘结果 10 是个标量 (0D 数组),形状 = ()。

x = np.array( [1, 2, 3] )
y = np.array( [3, 2, 1] )
z = np.dot(x,y) # 1*3+2*2+3*1
print( z.shape ) #z是一个标量,所以shape是()
print( z )
()
10

例二:np.dot(矩阵, 向量) 实际上做的就是普通的矩阵乘以向量。点乘结果是个向量 (1D 数组),形状 = (2, )。

x = np.array( [1, 2, 3] )
y = np.array( [[3, 2, 1], [1, 1, 1]] )
z = np.dot(y,x) # 3*1+2*2+1*3,1*1+1*2+1*3
print( z.shape )
print( z )
(2,)
[10  6]

例三:np.dot(矩阵, 矩阵) 实际上做的就是普通的矩阵乘以矩阵。点乘结果是个矩阵 (2D 数组),形状 = (2, 3)。

x = np.array( [[1, 2, 3], [1, 2, 3], [1, 2, 3]] )
y = np.array( [[3, 2, 1], [1, 1, 1]] )
z = np.dot(y,x) # [3*1+2*1+1*1,3*2+2*2+1*2,3*3+2*3+1*3],[1*1+1*1+1*1,1*2+1*2+1*2,1*3+1*3+1*3]
print( z.shape )
print( z )
(2, 3)
[[ 6 12 18]
 [ 3  6  9]]

:从例二和例三看出,当 y 第二个维度的元素 (y.shape[1]) 和 x 第一个维度的元素 (x.shape[0]) 个数相等时,np.dot(Y, X) 才有意义,点乘得到的结果形状 = (y.shape[0], x.shape[1])。

例四:当 x 是 3D 数组,y 是 1D 数组,np.dot(x, y) 是将 x 和 y 最后一维的元素相乘并加总。此例 x 的形状是 (2, 3, 4),y 的形状是 (4, ),因此点乘结果的形状是 (2, 3)。

x = np.ones( shape=(2, 3, 4) )
y = np.array( [1, 2, 3, 4] )
z = np.dot(x,y)
print( z.shape )
print( z )
(2, 3)
[[10. 10. 10.]
 [10. 10. 10.]]

例五:当 x 是 3D 数组,y 是 2D 数组,np.dot(x, y) 是将 x 的最后一维和 y 的倒数第二维的元素相乘并加总。此例 x 的形状是 (2, 3, 4),y 的形状是 (4, 2),因此点乘结果的形状是 (2, 3, 2)。

x = np.random.normal( 0, 1, size=(2, 3, 4) )
y = np.random.normal( 0, 1, size=(4, 2) )
z = np.dot(x,y)
print( z.shape )
print( z )
(2, 3, 2)
[[[ 0.22260907 -0.9438252 ]
  [ 1.35050681  0.75608906]
  [-1.60543843  1.18444724]]

 [[ 2.74852923 -1.28491497]
  [ 2.37193257 -1.7125361 ]
  [-1.59693321  2.73530355]]]

元素整合运算

在数组中,元素可以以不同方式整合 (aggregation)。拿求和 (sum) 函数来说,我们可以对数组

  • 所有的元素求和
  • 在某个轴 (axis) 上的元素求和
arr = np.arange(1,7).reshape((2,3))
arr
array([[1, 2, 3],
       [4, 5, 6]])
print( 'The total sum is', arr.sum() ) # 全部元素求和
print( 'The sum across rows is', arr.sum(axis=0) ) #对每一列求和
print( 'The sum across columns is', arr.sum(axis=1) ) # 对每一行求合
The total sum is 21
The sum across rows is [5 7 9]
The sum across columns is [ 6 15]

分析上述结果:

  • 1, 2, 3, 4, 5, 6 的总和是 21
  • 跨行求和(对每一列求和) = [1 2 3] + [4 5 6] = [5 7 9]
  • 跨列求和(对每一行求和) = [1+2+3 4+5+6] = [6 15]

行和列这些概念对矩阵 (二维矩阵) 才适用,高维矩阵还是要用轴 (axis) 来区分每个维度。让我们抛弃「行列」这些特殊概念,拥抱「轴」这个通用概念来重看数组 (一到四维) 。

在这里插入图片描述

规律:n 维数组就有 n 层方括号。最外层方括号代表「轴 0」即 axis=0,依次往里方括号对应的 axis 的计数加 1。

严格来说,numpy 打印出来的数组可以想象带有多层方括号的一行数字。比如二维矩阵可想象成

[[1, 2, 3],[4, 5, 6]]

三维矩阵可想象成

[[[1,2,3], [4,5,6]], [[7,8,9], [10,11,12]]]

由于屏幕的宽度不够,我们才把它们写成一列列的,如下

[ [ [1, 2, 3],
     [4, 5, 6] ], 
  [ [7, 8, 9],
     [10, 11, 12] ] ]

所以sum()求和函数可以看作:

在这里插入图片描述

arr = np.array([1,2,3])
print( 'The total sum is', arr.sum() )
print( 'The sum on axis0 is', arr.sum(axis=0) )
The total sum is 6
The sum on axis0 is 6
  • 1, 2, 3 的总和是 6
  • 在轴 0(只有一个轴) 上的元素求和是 6

在这里插入图片描述

arr = np.arange(1,7).reshape((2,3))
print( arr )
[[1 2 3]
 [4 5 6]]
print( 'The total sum is', arr.sum() )
print( 'The sum on axis0 is', arr.sum(axis=0) )
print( 'The sum on axis1 is', arr.sum(axis=1) )
The total sum is 21
The sum on axis0 is [5 7 9]
The sum on axis1 is [ 6 15]
  • 1 到 6 的总和是 6
  • 轴 0 上的元素 (被一个红方括号[]包住的) 是[1, 2, 3]和[4, 5, 6],求和得到[[5, 6, 7]]
  • 轴 1 上的元素 (被两个蓝方括号[] 包住的) 分别是 1, 2, 3 和 4, 5, 6,求和得到 [[1+2+3, 4+5+6]]= [[6, 15]]

结果是对的,但是好像括号比上图推导出来的少一个。原因np.sum()里面有个参数是 keepdims,意思是「保留维度」,默认值时 False,因此会去除多余的括号,比如 [[5, 7, 9]] 会变成 [5, 7, 9]。

如果把 keepdims 设置为 True,那么打印出来的结果和上图推导的一模一样。

print( arr.sum(axis=0, keepdims=True) )
print( arr.sum(axis=1, keepdims=True) )
[[5 7 9]]
[[ 6]
 [15]]

在这里插入图片描述

  • 1 到 12 的总和是 78
  • 轴 0 上的元素是一个红方括号[] 包住的两个 [[ ]],对其求和得到一个 [ [[ ]] ]
  • 轴 1 上的元素是两个蓝方括号[] 包住的两个[ ],对其求和得到两个 [[ ]],即 [ [[ ]], [[ ]] ]
  • 轴 2 上的元素是四个绿方括号[] 包住的三个标量,对其求和得到四个[],即 [ [[ ], [ ]], [[ ], [ ]] ]
arr = np.arange(1,13).reshape((2,2,3))
print(arr)
[[[ 1  2  3]
  [ 4  5  6]]

 [[ 7  8  9]
  [10 11 12]]]
print( 'The total sum is', arr.sum() )
print( 'The sum on axis0 is\n', arr.sum(axis=0) )
print( 'The sum on axis1 is\n', arr.sum(axis=1) )
print( 'The sum on axis2 is\n', arr.sum(axis=2) )
The total sum is 78
The sum on axis0 is
 [[ 8 10 12]
 [14 16 18]]
The sum on axis1 is
 [[ 5  7  9]
 [17 19 21]]
The sum on axis2 is
 [[ 6 15]
 [24 33]]

小结

除了 sum 函数,整合函数还包括 min, max, mean, std 和 cumsum,分别是求最小值、最大值、均值、标准差和累加,这些函数对数组里的元素整合方式和 sum 函数相同,就不多讲了。总结来说我们可以对数组

  • 所有的元素整合

  • 在某个轴 (axis) 上的元素整合

    整合函数= {sum, min, max, mean, std, cumsum}

广播机制计算

当对两个形状不同的数组按元素操作时,可能会触发「广播机制」。具体做法,先适当复制元素使得这两个数组形状相同后再按元素操作,两个步骤:

  • 广播轴 (broadcast axis):比对两个数组的维度,将形状小的数组的维度 (轴) 补齐
  • 复制元素:顺着补齐的轴,将形状小的数组里的元素复制,使得最终形状和另一个数组吻合
# 标量和一维数组
arr = np.arange(5)
print( arr )
print( arr + 2 ) # 元素 2 被广播到数组 arr 的所有元素上。
[0 1 2 3 4]
[2 3 4 5 6]
# 一维数组和二维数组
arr = np.arange(12).reshape((4,3))
print( arr )
print( arr.mean(axis=0) ) # 沿axis=0(跨行)进行求均值,即对每一列求均值
print( arr - arr.mean(axis=0) ) # 沿轴 0 的均值的一维数组被广播到数组 arr 的所有的行上。
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
[4.5 5.5 6.5]
[[-4.5 -4.5 -4.5]
 [-1.5 -1.5 -1.5]
 [ 1.5  1.5  1.5]
 [ 4.5  4.5  4.5]]

当我们对两个数组操作时,如果它们的形状

  • 不相容 (incompatible),广播机制不能进行
  • 相容 (compatible),广播机制可以进行

因此,进行广播机制分两步

  • 检查两个数组形状(shape)是否兼容,即从两个形状元组最后一个元素往前,来逐个检查
    • 它们是否相等
    • 是否有其中一个等于 1
  • 一旦它们形状兼容,确定两个数组的最终形状。
a = np.array([[1,2,3]])
b = np.array([[4],[5],[6]])
print( 'The shape of a is', a.shape )
print( 'The shape of b is', b.shape )
The shape of a is (1, 3)
The shape of b is (3, 1)

回顾进行广播机制的两步

  • 1.检查数组 a 和 b 形状是否兼容,从两个形状元组 (1, 3) 和 (3, 1)最后一个元素开始检查,发现它们都满足『有一个等于 1』的条件。
  • 2.因此它们形状兼容,两个数组的最终形状为 (max(1,3), max(3,1)) = (3, 3)
c = a + b
print( 'The shape of c is', c.shape )
print( 'a is\n', a )
print( 'b is\n', b )
print( 'c = a + b =\n', c )
The shape of c is (3, 3)
a is
 [[1 2 3]]
b is
 [[4]
 [5]
 [6]]
c = a + b =
 [[5 6 7]
 [6 7 8]
 [7 8 9]]
a = np.arange(5)
b = np.array(2)
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b is', b.ndim, 'and the shape of b is', b.shape )
The dimension of a is 1 and the shape of a is (5,)
The dimension of b is 0 and the shape of b is ()

数组 a 和 b 形状分别为 (5,) 和 (),首先我们把缺失的维度用 1 补齐得到 (5,) 和 (1,),再根据广播机制那套流程得到这两个形状是兼容的,而且最终形状为 (5,)。

c = a + b
print( 'The dimension of c is', c.ndim, 'and the shape of c is', c.shape, '\n' )
print( 'a is', a )
print( 'b is', b )
print( 'c = a + b =', c )
The dimension of c is 1 and the shape of c is (5,) 

a is [0 1 2 3 4]
b is 2
c = a + b = [2 3 4 5 6]
a = np.array( [[[1,2,3], [4,5,6]]] )
b1 = np.array( [[1,1,1], [2,2,2], [3,3,3]] )
b2 = np.arange(3).reshape((1,3))
b3 = np.arange(6).reshape((2,3))
b4 = np.arange(12).reshape((2,2,3))
b5 = np.arange(6).reshape((2,1,3))
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b1 is', b.ndim, 'and the shape of b1 is', b1.shape, '\n')
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b2 is', b.ndim, 'and the shape of b2 is', b2.shape, '\n' )
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b3 is', b.ndim, 'and the shape of b3 is', b3.shape, '\n' )
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b4 is', b.ndim, 'and the shape of b4 is', b4.shape, '\n' )
print( 'The dimension of a is', a.ndim, 'and the shape of a is', a.shape )
print( 'The dimension of b5 is', b.ndim, 'and the shape of b5 is', b5.shape )
The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b1 is 0 and the shape of b1 is (3, 3) 

The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b2 is 0 and the shape of b2 is (1, 3) 

The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b3 is 0 and the shape of b3 is (2, 3) 

The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b4 is 0 and the shape of b4 is (2, 2, 3) 

The dimension of a is 3 and the shape of a is (1, 2, 3)
The dimension of b5 is 0 and the shape of b5 is (2, 1, 3)

对于数组 a 和 b1,它们形状是 (1, 2, 3) 和 (3, 3)。元组最后一个都是 3,兼容;倒数第二个是 3 和 2,即不相等,也没有一个是 1,不兼容!a 和 b1 不能进行广播机制。

# a的形状(1,2,3),b2的形状(1,3),元组最后一个都是 3,兼容;倒数第二个是 2 和 1,虽然不相等,但是有一个是1,兼容。
c2 = a + b2
print( c2 )
print( c2.shape )
[[[1 3 5]
  [4 6 8]]]
(1, 2, 3)
# a的形状(1,2,3),b3的形状(2,3),元组最后一个都是 3,兼容;倒数第二个都是2,兼容。
c3 = a + b3
print( c3 )
print( c3.shape )
[[[ 1  3  5]
  [ 7  9 11]]]
(1, 2, 3)
# a的形状(1,2,3),b4的形状(2,2,3),元组最后一个都是 3,兼容;倒数第二个都是2,兼容,最后一个是1和2,有一个是1,兼容。
c4 = a + b4
print( c4 )
print( c4.shape )
[[[ 1  3  5]
  [ 7  9 11]]

 [[ 7  9 11]
  [13 15 17]]]
(2, 2, 3)
# a的形状(1,2,3),b5的形状(2,1,3),元组最后一个都是 3,兼容;倒数第二个是2和1,有一个是1,兼容,最后一个是1和2,有一个是1,兼容。
c5 = a + b5
print( c5 )
print( c5.shape )
[[[ 1  3  5]
  [ 4  6  8]]

 [[ 4  6  8]
  [ 7  9 11]]]
(2, 2, 3)

小结

数组变形有以下重要操作:

  • 改变维度的重塑和打平
  • 改变分合的合并和分裂
  • 复制本质的重复和拼接
  • 其他排序插入删除复制

数组计算有以下重要操作:

  • 元素层面:四则运算、函数,比较
  • 线性代数:务必弄懂点乘函数 dot()
  • 元素整合:务必弄懂轴这个概念!
  • 广播机制:太重要了,神经网络无处不在!
  • 1
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值