文章导读
- 课程难度:★★☆☆☆
- 重要度:★★★☆☆
- 预计学习时间:1小时
- 简介:本节主要讲解了python数据分析中数据运算的部分,包括:(1)基于基础一、二元的各种运算、三角函数的创建与运算、指对数的各种运算、调整精度的方法以及求向量和、差分及乘积等运算;(2)列表、numpy数组、Series和Dataframe数据的排序与查找问题。
- 重点涉及的函数和内容:np.round()、.sort()、sorted()、.argsort()、.sort_values()、.sort_index()、np.where()。
关于数学运算集中在numpy和scipy,而数据分析应用则主要集中在pandas。因此,我们在数据分析方面以DataFrame为素材讲解,在数学运算方面主要以numpy 数组为素材讲解,但有一些内置库的相似方法我们也会涉及。
我们首先将如下函数库引入:
import numpy as np
import pandas as pd
import scipy
我们一般用numpy实现数学运算领域的各种功能,因此在这里主要围绕numpy进行讲解。当然,有许多函数名称、调用方法和返回结果相同的函数,也存在于内置方法库和pandas中,我们这里也会进行一一地讲解。
首先提一下我们常使用的一些科学常数。这里包括自然对数的底数e
、欧拉常数γ
(调和级数和自然对数差值的极限)、以及π
。其余的np.nan
、np.inf
等在之前讲解过:
np.e
np.euler_gamma
np.pi
我们首先建立部分将在后续经常用于展示运算效果的数据:
x = np.array([100.45, 100.02, 100.0, 99.97, 100.05])
x1 = np.array([104.15, 98.72, 100.0, 102.25, 99.65])
y = np.array([1.639, 0.578, 1.031, 1.031, 0.59])
z = np.array([-4.5, -3, 2.5, 0.76, -0.5])
w = np.array([np.inf, np.nan, float('nan'), 1e10, 500])
x_int = np.array([13, 25, 24, 16, 33])
y_int = np.array([7, 5, 15, 20, 22])
x_bit = np.array([0b0101, 0b0110, 0b0101, 0b1010, 0b1101])
y_mv = np.array([1, 1, 1, 1, 1])
y_bit = np.array([0b0011, 0b1010, 0b0101, 0b1101, 0b0111])
x_bool = np.array([True, False, True, True, False])
y_bool = np.array([False, False, True, False, True])
x_array = np.array([[1, 2],[3, 4],[5, 6]]) # 3 * 2矩阵
y_array = np.array([[1, 2, 3, 4],[5, 6, 7, 8]]) # 2 * 4矩阵
我们首先介绍我们最为基础的一些运算,包括我们的加减乘除等等。这些运算可以分成几类,例如分成一元或二元运算、分成算数计算、逻辑计算,算数计算其中还有位运算等等。
常用的算术运算符包括:
+,-,*,/,** # 加、减、乘、除、乘方
//,% # 整数除法、整数除法的余数
&,|,~,^ # 与、或、非、亦或运算
<<,>> # 位运算
@ # 矩阵乘法
常用的比较运算符包括:
<,<= # 小于、小于等于
>,>= # 大于、大于等于
==,!= # 等于、不等于
一元运算符首先包括+
、-
和~
,分别表示正、负和按位取反:
+ x # np.positive(x)
- x # np.negative(x)
~x # np.invert(x_bit)
~x_bool # np.invert(x_bool)
执行上述代码,各自的运算结果如下:
[100.45 100.02 100. 99.97 100.05]
[-100.45 -100.02 -100. -99.97 -100.05]
[ -6 -7 -6 -11 -14]
[False True False False True]
其余一元运算函数包括:
np.modf(x) # 原值的整数和小数部分,形式为以两个ndarray数组组成的元组
np.fabs(x) # 原值的绝对值,另有np.absolute(x)
np.reciprocal(x) # 原值的倒数
np.sqrt(x) # 原值的平方根 square root,等同于x ** (1/2)
np.cbrt(x) # 原值的立方根 cube root,等同于x ** (1/3)
np.square(x) # 原值的平方,等同于x ** (2)
可以通过运算符完成的包括+
、-
、*
、/
、//
、%
和**
,这些运算符实现的运算含义和前面所描述的一致:
x + y # np.add(x, y)
x - y # np.subtract(x, y)
x * y # np.multiply(x, y)
x / y # np.divide(x, y), 另有np.true_divide(x, y)
x // y # np.floor_divide(x, y)
x % y # np.mod(x, y), 另有np.fmod(x, y)、np.remainder(x, y)
x ** y # np.power(x, y), 另有np.float_power(x, y)
x_array @ y_array # np.dot(x_array, y_array)
各自的运算结果如下:
[102.089 100.598 101.031 101.001 100.64 ]
[98.811 99.442 98.969 98.939 99.46 ]
[164.63755 57.81156 103.1 103.06907 59.0295 ]
[ 61.28737035 173.0449827 96.99321048 96.96411251 169.57627119]
[ 61. 173. 96. 96. 169.]
[0.471 0.026 1.024 0.994 0.34 ]
[1910.71517777 14.32353453 115.34532578 115.30964964 15.14007703]
[[11 14 17 20]
[23 30 37 44]
[35 46 57 68]]
其余二元算数函数包括:
np.divmod(x, y) # 同时返回相除的整数部分和余数,形式为以两个
# ndarray数组组成的元组,等于floor_divide和mod的合并
np.gcd(x_int, y_int) # 最大公约数,greatset common divisor
np.lcm(x_int, y_int) # 最小公倍数,lowest common multiple
np.maximum(x, x1) # 两个数组中对应元素的较大值,另有np.fmax(x, x1)
np.minimum(x, x1) # 两个数组中对应元素的较小值,另有np.fmin(x, x1)
各自的运算结果如下:
array([ 61., 173., 96., 96., 169.]), array([0.471, 0.026, 1.024, 0.994, 0.34 ]))
[ 1 5 3 4 11]
[ 91 25 120 80 66]
[104.15 100.02 100. 102.25 100.05]
[100.45 98.72 100. 99.97 99.65]
首先是**布尔运算中的逻辑运算符&
、|
、~
、^
:在这里插入代码片
x_bool & y_bool # np.logical_and(x_bool, y_bool)
x_bool | y_bool # np.logical_or(x_bool, y_bool)
~x_bool # np.logical_not(x_bool)
x_bool ^ y_bool # np.logical_xor(x_bool, y_bool)
各自的运算结果如下:
[False False True False False]
[ True False True True True]
[False True False False True]
[ True False False True True]
接下来是二进制位的逻辑运算符&
、|
、~
、^
,这些符号同样适用于二进制位。为便于展示,这里用二进制形式展示各二进制值:
[format(intvalue, '#b') for intvalue in (x_bit & y_bit)] # np.bitwise_and(x_bit, y_bit)
# 位运算的与运算
[format(intvalue, '#b') for intvalue in (x_bit | y_bit)] # np.bitwise_or(x_bit, y_bit)
# 位运算的或运算
[format(intvalue, '#b') for intvalue in (~x_bit)] # np.invert(x_bit)
# 位运算的非运算
# 原码为补码取反后加1
[format(intvalue, '#b') for intvalue in (x_bit ^ y_bit)] # np.bitwise_xor(x_bit, y_bit)
# 位运算的异或运算
各自的运算结果如下:
['0b1', '0b10', '0b101', '0b1000', '0b101']
['0b111', '0b1110', '0b101', '0b1111', '0b1111']
['-0b110', '-0b111', '-0b110', '-0b1011', '-0b1110']
['0b110', '0b1100', '0b0', '0b111', '0b1010']
二进制位的位运算符 <<
、>>
,为便于展示,这里用二进制形式展示各二进制值:
[format(intvalue, '#b') for intvalue in (x_bit)]
[format(intvalue, '#b') for intvalue in (x_bit << y_mv)] # np.left_shift(x_bit, y_mv)
# 将x向左移y位。
[format(intvalue, '#b') for intvalue in (x_bit >> y_mv)] # np.right_shift(x_bit, y_mv)
# 将x向右移y位
各自的运算结果如下:
['0b101', '0b110', '0b101', '0b1010', '0b1101']
['0b1010', '0b1100', '0b1010', '0b10100', '0b11010']
['0b10', '0b11', '0b10', '0b101', '0b110']
除了上述逻辑运算外,其他逻辑运算函数包括:
np.all(x_bool) # 是否所有的元素都为True
np.any(x_bool) # 是否至少有一个元素为True
np.isnan(w) # 原值是否为缺失值
np.isinf(w) # 原值是否为无穷大
# 另有np.isposinf(w)和np.isneginf(w),判断正/负无穷大
np.isfinite(w) # 原值是否不为空值或异常值
各自的运算结果如下:
False
True
[False True True False False]
[ True False False False False]
[False False False True True]
比较运算符主要包括<
、<=
、>
、>=
、==
、!=
,各自符号的含义和本节最初所提到的各类二元比较符号的含义相同:
x > x1 # np.greater(x, x1)
x >= x1 # np.greater_equal(x, x1)
x < x1 # np.less(x, x1)
x <= x1 # np.less_equal(x, x1)
x == x1 # np.equal(x, x1)
x != x1 # np.not_equal(x, x1)
各自的运算结果如下:
[False True False False True]
[False True True False True]
[ True False False True False]
[ True False True True False]
[False False True False False]
[ True True False True True]
对于赋值和算术运算结合在一起的运算符+=
、*=
等,除了 @
之外的所有的二元算术运算符都适用:
x_tmp1 = x.copy()
x_tmp2 = x.copy()
x_tmp3 = x_bool.copy()
x_tmp4 = x_bool.copy()
x_tmp1 *= 2
x_tmp2 *= y
x_tmp3 ^= True
x_tmp4 ^= y_bool
x_tmp1
x_tmp2
x_tmp3
x_tmp4
通过上述运算后,各自的结果如下:
[200.9 200.04 200. 199.94 200.1 ]
[164.63755 57.81156 103.1 103.06907 59.0295 ]
[False True False False True]
[ True False False True True]
关于算术运算和赋值运算结合的操作,有一件事需要提醒一下,这就是运算结果的数据类型由整个运算过程的所有对象决定。
三角函数的运算都是基于弧度的,角度的话需要运用函数库另外转换。在此,仍然建立我们的一些参数序列:
x_deg = np.array([0, 30, 45, 60, 90])
x_rad = np.array([0, np.pi/6, np.pi/4, np.pi/3, np.pi/2])
x_arcvalue1 = np.array([0, 0.5, np.sqrt(2)/2, np.sqrt(3)/2, 1])
x_arcvalue2 = np.array([0, np.sqrt(3)/3, 1, np.sqrt(3), 1e8])
x_side = np.array([2.5, 4.3, 1.0, 5.4, 3.3])
y_side = np.array([5.4, 1.8, 2.6, 3.3, 4.2])
我们以弧度的数组举例:
np.sin(x_rad)
np.cos(x_rad)
np.tan(x_rad)
输出结果如下:
array([0. , 0.5 , 0.70710678, 0.8660254 , 1. ])
array([1.00000000e+00, 8.66025404e-01, 7.07106781e-01, 5.00000000e-01,
6.12323400e-17])
array([0.00000000e+00, 5.77350269e-01, 1.00000000e+00, 1.73205081e+00,
1.63312394e+16])
反三角函数返回的值也是弧度,便于展示结果,在此将反三角函数的结果转成角度:
(np.arcsin(x_arcvalue1) / np.pi) * 180
(np.arccos(x_arcvalue1) / np.pi) * 180
(np.arctan(x_arcvalue2) / np.pi) * 180 # 这里可以看到这里的arctan的精度不高,
# 在1e8的情况下已经算成90度
输出结果如下:
array([ 0., 30., 45., 60., 90.])
array([90., 60., 45., 30., 0.])
array([ 0. , 30. , 45. , 60. , 89.99999943])
配套的,弧度和角度的互转如下:
np.deg2rad(x_deg) # 角度转弧度,等同的函数还有np.radians(x_deg)
np.rad2deg(x_rad) # 弧度转角度,等同的函数还有np.degrees(x_rad)
各自的运算结果如下:
array([0. , 0.52359878, 0.78539816, 1.04719755, 1.57079633])
array([ 0., 30., 45., 60., 90.])
我们此前提到的的 **
符号能支持我们的指数运算,但之前所提到的函数并没有支持取对数的运算。在此,我们逐一介绍一些以e、2、10位底数的指数和对数运算。我们先定义一些数据:
x = np.array([100.45, 100.02, 100.0, 99.97, 100.05])
y = np.array([1.639, 0.578, 1.031, 1.031, 0.59])
y1 = np.array([0.876, 1.452, 1.822, 0.932, 0.433])
x_int = np.array([13, 25, 24, 16, 33])
y_int = np.array([7, 5, 15, 20, 22])
np.exp(y_int) # 计算一个自然对数为底的指数值,等同于np.e ** y
np.expm1(y_int) # exp minus 1, 即计算一个自然对数为底的指数值减1
np.exp2(y_int) # 计算一个2为底的指数值
运算结果如下:
[ 2.71828183 7.3890561 20.08553692 54.59815003 148.4131591 ]
[ 1.71828183 6.3890561 19.08553692 53.59815003 147.4131591 ]
[ 2. 4. 8. 16. 32.]
np.log(x_int) # 以自然对数为底的对数
np.log1p(x_int) # log 1 plus, 以自然对数为底的对数值加1
np.log2(x_int) # 以2为底的对数
np.log10(x_int) # 以10为底的对数
各自的运算结果如下:
[2.56494936 3.21887582 3.17805383 2.77258872 3.49650756]
[2.63905733 3.25809654 3.21887582 2.83321334 3.52636052]
[3.70043972 4.64385619 4.5849625 4. 5.04439412]
[1.11394335 1.39794001 1.38021124 1.20411998 1.51851394]
指对数结合的一些其他函数:
np.logaddexp(y, y1) # 对于x1,x2, 计算log(exp(x1) + exp(x2)).
np.logaddexp(y, y1) # 对于x1,x2, 计算log2(exp2(x1) + exp2(x2)).
np.ldexp(x_int, y_int) # 返回 x * (2 ** y)
np.frexp(x_int) # 将原值分解为尾数和以2为底的指数
# 尾数 * ( 2 ** 指数) = 原值
各自的运算结果如下:
[2.02171871 1.8007389 2.19589957 1.67587181 1.20772515]
[2.02171871 1.8007389 2.19589957 1.67587181 1.20772515]
[1.664000e+03 8.000000e+02 7.864320e+05 1.677721e+07 1.384120e+08]
(array([0.8125 , 0.78125 , 0.75, 0.5, 0.515625]), array([4, 5, 5, 5, 6], dtype=int32))
精度调整的工具可能比我们想象的要多许多,我们这里做一个归纳。我们指出所需要的数据:
y = np.array([1.639, 0.578, 1.031, 1.031, 0.59])
z = np.array([-4.5, -3, 2.5, 0.76, -0.5])
z1 = np.array([-4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5])
在精度调整中,最重要的就是**np.round()
函数**,四舍五入保存至个位,有一个等同的函数np.around()
。这个函数可以补充一个数字指定保存到几位小数,默认是0也即整数,若为负数,则精度的小数点向左移动(即为-1时保存到10位、为-2时保存到百位):
np.round(y) # 等同的函数有np.around(y)
np.round(y, 1)
np.round(y, -1)
输出结果如下:
[2. 1. 1. 1. 1.]
[1.6 0.6 1. 1. 0.6]
[0. 0. 0. 0. 0.]
精度调整有一个需要注意的重要问题在于尾数是5(例如0.5)时候的情况,需要专门拿出来提示。它并不是我们默认的满5进1,而是在尾数为5时,前一位是奇数则进一,如果是偶数则舍去。而且,此时四舍五入计算所得的0是有符号的:
np.round(z1)
输出结果如下:
array([-4., -4., -2., -2., -0., 0., 2., 2., 4., 4.])
还有一些取原值的整数部分、小数部分等等的函数,归纳如下:
np.sign(z) # 符号(正为1,负为-1,0为0)
np.rint(z) # 最接近的整数
np.floor(z) # 不大于原值的最大整数
np.ceil(z) # 不小于原值的最小整数
np.trunc(z) # 截断小数部分,只留下整数部分
np.modf(z) # 同时保留原值的整数和小数部分
# 形式为以两个ndarray数组组成的元组
各自的运算结果如下:
[-1. -1. 1. 1. -1.]
[-4. -3. 2. 1. -0.]
[-5. -3. 2. 0. -1.]
[-4. -3. 3. 1. -0.]
[-4. -3. 2. 0. -0.]
(array([-0.5 , -0. , 0.5 , 0.76, -0.5 ]), array([-4., -3., 2., 0., -0.]))
这一块包含了向量内部元素的和、乘积与差分,同时也补充了二维和三维向量的叉积的计算方式。本节所需要的数据如下:
x = np.array([100.45, 100.02, 100.0, 99.97, 100.05])
y = np.array([1.639, 0.578, 1.031, 1.031, 0.59])
y_cross1 = np.array([1.639, 0.578, 1.031])
y_cross2 = np.array([0.578, 1.031, 1.031])
x_nan = np.array([100.45, 100.02, np.nan, 99.97, 100.05])
y_nan = np.array([1.639, np.nan, 1.031, np.nan, 0.59])
x_array = np.array([[1, 2],[3, 4],[5, 6]]) # 3 * 2矩阵
y_array = np.array([[1, 2, 3, 4],[5, 6, 7, 8]]) # 2 * 4矩阵
之前的函数,在处理nan
值时由于nan
的性质,返回结果会变成nan
值,对于求和和乘积,有能够忽略nan
值参与计算的方法,即在各种函数名之后加一个nan
,例如这里的求和函数np.sum()
,忽略nan
值的同等方法为np.nansum()
。之后讲到的统计函数也有能够忽略nan
值的统计函数,形式和这里的很类似:
np.sum(x) # 求向量各元素的和
np.sum(x_nan) # 当向量中元素存在nan值时,会返回nan值
np.nansum(x_nan) # 求向量各非nan值元素的和,忽略nan值
np.cumsum(x) # 求向量各元素的累积和,返回一个向量,
# 各元素的值是原向量内该位置元素及之前元素的和
np.cumsum(x_nan) # 但请注意这个函数并不忽略inf值,可以自行尝试一下
np.nancumsum(x_nan) # 求向量各非nan值元素的累积和,当遇到nan值将其看做0
各自的运算结果如下:
500.49
nan
400.49
[100.45 200.47 300.47 400.44 500.49]
[100.45 200.47 nan nan nan]
[100.45 200.47 200.47 300.44 400.49]
计算方式与刚刚的np.sum()
和np.cumsum()
相似。同样,也有忽略nan
值的对应方法:
np.prod(y) # 求向量各元素的积
np.prod(y_nan)
np.nanprod(y_nan) # 求向量各非nan值元素的积
np.cumprod(y) # 求向量各元素的累积乘积,返回一个向量,
# 各元素的值是原向量内该位置元素及之前元素的积
np.cumprod(y_nan) # 同上,不忽略inf值
np.nancumprod(y_nan) # 求向量各非nan值元素的累积乘积,当遇到nan值将其看做1
各自的运算结果如下:
0.5941226838005798
nan
0.9969873099999998
[1.639 0.947342 0.9767096 1.0069876 0.59412268]
[1.639 nan nan nan nan]
[1.639 1.639 1.689809 1.689809 0.99698731]
① np.diff()
np.diff()
函数主求差分,默认求一阶差分,即前一个值减去后一个值。可传一个补充参数描述求第n阶差分,这时就是一阶差分的迭代调用。调用方法及运算结果如下:
np.diff(x)
np.diff(y, 2) # 等同于np.diff(np.diff(y))
输出结果如下:
[-0.43 -0.02 -0.03 0.08]
[ 1.514 -0.453 -0.441]
② np.ediff1d()
np.ediff1d()
只求一阶差分,但是允许补充to_end
和to_begin
参数,作为返回序列的头和尾,头部和尾部的值并不参与到原始的求差分运算中。调用方法及运算结果如下:
np.ediff1d(y, 10, [15, 20]) # 位置上第二个参数是to_end,第三个参数是to_begin
输出结果如下:
array([ 15. , 20. , -1.061, 0.453, 0. , -0.441, 10. ])
③ np.gradient()
np.gradient()
函数求梯度,允许接收n维数组调用方法及运算结果如下:
np.gradient(y)
np.gradient(y_array)
输出结果如下:
[-1.061 -0.304 0.2265 -0.2205 -0.441 ]
[array([[4., 4., 4., 4.], [4., 4., 4., 4.]]), array([[1., 1., 1., 1.], [1., 1., 1., 1.]])]
④ np.cross()
np.cross()
,求向量叉积( 一般对应相乘求的是内积,叉积是a * b * sin(θ)
,求的是向量a和b构成平面的法向量。在空间向量中,三维叉积的运算结果是a=[a1,a2,a3],b=[b1,b2,b3],则a×b=[a2b3-a3b2, a3b1-a1b3, a1b2-a2b1]),接收的数组必须是二维或是三维的(对于高维数组,最后一维的维度是2或3)。调用方法及运算结果如下:
np.cross(y_cross1, y_cross2)
输出结果如下:
[-0.467043, -1.093891, 1.355725]
这里准备一些需要的数据:
x = np.array([100.45, 100.02, 100.0, 99.97, 100.05])
y = np.array([1.639, 0.578, 1.031, 1.031, 0.59])
x_nan = np.array([100.45, 100.02, np.nan, 99.97, 100.05])
y_nan = np.array([1.639, np.nan, 1.031, np.nan, 0.59])
xy_ary = np.array([x, y]) # 一个2 * 5的数组
xy_ary_nan = np.array([x_nan, y_nan])
列表的排序使用函数.sort()
和sorted()
,原排序结果是从小到大,比较规则按照各种元素(如数值、字符串、列表等)之间既有的比较规则.
.sort()
函数是一个列表的成员函数。它可以接两个参数,key
和reverse
。这个函数没有返回值,是在原列表上进行的修改。常用的参数如下:
key:它是一个函数的名字,表示对列表中的元素,用一个函数处理后的值做排序。
reverse:它是表示是否需要再做一次反序,将结果从大到小输出。
具体示例如下:
先讲解**reverse
参数**:
a = [1, -3, 5, 6, -9]
a.sort()
print(a)
a = [1, -3, 5, 6, -9]
a.sort(reverse = True)
print(a)
输出结果如下:
[-9, -3, 1, 5, 6]
[6, 5, 1, -3, -9]
再来讲解**key
参数**。它可以用我们的一个函数名,也可以是一个匿名函数的定义。在此我们单独定义一个求平方的函数作为传递给key的参数:
def a2(a):
return a ** 2
a = [1, -3, 5, 6, -9]
a.sort(key = a2, reverse = True) # 对a的平方值进行排序,同时将结果反序输出
# 传入key参数后,不是对原始值,
# 而是对列表元素在函数处理后的结果进行排序
print(a)
输出结果如下:
[-9, 6, 5, -3, 1]
与.sort()
不同,sorted()
是一个内置的方法,它能接收的参数、具体的用法都是一样的,但是它有返回值,而且不会改变原列表。我们参考刚刚的例子,重新使用这两个参数以展示其效果,因为用法是相似的,所以不再重复讲解:
a = ['one', 'two', 'three', 'four']
print(sorted(a)) # 将a升序排序
print(sorted(a, reverse = True)) # 将a降序排序
print(sorted(a, key = len)) # 将a根据字符串长度进行升序排序
print(a) # 验证这种修改并未影响原值
输出结果如下:
['four', 'one', 'three', 'two']
['two', 'three', 'one', 'four']
['one', 'two', 'four', 'three']
['one', 'two', 'three', 'four']
总之,.sort()
是列表的成员函数,用待排序的列表加点 . 访问,而sorted()
是内置方法,直接接一个待排序列表参数。sort
会改变原列表的值,没有返回值,而sorted
不会改变原值,返回值是排序完毕的列表。这些细节需要多多注意。
对值做排序最基础的排序工具是np.sort()
。一般可以用得上的是axis
参数,指示按照哪个维度排序,默认是最后一个维度,如果为None
则将数组压成一维进行排序。
这个函数结果产生的排序结果是升序的,没有参数可以指示让输出结果降序排列。当然在前面的课程里,怎么将数组反序我们已经学过了(访问[::-1])。同时,排序的结果里面缺失值会被放在最后面,我们举如下例子观察效果:
np.sort(xy_ary) # 对最末一个维度进行排序
np.sort(xy_ary, axis = 0) # 对axis为0的轴进行排序
np.sort(xy_ary, axis = None)[::-1] # 将所有数据压成一维,排序后进行反序
np.sort(xy_ary_nan) # 对带有nan值的数组进行排序
各自的运算结果如下:
[[ 99.97 100. 100.02 100.05 100.45 ]
[ 0.578 0.59 1.031 1.031 1.639]]
[[ 1.639 0.578 1.031 1.031 0.59 ]
[100.45 100.02 100. 99.97 100.05 ]]
[100.45 100.05 100.02 100. 99.97 1.639 1.031 1.031 0.59 0.578]
[[ 99.97 100.02 100.05 100.45 nan]
[ 0.59 1.031 1.639 nan nan]]
np.sort()
有两个近似的函数,一个是numpy数组的.sort()
方法,这是numpy数组的成员函数。这个函数会直接改变原值,无返回值。axis
参数近似np.sort
,但不能传递None
。
xy_ary_tmp = np.array([x, y]) # 重建一个为了防止xy_ary被修改
xy_ary_tmp.sort()
xy_ary_tmp
y_ary_tmp = np.array([x, y]) # 重建一个为了防止xy_ary被修改
xy_ary_tmp.sort(axis = 0)
xy_ary_tmp
输出结果如下:
[[ 99.97 100. 100.02 100.05 100.45 ]
[ 0.578 0.59 1.031 1.031 1.639]]
[[ 1.639 0.578 1.031 1.031 0.59 ]
[100.45 100.02 100. 99.97 100.05 ]]
其方法为对第一维数据进行排序,处理结果类似于np.sort(xy_ary, axis = 0)
np.msort(xy_ary)
输出结果如下:
array([[ 1.639, 0.578, 1.031, 1.031, 0.59 ],
[ 100.45 , 100.02 , 100. , 99.97 , 100.05 ]])
这个函数返回的是一个下标列表,下标列表的顺序是被自小到大重排的各个元素的下标。用原数组访问这个返回结果会得到原数组排序的结果,这就是它与数组排序的结果的关系。
与np.sort()
函数相同,缺失值的下标会被放在最后面;同时,axis
参数同样适用。
np.argsort(xy_ary)
np.argsort(xy_ary_nan)
np.argsort(xy_ary, axis = 0)
np.argsort(xy_ary, axis = None)
ind = np.argsort(x)
x[ind] # 用原数组访问返回结果,得到排序后的数组
各自的运算结果如下:
[[3 2 1 4 0]
[1 4 2 3 0]]
[[3 1 4 0 2]
[4 2 0 1 3]]
[[1 1 1 1 1]
[0 0 0 0 0]]
[6 9 7 8 5 3 2 1 4 0]
array([ 99.97, 100. , 100.02, 100.05, 100.45])
sort_values()
和.sort_index()
都是Series和DataFrame的成员函数,我们围绕DataFrame进行讲解。
该函数是对值进行排序,我们会主要使用如下参数:
by:指示排序的面向对象列表,它是第一个参数、必须给出。可以传一个字符串或一个列/行索引值的列表,函数按给出排序列表的先后顺序排序。
axis:指示排序是按行还是按列排序(默认是0即对列样本排序)。
ascending:指示排序是升序或是降序(默认为True即升序),可以传递一个与排序列表等长的布尔列表。
na_position:指示排序列表中如果是空值放在最前还是最后,可选'first', 'last',默认为最后('last')。
各种参数的调用方式举例如下。因为输出的文本量较大,为图简洁,在此不展示程序执行的最终结果:
frame_nan.sort_values(['210X1']) # 基本的按单列排序
frame_nan.sort_values(['210X1'], na_position = 'first') # 将空值置于最前面
frame_nan.sort_values([0], axis = 1) # 对行样本进行排序 # 传入行索引
frame_ori.sort_values(['TOOL', 'Value']) # 对多列进行排序
# 先排TOOL,再排Value
frame_ori.sort_values(['TOOL', 'Value'], ascending = False) # 以降序排列
与.sort_values()
对应的是.sort_index()
函数,它是对行或列的索引排序,因此它的参数没有by
。但其他的参数axis
、ascending
、na_position
都是意义一样的。我们将原来的列表按照打乱顺序,举两个简单的例子如下。同上,在此不展示程序执行的最终结果。
frame_indexsorted = frame_ori.sort_values(['TOOL', 'Value'])# 打乱原DataFrame索引
frame_indexsorted.sort_index() # 按行重排索引
frame_indexsorted.sort_index(axis = 1) # 按列重排索引
frame_indexsorted.sort_index(ascending = False) # 将索引降序排列
至此,我们对我们的排序做一个总结:
列表做排序时,可以自由地定义排序的规则(函数),但一次只能对列表中的各个元素排序,即不能按选择按列排等等。
numpy的数组支持不同的维度排,也可以找到排序顺序对应的元素下标,但是它无法定义排序方式,而且排序只能从小到大排。
pandas dataframe支持按照行或者列的值或者索引排序,排序结果的展示也比较美观,但是它也无法定义排序方法,而且只能排二维的数据。
各种排序方法都有其优缺点,所幸的是我们这些数据结构之间的互转方便,我们能在我们需要的时候进行灵活的运用。
这里所说的查找主要有两重意思,主要是面向下标的查找。一是找到最大值、最小值等等,它的值或是所在的下标,二是筛选出满足特定要求的值的元素下标。
我们仍旧建立本节中相应的元素如下:
x = np.array([100.45, 100.02, 100.0, 99.97, 100.05])
y = np.array([1.639, 0.578, 1.031, 1.031, 0.59])
x_nan = np.array([100.45, 100.02, np.nan, 99.97, 100.05])
y_nan = np.array([1.639, np.nan, 1.031, np.nan, 0.59])
x_0 = np.array([100.45, 100.02, 0, 99.97, 100.05])
y_0 = np.array([1.639, 0, 1.031, 0, 0.59])
xy_ary = np.array([x, y])
xy_ary_nan = np.array([x_nan, y_nan])
xy_ary_0 = np.array([x_0, y_0])
首先是查找最大值、最小值以及所在的下标,我们用np.argmax()
和np.argmin()
函数。类似于np.nansum()
、np.nanprod()
函数,这里也有np.nanargmax()
和np.nanargmin()
函数可以调用。
求最大值及其下标的函数整理如下:
np.max(xy_ary) # 计算整个数组最大值
np.max(xy_ary, axis = 1) # 以轴1分别计算最大值
np.argmax(xy_ary) # 数组转为1维后,最大值的下标
np.argmax(xy_ary, axis = 1) # 以轴1分别计算最大值的下标
np.nanmax(xy_ary_nan, axis = 1) # 以轴1分别计算最大值,忽略缺失值
np.nanargmax(xy_ary_nan, axis = 1) # 以轴1分别计算最大值下标,忽略缺失值
输出结果如下:
100.45
[100.45 1.639]
0
[0 0]
[100.45 1.639]
[0 0]
与求最大值相对应,配套的求最小值及最小值下标的函数整理如下,其调用方式及参数与最大值时是相同的:
np.min(xy_ary) # 计算整个数组最小值
np.min(xy_ary, axis = 1) # 以轴1分别计算最小值
np.argmin(xy_ary) # 数组转为1维后,最小值的下标
np.argmin(xy_ary, axis = 1) # 以轴1分别计算最小值的下标
np.nanmin(xy_ary_nan, axis = 1) # 以轴1分别计算最小值,忽略缺失值
np.nanargmin(xy_ary_nan, axis = 1) # 以轴1分别计算最小值下标,忽略缺失值
输出结果如下:
0.578
[99.97 0.578]
6
[3 1]
[99.97 0.59]
[3 4]
它接收的是一个表达式。我们曾经也学过基于布尔值的元素访问,下面用两个例子来比较np.where()
函数和基于布尔数组的访问效果:
ind_100p = np.where(xy_ary > 100) # 找出所有为0的元素
xy_ary[ind_100p] # 访问满足条件的所有元素
xy_ary[xy_ary > 100] # 基于布尔值筛选的表达式
# 基于布尔值的访问
输出结果如下:
[ 100.45 100.02 100.05]
[ 100.45 100.02 100.05]
上面返回的结果相同。但相比基于布尔数组的访问,应用np.where()
函数的一个好处是能够返回到数组的下标,因此可以根据一些具体的需求进行其他处理。
这里补充两个有两个判断元素不为0的函数,以及一个对不为0的函数的计数函数,它可以看成np.where()
和np.sum()
函数的一个综合运用:
np.nonzero(xy_ary_0) # 即np.where(xy_ary_0 != 0)
np.flatnonzero(xy_ary_0) # 即np.where(xy_ary_0.flatten() != 0)
np.count_nonzero(xy_ary_0, axis = 1) # 即np.sum(xy_ary_0 != 0, axis = 1)
各自的运算结果如下:
(array([0, 0, 0, 0, 1, 1, 1], dtype=int64), array([0, 1, 3, 4, 0, 2, 4], dtype=int64))
[0 1 3 4 5 7 9]
[4 3]
查找部分还有一项重要功能,即查找所有不重复的元素。我们此前学习过利用内置的方法set
,将数据做成集合来实现查找不重复值的目的,但那种方法对于大数据集运算时间较长。对于numpy数组,我们用np.unique()
方法;而对于Series或DataFrame,我们用其成员方法的.unique()
。它们都支持对去重,但效果因为numpy和pandas本身的限制略有不同:
a = np.array([1, 2, 'alpha', 'alpha', 2])
s = pd.Series([1, 2, 'alpha', 'alpha', 2])
np.unique(a)
s.unique()
运算结果如下:
['1' '2' 'alpha'] # numpy数组所有元素都是同一类型,因此数字会转成字符串
[1 2 'alpha'] # Series则没有这一限制,这里它的数据类型为Object
这种查找所有不重复项的功能,可以和《Python学习笔记(8-2):数据分析——数据预处理》中的pd.drop_duplicates
去重函数进行比较。
对于缺乏Python基础的同仁,可以通过免费专栏《Python学习笔记(基础)》从零开始学习Python
请始终相信“Done is better than perfect”
,不要过分追求完美,即刻行动就是最好的开始, 一个模块一个模块地积累和练习,必将有所收获。
还有其他的问题欢迎在评论区留言!
[版权申明] 非商业目的注明出处可自由转载,转载请标明出处!!!
博客:butterfly_701c