Numpy教程
- 创建数据
- 原生list生成
- 基于numpy函数生成
- 多维数组属性
- 数据索引,切片
- 变形,拼接
- 通用函数
- 广播原则
- 布尔索引,逻辑应用
创建数组(ndarray)
认识,检查Numpy中的数据类型
使用原生python的数据结构(list)产生多维数组
import numpy as np # 导入numpy模块, 并命名别名为np(简单的名字)
numpy中核心的数据结构ndarray(多维数组), 我们先利用python原生list的数据结构来进行数据的创建。
实现手段通过np.array()函数来实现。
np.array([1, 2, 3], dtype=np.float) # 创建一纬数据, 指定数据类型为float
array([1., 2., 3.])
可以看到上面的数据中1., 2.这表明我们定义的np.float生效了。
指定数据类型的时候, 就说明了一点array中的所有数据类型是一致的,不存在异构特性,这样的数据结构更加高效。数据科学中一般都是这样,一个字段的数据都是一类数据。
np.array([range(i, i+3) for i in [2, 4, 6]]) # 嵌套列表生成多维数组
array([[2, 3, 4],
[4, 5, 6],
[6, 7, 8]])
在numpy中创建多维数组
np.zeros(10, dtype='int') # 创建长度为10的一纬数组, 由整数0组成
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
np.ones((3, 4), dtype=float) # 创建尺寸为(3, 4)纬度的数组, 由浮点数1组成
array([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
np.full((3, 4), 3.14)
array([[3.14, 3.14, 3.14, 3.14],
[3.14, 3.14, 3.14, 3.14],
[3.14, 3.14, 3.14, 3.14]])
np.arange(0, 20, 2)
array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
np.linspace(0, 1, 5)
array([0. , 0.25, 0.5 , 0.75, 1. ])
np.linspace(0, 1, num=5, endpoint=False) # 线性切割, 取5个数, 结束点舍弃
array([0. , 0.2, 0.4, 0.6, 0.8])
# 创建3x3的,在0~1随机分布的数组
np.random.random((3, 3))
array([[0.01442053, 0.44934336, 0.49166372],
[0.59761446, 0.74568858, 0.84832538],
[0.38406606, 0.11851915, 0.49031066]])
# 创建3x3,均值为0, 方差为1的正态分布
np.random.normal(0, 1, (3, 3))
array([[ 0.56689504, 0.62134462, 0.43416949],
[ 2.91520603, -0.66956539, 1.83170135],
[ 0.13399765, -0.90022633, -0.01680633]])
# 创建3x3, [0, 10)分布的随机整数
np.random.randint(0, 10, (3, 3))
array([[5, 8, 0],
[8, 8, 1],
[6, 3, 9]])
# 创建3x3的单位矩阵
np.eye(3)
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
多维数组属性
np.random.seed(0)
x = np.random.randint(10, size=(3, 3))
print('x ndim:', x.ndim) # 数组纬度
print('x shape:', x.shape) # 数组形状(各维度的尺寸)
print('x size:', x.size) # shape中所有的尺寸相乘
print('x itemsize:', x.itemsize) # 字节为单位, 每个元素的内存大小
print('x nbytes:', x.nbytes) # size x itemsize, 数组的存储大小
x ndim: 2
x shape: (3, 3)
x size: 9
x itemsize: 4
x nbytes: 36
数组索引
单个元素的索引
np.random.seed(0)
x = np.random.randint(0, 10, (6,))
x2 = np.random.randint(0, 10, (3, 4))
x
array([5, 0, 3, 3, 7, 9])
x[0]
5
x[-1]
9
x2
array([[3, 5, 2, 4],
[7, 6, 8, 8],
[1, 6, 7, 7]])
x2[0, 0]
3
x2[2, -1]
7
# 通过索引直接修改值
x2[2, -1] = 99
x2
array([[ 3, 5, 2, 4],
[ 7, 6, 8, 8],
[ 1, 6, 7, 99]])
修改时注意原ndarray的dtype类型, 此处x, x2的dtype都是int, 如下修改其元素为浮点数时会自动强装成int, float会被截短
x[-2] = 3.1415
x
array([5, 0, 3, 3, 3, 9])
解决办法:
y = x.astype(float) # 将数据类型转换为float
y[-2] = 3.1415
y
array([5. , 0. , 3. , 3. , 3.1415, 9. ])
这里揭示了一个现象,ndarray类似原生array数据类型,是一种数据类型的有序’集合’,在修改数据类型的时候需要特别留意数据精度的问题。
索引的进阶,切片
import numpy as np
'''创建数据'''
x = np.random.randint(0, 10, (3, 4))
x
array([[8, 1, 5, 9],
[8, 9, 4, 3],
[0, 3, 5, 0]])
x[:, 2] # 所有行第二列
array([5, 4, 5])
x[::2, ::2] # 0, 2 行, 0, 2列
array([[8, 5],
[0, 5]])
print(slice.__doc__) # 切片的文档部分
slice(stop)
slice(start, stop[, step])
Create a slice object. This is used for extended slicing (e.g. a[0:10:2]).
切片的高阶使用,避免浅拷贝带来的数据风险。
sli = slice(0, 3, 2) # 左闭右开, endpoint默认是不取的
x[sli, sli]
array([[8, 5],
[0, 5]])
c = x[sli, sli]
c
array([[8, 5],
[0, 5]])
c[0, 0] = 99
c
array([[99, 5],
[ 0, 5]])
x # x中的数值[0, 0]变成了99
array([[99, 1, 5, 9],
[ 8, 9, 4, 3],
[ 0, 3, 5, 0]])
切记, 为了提高效率及减少内存的使用在获取切片或通过索引创建副本的时候,修改副本会造成原数据的变更,如果这与你的期望不符的化,要使用copy
c = x[sli, sli].copy()
c
array([[99, 5],
[ 0, 5]])
c[0, 0] = 0
c
array([[0, 5],
[0, 5]])
x # 看到x中的数值没有发生变化
array([[99, 1, 5, 9],
[ 8, 9, 4, 3],
[ 0, 3, 5, 0]])
数组的变形
grid = np.arange(1, 10, 1).reshape((3,3)) # 1-9步长为1, 重置为3x3的数组
grid
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
newaxis:让人困惑的纬度定义
x = np.array([1, 2, 3])
x
array([1, 2, 3])
x.reshape((1, 3)) # 重置1x3数组
array([[1, 2, 3]])
x[np.newaxis, :] # 获取行向量
array([[1, 2, 3]])
x[:, np.newaxis] # 获取列向量
array([[1],
[2],
[3]])
我更推荐reshape的方式来得到行列向量。这样比newaxis更好理解,但是有理由相信
newaxis在其他的地方会有更好的使用前景。
x.reshape((1, 3)) # reshape行向量
array([[1, 2, 3]])
x.reshape((-1, 1)) # 等效reshape((3, 1)), -1在于无需知道数组的长度
array([[1],
[2],
[3]])
拼接数组
grid = np.array([1, 2, 3])
np.concatenate([grid, grid])
array([1, 2, 3, 1, 2, 3])
grid = np.arange(0, 6).reshape(2, 3)
np.concatenate([grid, grid]) # 默认沿着x轴拼接数据
array([[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]])
np.concatenate([grid, grid], axis=1) # 指定沿着第二个纬度,y轴拼接
array([[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]])
好在由简单的使用方式:(快捷拼接,不需要考虑axis参数)
np.hstack([grid, grid])
array([[0, 1, 2, 0, 1, 2],
[3, 4, 5, 3, 4, 5]])
np.vstack([grid, grid])
array([[0, 1, 2],
[3, 4, 5],
[0, 1, 2],
[3, 4, 5]])
拆分数组
x = np.arange(0, 8)
x
array([0, 1, 2, 3, 4, 5, 6, 7])
np.split(x, [2, 4])
[array([0, 1]), array([2, 3]), array([4, 5, 6, 7])]
grid = np.arange(0, 16).reshape(4, 4)
upper, lower = np.split(grid, [2]) # 默认沿着x切割
print(upper)
print(lower)
[[0 1 2 3]
[4 5 6 7]]
[[ 8 9 10 11]
[12 13 14 15]]
left, right = np.split(grid, [2], axis=1)
print(left)
print(right)
[[ 0 1]
[ 4 5]
[ 8 9]
[12 13]]
[[ 2 3]
[ 6 7]
[10 11]
[14 15]]
同样存在快捷的方式:
left, right = np.hsplit(grid, [2])
print(left)
print(right)
[[ 0 1]
[ 4 5]
[ 8 9]
[12 13]]
[[ 2 3]
[ 6 7]
[10 11]
[14 15]]
通用函数
一个数组和标量进行四则运算
我们先使用原生python来实现一个映射到所有数组元素取倒数的映射:
def cal_recip(values):
'''假设只有一个维度的数据'''
output = np.empty(len(values))
for idx, value in enumerate(values):
output[idx] = 1.0 / value
return output
big_array = np.random.randint(1, 100, size=1000000)
%timeit cal_recip(big_array)
4.35 s ± 110 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit (1.0 / big_array)
7.92 ms ± 16.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
可以看出执行效率,python慢是出了名的,但是很多时候是因为优化和写的不够python的原因导致的。
x = np.arange(0, 12).reshape(3, 4)
-(x**2 + 7) - 10
array([[ -17, -18, -21, -26],
[ -33, -42, -53, -66],
[ -81, -98, -117, -138]], dtype=int32)
科学计算
- 复数先不讨论
- 反/三角函数
- 值数/对数
x = np.array([-1, 2, -3, -5, 9])
abs(x) # py内置函数
array([1, 2, 3, 5, 9])
np.abs(x) # numpy是聚合了很多数学操作(通用函数, 插值算法等)
array([1, 2, 3, 5, 9])
三角函数
theta = np.linspace(0, np.pi, 3)
print('theta = ' , theta)
print('sin(theta) = ' , np.sin(theta))
print('cos(theta) = ' , np.cos(theta))
print('tan(theta) = ' , np.tan(theta))
theta = [0. 1.57079633 3.14159265]
sin(theta) = [0.0000000e+00 1.0000000e+00 1.2246468e-16]
cos(theta) = [ 1.000000e+00 6.123234e-17 -1.000000e+00]
tan(theta) = [ 0.00000000e+00 1.63312394e+16 -1.22464680e-16]
x = np.array([-1, 0, 1])
print('x = ', x)
print('arcsin(x) = ', np.arcsin(x))
print('arccos(x) = ', np.arccos(x))
print('arctan(x) = ', np.arctan(x))
x = [-1 0 1]
arcsin(x) = [-1.57079633 0. 1.57079633]
arccos(x) = [3.14159265 1.57079633 0. ]
arctan(x) = [-0.78539816 0. 0.78539816]
指数
x = np.array([1, 2, 3])
print('x = ', x)
print('e^x = ', np.exp(x))
print('2^x = ', np.exp2(x))
print('3^x = ', np.power(3, x))
x = [1 2 3]
e^x = [ 2.71828183 7.3890561 20.08553692]
2^x = [2. 4. 8.]
3^x = [ 3 9 27]
x = np.array([1, 2, 4, 10])
print('x = ', x)
print('ln(x) = ', np.log(x))
print('log2(x) = ', np.log2(x))
print('log10(x) = ', np.log10(x))
x = [ 1 2 4 10]
ln(x) = [0. 0.69314718 1.38629436 2.30258509]
log2(x) = [0. 1. 2. 3.32192809]
log10(x) = [0. 0.30103 0.60205999 1. ]
高级通用特性
将输出的结果输出到指定位置:
x = np.arange(0, 5)
y = np.empty(5)
np.multiply(x, 10, out=y)
y
array([ 0., 10., 20., 30., 40.])
聚合
x = np.arange(0, 6)
np.add.reduce(x)
15
from functools import reduce
# 较为朴素的实现方法
reduce(np.add, x)
15
多维度的聚合
例如sum函数和mean函数
x = np.array([[1, 2, 5], [3, 6, 8]])
x
array([[1, 2, 5],
[3, 6, 8]])
np.mean(x)
4.166666666666667
np.mean(x, axis=1)
array([2.66666667, 5.66666667])
np.sum(x)
25
np.sum(x, axis=1)
array([ 8, 17])
指定axis的做法在多维数组中是很常用的,在聚合操作中axis指定了聚合的维度
广播原则
一个简单的广播:
a = np.array([0, 1, 2])
b = np.array([4, 5, 6])
a + b
array([4, 6, 8])
a = np.arange(3)
b = np.arange(3).reshape(3, -1)
a + b
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
上面广播可视化图例
广播的规则
- 规则1:如果两个数组的维度不同,在最小维度的左边补上1
- 规则2:如果两个数组的形状在任何维度上都不一致,沿着维度为1的方向上补全匹配另一个数组的形状
- 规则3:如果两个数组的形状在任何一个维度上都不匹配另一个数组,并且没有任何一个维度等于1,那么就会引发异常
示例1:
a = np.ones((2, 3))
b = np.arange(3) # b->shape(1,3)->(2, 3)
a + b
array([[1., 2., 3.],
[1., 2., 3.]])
示例2:
a = np.ones((3, 2))
b = np.arange(3) # b->(1, 3)->(3, 3) != (3, 2)
a + b
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-73-e64aba6b854b> in <module>()
1 a = np.ones((3, 2))
2 b = np.arange(3) # b->(1, 3)->(3, 3) != (3, 2)
----> 3 a + b
ValueError: operands could not be broadcast together with shapes (3,2) (3,)
布尔逻辑索引
np.random.seed(0)
x = np.random.randint(10, size=(3, 4))
x
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 6]])
x < 6 # 逻辑判断得到布尔索引
array([[ True, True, True, True],
[False, False, True, True],
[ True, True, False, False]])
统计小于6的个数:
np.count_nonzero(x<6)
8
每行小于6的个数:
np.sum(x<6, axis=1)
array([4, 2, 2])
有没有小于8的值存在:
np.any(x < 8)
True
有没有大于9的值存在:
np.any(x>9)
False
取出所有1-8的数:
np.where((x>1) & (x<8), x, np.nan)
array([[ 5., nan, 3., 3.],
[ 7., nan, 3., 5.],
[ 2., 4., 7., 6.]])
x[(x>1) & (x<8)] # 不需求位置的简单版本
array([5, 3, 3, 7, 3, 5, 2, 4, 7, 6])
这里的逻辑运算必须使用位运算符,因为and or, not是对单个逻辑值的与或非判断。而位运算符才能处理逻辑组合的形式:
True or False
True
np.array([True, False, True]) & np.array([False, False, True]), [True, False, True] or [False, False, True]
(array([False, False, True]), [True, False, True])
数据科学中的array区别于原生python中的list