《利用Python进行数据分析》第四章-numpy基础

Numpy数组函数和数组数据处理

1.通用函数:快速的元素级数组函数

通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数。你可以将其看做简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。许多ufunc都是简单的元素级变体,如sqrt和exp:

In [1]: import numpy as np

In [2]: arr = np.arange(10)

In [3]: arr
Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [4]: np.sqrt(arr)
Out[4]: 
array([ 0. , 1. , 1.41421356, 1.73205081, 2. ,
2.23606798, 2.44948974, 2.64575131, 2.82842712, 3. ])

In [5]: np.exp(arr)
Out[5]: 
array([ 1.00000000e+00, 2.71828183e+00, 7.38905610e+00,
2.00855369e+01, 5.45981500e+01, 1.48413159e+02,
4.03428793e+02, 1.09663316e+03, 2.98095799e+03,
8.10308393e+03])

这些都是一元(unary)ufunc。另外一些(如add或maximum)接受2个数组(因此也叫二元(binary)

In [6]: x=np.random.randn(9)

In [7]: x
Out[7]: 
array([-0.25250282, 1.21724418, 0.25989955, 0.34517506, -0.78180916,
-1.25297812, -0.40412865, 1.44120584, 1.06813696])

In [8]: y=np.random.randn(9)

In [9]: np.maximum(x,y)#元素级最大值
Out[9]: 
array([ 1.6500482 , 1.21724418, 0.92500696, 0.34517506, 0.07987384,
0.43910019, -0.40412865, 1.44120584, 1.06813696])

但有些ufunc的确可以返回多个数组。modf就是一个例子,它是Python内置函数divmod的矢量化版本,用于浮点数数组的小数和整数部分。

In [10]: arr=random.randn(7)*6
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-10-786d70de738f> in <module>()
----> 1 arr=random.randn(7)*6

NameError: name 'random' is not defined

In [11]: arr=np.random.randn(8)*5
In [12]: arr
Out[12]: 
array([ 6.47267866, 2.36813813, 3.12836 , -4.60901158, 3.74744856,
8.55320503, -3.00223264, 3.70719592])

In [13]: np.modf(arr)
Out[13]: 
(array([ 0.47267866, 0.36813813, 0.12836 , -0.60901158, 0.74744856,
0.55320503, -0.00223264, 0.70719592]),
array([ 6., 2., 3., -4., 3., 8., -3., 3.]))

In [14]: num=np.arange(6)

In [15]: num
Out[15]: array([0, 1, 2, 3, 4, 5])

In [16]: np.square(num)
Out[16]: array([ 0, 1, 4, 9, 16, 25], dtype=int32)

In [18]: num2=np.array([-9,16,-8,49,-4,6])

In [19]: num2
Out[19]: array([-9, 16, -8, 49, -4, 6])

In [20]: np.abs(num2)
Out[20]: array([ 9, 16, 8, 49, 4, 6])

表4-3和表4-4分别列出了一些一元和二元ufunc。
image.png
image.png

2.利用数组进行数据处理

NumPy数组使你可以将许多种数据处理任务表述为简洁的数组表达式(否则需要编写循环)。用数组表达式代替循环的做法,通常被称为矢量化。一般来说,矢量化数组运算要比等价的纯Python方式快上一两个数量级(甚至更多),尤其是各种数值计算。
假设我们想要在一组值(网格型)上计算函数sqrt(x^2+y^2)。np.meshgrid函数接受两个一维数组,并产生两个二维矩阵(对应于两个数组中所有的(x,y)对)

In [22]: point=np.arange(-5,5,0.01)#1000个间隔相等的点

In [23]: xs,ys=np.meshgrid(point,point)

In [24]: ys
Out[24]: 
array([[-5. , -5. , -5. , ..., -5. , -5. , -5. ],
[-4.99, -4.99, -4.99, ..., -4.99, -4.99, -4.99],
[-4.98, -4.98, -4.98, ..., -4.98, -4.98, -4.98],
..., 
[ 4.97, 4.97, 4.97, ..., 4.97, 4.97, 4.97],
[ 4.98, 4.98, 4.98, ..., 4.98, 4.98, 4.98],
[ 4.99, 4.99, 4.99, ..., 4.99, 4.99, 4.99]])

现在对该函数的求值运算就好办了,把这两个数组当做两个浮点数那样编写表达式即可

In [25]: import matplotlib.pyplot as plt

In [26]: z=np.sqrt(xs ** 2 + ys ** 2)

In [27]: z
Out[27]: 
array([[ 7.07106781, 7.06400028, 7.05693985, ..., 7.04988652,
7.05693985, 7.06400028],
[ 7.06400028, 7.05692568, 7.04985815, ..., 7.04279774,
7.04985815, 7.05692568],
[ 7.05693985, 7.04985815, 7.04278354, ..., 7.03571603,
7.04278354, 7.04985815],
..., 
[ 7.04988652, 7.04279774, 7.03571603, ..., 7.0286414 ,
7.03571603, 7.04279774],
[ 7.05693985, 7.04985815, 7.04278354, ..., 7.03571603,
7.04278354, 7.04985815],
[ 7.06400028, 7.05692568, 7.04985815, ..., 7.04279774,
7.04985815, 7.05692568]])

In [28]: plt.imshow(z,cmap=plt.cm.gray);plt.colorbar()
Out[28]: <matplotlib.colorbar.Colorbar at 0x8ede240>

In [29]: plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
Out[29]: <matplotlib.text.Text at 0x97c8d68>

函数值(一个二维数组)的图形化结果如图所示。这张图我是用matplotlib的imshow函数创建的。根据网格对函数求值的结果:
image.png

3.将条件逻辑表述为数组运算

numpy.where函数是三元表达式x if condition else y的矢量化版本。假设我们有一个布尔数组和两个值数组:

In [30]: xarr=np.array([1.1,1.2,1.3,1.4,1.5])

In [31]: yarr=np.array([2.1, 2.2, 2.3, 2.4, 2.5])

In [32]: cond = np.array([True, False, True, True, False])

假设我们想要根据cond中的值选取xarr和yarr的值:当cond中的值为True时,选取xarr的值,否则从yarr中选取。列表推导式的写法应该如下所示:

In [34]: result = [(x if c else y)
    ...: for x,y,c in zip(xarr,yarr,cond)]

In [35]: result
Out[35]: [1.1000000000000001, 2.2000000000000002, 1.3, 1.3999999999999999, 2.5]

这有几个问题。第一,它对大数组的处理速度不是很快(因为所有工作都是由纯Python完成的)。第二,无法用于多维数组。若使用np.where,则可以将该功能写得非常简洁:

In [36]: result=np.where(cond,xarr,yarr)

In [37]: result
Out[37]: array([ 1.1, 2.2, 1.3, 1.4, 2.5])

np.where的第二个和第三个参数不必是数组,它们都可以是标量值。在数据分析工作中,where通常用于根据另一个数组而产生一个新的数组。假设有一个由随机数据组成的矩阵,你希望将所有正值替换为2,将所有负值替换为-2。若利用np.where,则会非常简单:

In [40]: arr=np.random.randn(4,4)

In [41]: arr
Out[41]: 
array([[ 3.05909033, -0.45293166, -2.17080782, 0.76203575],
[-0.59751362, -0.81526266, -0.23207938, -0.10778823],
[-1.11461048, 0.11587053, 0.06125199, 0.7875107 ],
[ 0.48923144, 1.45993583, 0.25710067, -0.59854274]])

In [42]: np.where(arr > 0,2,-2)
Out[42]: 
array([[ 2, -2, -2, 2],
[-2, -2, -2, -2],
[-2, 2, 2, 2],
[ 2, 2, 2, -2]])

In [43]: np.where(arr >0,2,arr) #只将正值设置为2
Out[43]: 
array([[ 2. , -0.45293166, -2.17080782, 2. ],
[-0.59751362, -0.81526266, -0.23207938, -0.10778823],
[-1.11461048, 2. , 2. , 2. ],
[ 2. , 2. , 2. , -0.59854274]])

传递给where的数组大小可以不相等,甚至可以是标量值。

4.数学和统计方法

可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。sum、mean以及标准差std等聚合计算(aggregation,通常叫做约简(reduction))既可以当做数组的实例方法调用,也可以当做顶级NumPy函数使用:

In [55]: arr=np.random.randn(5,4)#正态分布数据

In [56]: arr.mean()
Out[56]: -0.049889604534816778

In [57]: np.mean(arr)
Out[57]: -0.049889604534816778

In [58]: arr.sum()
Out[58]: -0.99779209069633557

mean和sum这类的函数可以接受一个axis参数(用于计算该轴向上的统计值),最终结果是一个少一维的数组:

In [59]: arr.mean(axis=1)
Out[59]: array([ 0.61692875, -0.13910609, -0.00297005, -0.50647901, -0.21782161])

In [60]: arr
Out[60]: 
array([[-2.13861877, 2.29349781, 1.43112711, 0.88170883],
[-0.65024191, 0.4604094 , -0.56090264, 0.19431079],
[ 0.46188982, -1.09339898, 0.62626328, -0.00663433],
[ 0.44629915, -1.46862793, -0.06877831, -0.93480896],
[-0.24556897, 0.60528118, -0.18854913, -1.04244953]])
In [61]: arr.sum(0)
Out[61]: array([-2.12624069, 0.79716149, 1.23916031, -0.9078732 ])

其他如cumsum和cumprod之类的方法则不聚合,而是产生一个由中间结果组成的数组:

In [62]: arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])

