数据分析
numpy_2">numpy
Numpy 是一个开源的 Python 科学计算库,用于快速处理任意维度的数组。Numpy 支持常见的数组和矩阵操作,对于同样的数值计算任务,使用 NumPy 不仅代码要简洁的多,而且 NumPy 在性能上也远远优于原生 Python,至少是一到两个数量级的差距,而且数据量越大,NumPy 的优势就越明显。
NumPy 最为核心的数据类型是ndarray
,使用ndarray
可以处理一维、二维和多维数组,该对象相当于是一个快速而灵活的大数据容器。NumPy 底层代码使用 C 语言编写,解决了 GIL 的限制,ndarray
在存取数据的时候,数据与数据的地址都是连续的,这确保了可以进行高效率的批量操作,性能上远远优于 Python 中的list
;另一方面ndarray
对象提供了更多的方法来处理数据,尤其获取数据统计特征的方法,这些方法也是 Python 原生的list
没有的。
创建数组对象
方式1:使用list创建
import numpy as nparr1 = np.array([1, 2, 3, 4, 5])
print(arr1) # [1 2 3 4 5]
arr2 = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2)
"""
[[1 2 3][4 5 6]]"""
方式2:使用arange(start, stop, step)
# 使用arange函数,指定取值范围和跨度创建数组对象arr3 = np.arange(1,11,2) # [1, 11)类似左闭右开区间
方式3:使用linspace(start, stop, num_count)
使用linspace函数,用指定范围和元素个数创建数组对象,生成等差数列
arr4 = np.linspace(1,10, 5) # [1, 10] 类似全闭区间
方式4: 使用logspace[start_power, stop_power, num_count, base]
# 使用logspace函数,生成等比数列
arr5 = np.logspace(1, 4, num=5, base=2) # [base**start, base**stop]
等比数列的起始值是$2^1$,等比数列的终止值是$2^{4}$,num是元素的个数,base就是底数。
方式5: 使用fromstring
函数
arr6 = np.fromstring("1 2 3 4 5 6", sep=" ",dtype='i8') #通过fromstring函数从字符串提取数据创建数组对象
方式6:使用random模块
array8 = np.random.rand(10) #生成10个范围在[0,1)的随机小数
array9 = np.random.randint(1, 100, 10) #产生10个$[1, 100)$范围的随机整数
array10 = np.random.normal(50, 10, 20) #产生20个$\small{\mu=50}$,$\small{\sigma=10}$的正态分布随机数
array11 = np.random.rand(3, 4) # 产生$[0, 1)$范围的随机小数构成的3行4列的二维数组
array12 = np.random.randint(1, 100, (3, 4, 5)) # 产生$[1, 100)$范围的随机整数构成的三维数组
方法7:创建全0、全1或指定元素的数组
array13 = np.zeros((3, 4)) #3行4列全0数组
array14 = np.ones((3, 4)) #3行4列全1数组
array15 = np.full((3, 4), 10) #3行4列数组以10填充
array16 = np.eye(4) #单位矩阵
数组 属性
size:获取数组元素的个数
shape: 数组形状
dtype:获取元素的数据类型
ndim:获取数组的维度
数组的索引运算
和 Python 中的列表类似,NumPy 的ndarray
对象可以进行索引和切片操作,通过索引可以获取或修改数组中的元素,通过切片操作可以取出数组的一部分,我们把切片操作也称为切片索引。
普通索引
类似于 Python 中list
类型的索引运算。
arr2 = np.random.randint(1,100,(3,4))
print(arr2)
arr2[0] = [1,2,3,4]
print(arr2)
输出
[[80 11 57 7][22 20 11 51][82 16 51 69]][[ 1 2 3 4][22 20 11 51][82 16 51 69]]
切片索引
切片索引是形如[开始索引:结束索引:跨度]
的语法,通过指定开始索引(默认值无穷小)、结束索引(默认值无穷大)和跨度(默认值1),从数组中取出指定部分的元素并构成新的数组。因为开始索引、结束索引和步长都有默认值,所以它们都可以省略,如果不指定步长,第二个冒号也可以省略。一维数组的切片运算跟 Python 中的list
类型的切片非常类似,此处不再赘述,二维数组的切片可以参考下面的代码,相信非常容易理解。
arr2 = np.random.randint(1,100,(3,4))
print(arr2[:2, 1:])
print(arr2[::2, ::2])
输出
[[ 3 73 84][17 7 90]][[17 73][23 3]]
花式索引
式索引是用保存整数的数组充当一个数组的索引,这里所说的数组可以是 NumPy 的ndarray
,也可以是 Python 中list
、tuple
等可迭代类型,可以使用正向或负向索引。
arr2 = np.random.randint(1,100,(3,4))
print(arr2)
print("---------------")
print(arr2[[0,2],[1,3]])
print(arr2[[0,2],1])
输出
[[78 35 62 23][ 3 64 42 91][43 84 46 7]]
---------------
[35 7]
[35 84]
布尔索引
布尔索引就是通过保存布尔值的数组充当一个数组的索引,布尔值为True
的元素保留,布尔值为False
的元素不会被选中。布尔值的数组可以手动构造,也可以通过关系运算来产生。
arr2 = np.random.randint(1,100,(3,4))
print(arr2)
print(arr2>24)
print(~(arr2)>24) # ~运算符可以对布尔数组中的布尔值进行逻辑取反,也就是原来的True会变成False,原来的False会变成True
print((arr2>12) &(arr2 %2 == 0)) # &运算符可以作用于两个布尔数组,如果两个数组对应元素都是True,那么运算的结果就是True,否则就是False,该运算符的运算规则类似于 Python 中的 and 运算符,只不过作用的对象是两个布尔数组
# |运算符可以作用于两个布尔数组,如果两个数组对应元素都是False,那么运算的结果就是False,否则就是True,该运算符的运算规则类似于 Python 中的 or 运算符,只不过作用的对象是两个布尔数组。
输出
[[20 2 74 86][86 75 74 80][68 79 33 75]]
[[False False True True][ True True True True][ True True True True]]
[[False False False False][False False False False][False False False False]]
[[ True False True True][ True False True True][ True False False False]]
关于索引运算需要说明的是,切片索引虽然创建了新的数组对象,但是新数组和原数组共享了数组中的数据,简单的说,无论你通过新数组对象或原数组对象修改数组中的数据,修改的其实是内存中的同一块数据。花式索引和布尔索引也会创建新的数组对象,而且新数组复制了原数组的元素,新数组和原数组并不是共享数据的关系,这一点可以查看数组对象的base
属性,有兴趣的读者可以自行探索。
数组方法
描述统计信息主要包括数据的集中趋势、离散程度和频数分析等,其中集中趋势主要看均值和中位数,离散程度可以看极值、方差、标准差等,详细的内容大家可以阅读《统计思维系列课程01:解读数据》。
计算总和、均值和中位数。
array1 = np.random.randint(1, 10, 10) # [3 5 1 4 6 6 3 9 3 3]
print(array1.sum()) # 求和
print(array1.mean()) # 平均值
print(np.median(array1)) # 中位数
print(np.quantile(array1, 0.5)) #分位数
上面代码中的mean
、median
和quantile
分别是 NumPy 中计算算术平均值、中位数和分位数的函数,其中quantitle
函数的第二个参数设置为0.5表示计算50%分位数,也就是中位数。
极值、全距和四分位距离。
array1 = np.random.randint(1, 10, 10) # [3 5 1 4 6 6 3 9 3 3]
print(array1)
print(np.max(array1))
print(np.min(array1))
print(np.ptp(array1))
print(np.quantile(array1,[0.25, 0.75]))
方差、标准差和变异系数。
array1 = np.random.randint(1, 10, 10) # [3 5 1 4 6 6 3 9 3 3]
print(array1)
print(np.var(array1))
print((array1.std())/(array1.mean()))
箱线图
箱线图中的小圆圈用来表示离群点,也就是大于 Q 3 + 1.5 × I Q R \small{Q_3 + 1.5 \times IQR} Q3+1.5×IQR或小于 Q 1 − 1.5 × I Q R \small{Q_1 - 1.5 \times IQR} Q1−1.5×IQR的值。公式中的常量1.5
可以通过绘制箱线图的boxplot
函数的whis
参数进行修改,常用的值是1.5
和3
,修改为3
通常是为了标识出极度离群点。
需要说明的是,NumPy 的数组对象并没有提供计算几何平均值、调和平均值、去尾平均值等的方法,如果有这方面的需求,可以使用名为 scipy 的三方库,它的stats
模块中提供了这些函数。此外,该模块还提供了计算众数、变异系数、偏态、峰度的函数,代码如下所示。
from scipy import statsprint(np.mean(array1)) # 算术平均值
print(stats.gmean(array1)) # 几何平均值
print(stats.hmean(array1)) # 调和平均值
print(stats.tmean(array1, [10, 90])) # 去尾平均值
print(stats.variation(array1)) # 变异系数
print(stats.skew(array1)) # 偏态系数
print(stats.kurtosis(array1)) # 峰度系数
其他方法
-
all()
/any()
方法:判断数组是否所有元素都是True
/ 判断数组是否有为True
的元素。 -
astype()
方法:拷贝数组,并将数组中的元素转换为指定的类型。 -
reshape()
方法:调整数组对象的形状。 -
dump()
方法:保存数组到二进制文件中,可以通过 NumPy 中的load()
函数从保存的文件中加载数据创建数组。array1 = np.random.randint(1, 10, 10) # [3 5 1 4 6 6 3 9 3 3] print(array1) array1.dump('arr_data') print(np.load('arr_data',allow_pickle=True))
-
tofile()
方法:将数组对象写入文件中。 -
fill()
方法:向数组中填充指定的元素。 -
flatten()
方法:将多维数组扁平化为一维数组。 -
nonzero()
方法:返回非0元素的索引。 -
round()
方法:对数组中的元素做四舍五入操作。 -
sort()
方法:对数组进行就地排序。 -
swapaxes()
和
transpose()`方法:交换数组指定的轴和转置。 -
tolist()
方法:将数组转成 Python 中的list
。
数组 运算
使用 NumPy 最为方便的是当需要对数组元素进行运算时,不用编写循环代码遍历每个元素,所有的运算都会自动的矢量化。简单的说就是,NumPy 中的数学运算和数学函数会自动作用于数组中的每个成员。
数组与标量的运算
NumPy 的数组可以跟一个数值进行加、减、乘、除、求模、求幂等运算,对应的运算会作用到数组的每一个元素上,如下所示。
array1 = np.random.randint(1, 10, 10) # [3 5 1 4 6 6 3 9 3 3]
print(array1)
print(array1+10)
print(array1*2)
输出
[4 6 6 9 6 5 4 9 8 6]
[14 16 16 19 16 15 14 19 18 16]
[ 8 12 12 18 12 10 8 18 16 12]
除了上述的运算,关系运算也是没有问题的,之前讲布尔索引的时候已经遇到过了。
数组与数据的运算
NumPy 的数组跟数组也可以执行算术运算和关系运算,运算会作用于两个数组对应的元素上,这就要求两个数组的形状(shape
属性)要相同,如下所示。
array1 = np.random.randint(1, 5, 5) # [3 5 1 4 6 6 3 9 3 3]
array2 = np.random.randint(10,20,5)
print(f"array1===={array1}")
print(f"array2===={array2}")
print(array1+array2)
print(array2/array1)
print(array2 >array1)
输出
array1====[1 4 4 1 1]
array2====[16 11 14 16 12]
[17 15 18 17 13]
[16. 2.75 3.5 16. 12. ]
[ True True True True True]
通用一元函数
NumPy 中通用一元函数的参数是一个数组对象,函数会对数组进行元素级的处理,例如:sqrt
函数会对数组中的每个元素计算平方根,而log2
函数会对数组中的每个元素计算以2为底的对数,代码如下所示。
通用一元函数
函数 | 说明 |
---|---|
abs / fabs | 求绝对值的函数 |
sqrt | 求平方根的函数,相当于array ** 0.5 |
square | 求平方的函数,相当于array ** 2 |
exp | 计算 e x e^x ex的函数 |
log / log10 / log2 | 对数函数(e 为底 / 10 为底 / 2 为底) |
sign | 符号函数(1 - 正数;0 - 零;-1 - 负数) |
ceil / floor | 上取整 / 下取整 |
isnan | 返回布尔数组,NaN对应True ,非NaN对应False |
isfinite / isinf | 判断数值是否为无穷大的函数 |
cos / cosh / sin | 三角函数 |
sinh / tan / tanh | 三角函数 |
arccos / arccosh / arcsin | 反三角函数 |
arcsinh / arctan / arctanh | 反三角函数 |
rint / round | 四舍五入函数 |
通用二元函数
NumPy 中通用二元函数的参数是两个数组对象,函数会对两个数组中的对应元素进行运算,例如:maximum
函数会对两个数组中对应的元素找最大值,而power
函数会对两个数组中对应的元素进行求幂操作,代码如下所示。
array3 = np.array([[4, 5, 6], [7, 8, 9]])
array4 = np.array([[1, 2, 3], [3, 2, 1]])
print(np.maximum(array3, array4))
print(np.power(array3, array4))
输出
[[4 5 6][7 8 9]]
[[ 4 25 216][343 64 9]]
函数 | 说明 |
---|---|
add(x, y) / substract(x, y) | 加法函数 / 减法函数 |
multiply(x, y) / divide(x, y) | 乘法函数 / 除法函数 |
floor_divide(x, y) / mod(x, y) | 整除函数 / 求模函数 |
allclose(x, y) | 检查数组x 和y 元素是否几乎相等 |
power(x, y) | 数组 x x x的元素 x i x_i xi和数组 y y y的元素 y i y_i yi,计算 x i y i x_i^{y_i} xiyi |
maximum(x, y) / fmax(x, y) | 两两比较元素获取最大值 / 获取最大值(忽略NaN) |
minimum(x, y) / fmin(x, y) | 两两比较元素获取最小值 / 获取最小值(忽略NaN) |
dot(x, y) | 点积运算(数量积,通常记为 ⋅ \cdot ⋅,用于欧几里得空间(Euclidean space)) |
inner(x, y) | 内积运算(内积的含义要高于点积,点积相当于是内积在欧几里得空间 R n \mathbb{R}^n Rn的特例,而内积可以推广到赋范向量空间,只要它满足平行四边形法则即可) |
cross(x, y) | 叉积运算(向量积,通常记为 × \times ×,运算结果是一个向量) |
outer(x, y) | 外积运算(张量积,通常记为 ⨂ \bigotimes ⨂,运算结果通常是一个矩阵) |
intersect1d(x, y) | 计算x 和y 的交集,返回这些元素构成的有序数组 |
union1d(x, y) | 计算x 和y 的并集,返回这些元素构成的有序数组 |
in1d(x, y) | 返回由判断x 的元素是否在y 中得到的布尔值构成的数组 |
setdiff1d(x, y) | 计算x 和y 的差集,返回这些元素构成的数组 |
setxor1d(x, y) | 计算x 和y 的对称差,返回这些元素构成的数组 |
广播机制
上面数组运算的例子中,两个数组的形状(shape
属性)是完全相同的,我们再来研究一下,两个形状不同的数组是否可以直接做二元运算或使用通用二元函数进行运算,请看下面的例子
array1 = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]])
array2 = np.array([1, 2, 3])
array3 = np.array([[1], [2], [3], [4]])
print(array1+array2)
print(array1+ array3)
print(array2+array3)
输出
[[1 2 3][2 3 4][3 4 5][4 5 6]][[1 1 1][3 3 3][5 5 5][7 7 7]][[2 3 4][3 4 5][4 5 6][5 6 7]]
通过上面的例子,我们发现形状不同的数组仍然有机会进行二元运算,但这不代表任意形状的数组都可以进行二元运算。简单的说,只有两个数组后缘维度相同或者后缘维度不同但其中一个数组后缘维度为1时,广播机制才会被触发。通过广播机制,NumPy 将两个原本形状不相同的数组变成形状相同,才能进行二元运算。所谓后缘维度,指的是数组形状(shape
属性)从后往前看对应的部分,我们举例说明。
其他常用函数
除了上面讲到的函数外,NumPy 中还提供了很多用于处理数组的函数,ndarray
对象的很多方法也可以通过调用函数来实现,下表给出了一些常用的函数。
函数 | 说明 |
---|---|
unique | 去除数组重复元素,返回唯一元素构成的有序数组 |
copy | 返回拷贝数组得到的数组 |
sort | 返回数组元素排序后的拷贝 |
split / hsplit / vsplit | 将数组拆成若干个子数组 |
stack / hstack / vstack | 将多个数组堆叠成新数组 |
concatenate | 沿着指定的轴连接多个数组构成新数组 |
append / insert | 向数组末尾追加元素 / 在数组指定位置插入元素 |
argwhere | 找出数组中非0元素的位置 |
extract / select / where | 按照指定的条件从数组中抽取或处理数组元素 |
flip | 沿指定的轴翻转数组中的元素 |
fromregex | 通过读取文件和正则表达式解析获取数据创建数组对象 |
repeat / tile | 通过对元素的重复来创建新数组 |
roll | 沿指定轴对数组元素进行移位 |
resize | 重新调整数组的大小 |
place / put | 将数组中满足条件的元素/指定的元素替换为指定的值 |
partition | 用选定的元素对数组进行一次划分并返回划分后的数组 |
向量
向量(vector)也叫矢量,是一个同时具有大小和方向,且满足平行四边形法则的几何对象。与向量相对的概念叫标量或数量,标量只有大小,绝大多数情况下没有方向。我们通常用带箭头的线段来表示向量,在平面直角坐标系中的向量如下图所示。需要注意的是,向量是表达大小和方向的量,并没有规定起点和终点,所以相同的向量可以画在任意位置,例如下图中 w \boldsymbol{w} w和 v \boldsymbol{v} v两个向量并没有什么区别。