In [63]: arr.cumsum(0)
Out[63]: 
array([[ 0, 1, 2],
[ 3, 5, 7],
[ 9, 12, 15]], dtype=int32)

In [64]: arr.cumprod(1)
Out[64]: 
array([[ 0, 0, 0],
[ 3, 12, 60],
[ 6, 42, 336]], dtype=int32)


In [65]: np.max(arr)
Out[65]: 8

In [66]: np.argmax(arr)
Out[66]: 8

In [67]: np.sum(arr)
Out[67]: 36

image.png

5.用于布尔型数组的方法

在上面这些方法中,布尔值会被强制转换为1(True)和0(False)。因此,sum经常被用来对布尔型数组中的True值计数:

In [68]: arr=np.random.randn(100)

In [69]: (arr >0).sum() #正值的数量
Out[69]: 56

另外还有两个方法any和all,它们对布尔型数组非常有用。any用于测试数组中是否存在一个或多个True,而all则检查数组中所有值是否都是True:

In [70]: bools = np.array([False, False, True, False])

In [71]: bools.any()
Out[71]: True

In [72]: bools.all()
Out[72]: False

这两个方法也能用于非布尔型数组,所有非0元素将会被当做True。

6.排序

跟Python内置的列表类型一样,NumPy数组也可以通过sort方法就地排序:

In [73]: arr=np.random.randn(9)

In [74]: arr
Out[74]: 
array([-0.62889004, -1.81492359, 1.45620238, 1.51305779, 0.07868837,
-0.60457714, -1.60634838, -0.24632214, -0.2419341 ])

In [75]: arr.sort()

In [76]: arr
Out[76]: 
array([-1.81492359, -1.60634838, -0.62889004, -0.60457714, -0.24632214,
-0.2419341 , 0.07868837, 1.45620238, 1.51305779])

多维数组可以在任何一个轴向上进行排序,只需将轴编号传给sort即可

In [78]: arr3=np.random.randn(6,3)

In [79]: arr3
Out[79]: 
array([[-0.23956417, 0.84718833, 0.08335178],
[-0.55181716, 0.14174783, 0.02762056],
[-1.33281595, -0.22893889, -0.20462358],
[ 0.67883366, 0.9351854 , 1.31355628],
[ 0.42187437, -1.57850288, 0.39878659],
[-0.78516421, -0.00805874, 0.0210935 ]])
In [80]: arr3.sort(1)

In [81]: arr3
Out[81]: 
array([[-0.23956417, 0.08335178, 0.84718833],
[-0.55181716, 0.02762056, 0.14174783],
[-1.33281595, -0.22893889, -0.20462358],
[ 0.67883366, 0.9351854 , 1.31355628],
[-1.57850288, 0.39878659, 0.42187437],
[-0.78516421, -0.00805874, 0.0210935 ]])

顶级方法np.sort返回的是数组的已排序副本,而就地排序则会修改数组本身。计算数组分位数最简单的办法是对其进行排序,然后选取特定位置的值:

In [83]: large_arr = np.random.randn(1000)

In [84]: large_arr.sort()

In [85]: large_arr[int(0.05 * len(large_arr))] # 5%分位数
Out[85]: -1.603427813313238

7.唯一化以及其他的集合逻辑

NumPy提供了一些针对一维ndarray的基本集合运算。最常用的可能要数np.unique了,它用于找出数组中的唯一值并返回已排序的结果:

In [86]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])

In [87]: np.unique(names)
Out[87]: 
array(['Bob', 'Joe', 'Will'], 
dtype='<U4')

In [88]: ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])

In [89]: np.unique(ints)
Out[89]: array([1, 2, 3, 4])

拿跟np.unique等价的纯Python代码来对比一下

In [90]: sorted(set(names))
Out[90]: ['Bob', 'Joe', 'Will']

另一个函数np.in1d用于测试一个数组中的值在另一个数组中的成员资格,返回一个布尔型数组:

In [91]: values = np.array([6, 0, 0, 3, 2, 5, 6])

In [92]: np.in1d(values, [2, 0, 6])
Out[92]: array([ True, True, True, False, True, False, True], dtype=bool)

image.png

练习过程中涉及到数学和线性代数知识,需要去补充相关的知识,才能更好的理解。

猜你喜欢

转载自blog.csdn.net/lumugua/article/details/78767967