本篇博客所有示例使用Jupyter NoteBook演示。
Python数据分析系列笔记基于:利用Python进行数据分析(第2版)
目录
1.简介
2.将条件逻辑表述为数组运算
3.数学和统计方法
4.用于布尔型数组的方法
5.唯一化以及其他集合逻辑
6.排序
NumPy数组可以让你将许多中数据处理任务表述为简洁的数组表达式(不需要写循环).用数组表达式代替循环的做法,通常称为矢量化。一般说,矢量化数组运算要比等价的纯Python方式快上一两个数量级(甚至更多),尤其是各种数值计算。我们之前介绍的广播,就是一种针对矢量化计算的强大手段。
在一组值(网格型)上计算函数sqrt(x^2+y^2). np.meshgrid函数接受两个一维数组,并产生两个2维数组/矩阵(对应于两个一维数组中所有的(x,y)对):
points = np.arange(-5,5,0.01) #arange(start,end,step)
xs,ys = np.meshgrid(points,points)
print(xs)
print(ys)
xs和ys对应位置的数值构成网格中一个点的坐标:
z = np.sqrt(xs**2+ys**2)
print(z)
import matplotlib.pyplot as plt
%matplotlib inline
plt.imshow(z,cmap=plt.cm.gray)
plt.colorbar()
plt.title("Image plot of $sqrt{x^2+y^2}$ for a grid of values")
np.where函数是三元表达式x if condition else y的矢量化版本。
xarr = np.array([1.1,1.2,1.3,1.4,1.5])
yarr = np.array([2.1,2.2,2.3,2.4,2.5])
cond = np.array([True,False,True,True,False])
假设我们想要根据cond的值选取xarr和yarr的值:当cond为True时,选取xarr的值,否则从yarr中选取:
首先使用循环写法/列表推导式:
result = [x if c else y for x,y,c in zip(xarr,yarr,cond)]
print(result)
循环写法有一些问题。第一,他对大数组的处理速度不是很快(所有工作都是纯Python完成的).第二,无法用于多维数组。
使用np.where,矢量化运算,就可以把该功能写的非常简洁:
result = np.where(cond,xarr,yarr)
print(result)
np.where的第二个和第三个参数不必是数组,也可以是标量。在数据分析工作中,where通常用于根据另一个数组产生一个新的数组。
假设有一个由随机数组成的矩阵/2维数组,把所有的正值替换为2,所有负值替换为-2.利用np.where,就会非常简单:
arr = np.random.randn(4,4)
print(arr)
print(arr>0)
print(np.where(arr>0,2,-2))
使用np.where时可以将标量和数组结合起来。比如,可以用2替换arr中正值,其余值不变:
print(np.where(arr>0,2,arr))
传递给where的数组大小可以不相等,甚至可以是标量值。
可以通过一些数学函数对整个数组或某个轴向上的数据进行统计计算。sum、mean、std等聚合计算(aggregation,通常叫做约简(reduction))既可以当作数组的实例方法调用,也可以当作顶级NumPy函数使用。
arr = np.random.randn(5,4) #标准正太分布随机数
print(arr.mean()) #求数组中所有值的平均值
print(np.mean(arr))#与arr.mean()等价
print(arr.sum()) #求数组所有值的和 np.sum(arr)
mean和sum这类函数可以接受一个axis参数,用于在某个轴上进行统计,最终结果是一个少一维的数组:
print(arr.mean(1)) #沿1轴(水平方向/沿各个列的方向) 对每行的数据求平均
print(arr.sum(axis=0)) #沿0轴(竖直方向/沿各个行的方向) 对每列的数据求和
在多维数组中,累加函数(如cumsum)返回的是同样大小的数组,但是会根据每个维度的切片沿着标记轴计算部分聚类:
arr = np.array([[0,1,2],[3,4,5],[6,7,8]])
print(arr)
print(arr.cumsum(axis=0)) #沿0轴 (竖直方向/沿各个行的方向) 对每列的数据求累计和
print(arr.cumprod(axis=1)) #沿1轴(水平方向/沿各个列的方向) 对每行的数据求累计乘积
下表列出了全部的基本数组统计方法:
上面的那些方法中,布尔值会被强制转换为1(True)和0(False).因此,sum经常被用来对布尔型数组中的True计数:
arr = np.random.randn(100)
print((arr>0).sum())
还有两个方法any和all,他们对布尔数组很有用。any用于测试数组中是否存在一个或多个True,而all检查数组中所有值是否为True:
bools = np.array([False,True,False,False])
print(bools.any())
print(bools.all())
这两个方法也能用于非布尔型数组,所有非0元素会被当作True,0元素会被当作False。
NumPy提出了一些针对一维数组的基本集合运算。
最常用的是np.unique,用于找出一维数组中的唯一值并返回已排序的结果:
names = np.array(['bob','will','joe','bob','joe','will'])
print(np.unique(names))
ints = np.array([3,3,3,2,2,1,1,4,4,])
print(np.unique(ints))
#纯Python写法 与np.unique等价
print(sorted(set(ints)))
np.in1d函数用于测试一个数组中的值是否在另一个数组中,返回一个布尔型数组:
values = np.array([6,0,0,3,2,5,6])
print(np.in1d(values,[2,3,6]))
下表是NumPy中的集合函数:
跟Python内置 的列表一样,NumPy数组也可以通过sort函数进行就地排序:
arr = np.random.randn(6)
print(arr)
arr.sort() #对arr原地排序
print(arr)
多维数组可以沿任何一个轴向进行排序:
arr = np.random.randn(5,3)
print(arr)
arr.sort(axis=1) ##沿1轴(水平方向/沿各个列的方向) 对每行的数据排序
print(arr)
顶级函数np.sort返回的是数组已排序副本,而就地排序则会修改数组本身。计算数组分位数最简单的方法是对其进行排序,然后选取特定位置的值:
large_arr = np.random.randn(10)
print(large_arr)
print(np.sort(large_arr)) #large_arr.sort()会对数组本身进行改变 而np.sort(large_arr)会返回数组排好序的副本 而数组本身没变
print(large_arr)
print("----------------")
large_arr.sort()
print(large_arr)
print(large_arr[int(0.05*len(large_arr))]) #5%分位数
跟Python内置列表一样,数组的sort实例方法也是就地排序,对数组本身进行改变,即数组内容的重新排列不会产生新数组:
arr = np.random.randn(6)
print(arr)
arr.sort()
print(arr)
对数组进行就地排序时要特别注意,如果待排序的数组只是一个视图,则原始数组也会被修改:
arr = np.random.randn(3,5)
print(arr)
arr[:,0].sort() #arr[:,0]切片是arr的视图
print(arr) #源数组也改变了
相反,np.sort会为原数组创建一个已排序好的副本,他的参数和ndarray.sort一样:
arr = np.random.randn(5)
print(arr)
print(np.sort(arr))
print(arr)
这两种排序方法都可以接受一个axis参数,可以沿指定轴向对数据进行单独排序:
arr = np.random.randn(3,5)
print(arr)
arr.sort(1)
print(arr)
这两种方法都是对数组进行升序排列不会降序。其实这无所谓,因为数组切片会产生视图(不会产生副本,不需要其他计算工作)。许多Python用户都熟悉一个有关列表的小技巧:values[::-1]可以返回一个反序的列表,数组也是如此:
print(arr[:,::-1])
在数据分析中,常常需要根据一个或多个键对数据集进行排序。如:一个有关学生信息的数据表可能需要以姓和名进行排序(先姓后名),这是一个间接排序的例子。之后我们会学习Pandas,就会见到更多高级例子了。给定一个键或多个键,你就可以得到一个由整数组成的索引数组(索引器),其中索引值说明了数据在新顺序下的位置。argsort和np.lexsort是两个主要函数。
values = np.array([5,0,1,3,2])
indexer = values.argsort() #得到数组排序后的索引
print(indexer)
print(values[indexer]) #排好序的数组
接下来是一个更复杂的例子。只对数组的第一行进行排序:
arr = np.random.randn(3,5)
print(arr)
arr[0] = values
print(arr)
print(arr[:,arr[0].argsort()])
lexsort跟argsort差不多,只不过它可以一次性对多个键数组执行间接排序(字典序)。
假设我们想对一些以姓和名标识的数据进行排序:
first_names = np.array(['alice','bob','james','mike','jones'])
last_names = np.array(['zhang','zhao','qian','sun','li'])
sorter = np.lexsort((first_names,last_names))
print(sorter)
for f,l in zip(last_names[sorter],first_names[sorter]):
print(f+" "+l)
lexsort键的应用顺序是从最后一个传入的算起,last_names先于first_names被应用。
之后我们学习pandas中的Series和DataFrame的sort_index以及Series的order函数就是通过这些函数的变体(还要考虑缺失值)实现的。
稳定的排序算法会保持等价元素的相对位置。对于相对位置具有实际意义的那些间接排序而言,这一点很重要:
values = np.array(['2:first','2:second','1:first','1:second','1:third'])
key = np.array([2,2,1,1,1])
indexer = key.argsort(kind = 'mergesort')
print(indexer)
print(values[indexer]) #花哨索引 values.take(indexer)
mergesort是唯一的稳定排序,保证有O(nlogn)的性能(空间复杂度),但是其平均性能比默认的quicksort要差。
下表列出了可用的排序算法及其相关的性能指标,大体了解一下就好:
排序的目的之一是确定数组中最大或最小的元素。np.partition和np.argpartition,可以返回前k个最小元素或最小元素索引:
np.random.seed(12345)
arr = np.random.randn(20)
print(arr)
print(np.partition(arr,3))
np.partition(arr,3)返回数组中最小的3个元素,这三个元素之间没有特定的顺序。
np.argpartition会返回数组中前k小的元素的索引:
np.random.seed(12345)
arr = np.array([5,4,3,2,1])
indices = np.argpartition(arr,3)
print(indices)
print(arr[indices])
np.searchsorted是一个在有序数组上执行2分查找的数组方法,参数是在有序数组要插入的值,返回应该插入的位置,能保持原数组的有序性:
arr = np.array([0,1,7,12,15]) #有序数组 如果无序要先排序
print(arr.searchsorted(9)) #如果插入9 应该在哪个位置
print(np.searchsorted(arr,9)) #两种写法等价
print(arr.searchsorted([0,8,11,16])) #可以传入一组数
从上面的结果来看,对于元素0,searchsorted会返回0。这是因为默认返回相等值左侧索引:
arr = np.array([0,0,0,1,1])
print(arr.searchsorted([0,1]))
print(arr.searchsorted([0,1],side='right'))
searchsorted的另一个用法,假设我们有一个数据数组(值在0-10000之间),还有一个数组,我们希望用它将数据数组拆分开:
data = np.floor(np.random.uniform(0,10000,size=50))
bins = np.array([0,100,1000,5000,10000])
print(data)
labels = bins.searchsorted(data) #得到数据数组中每个数据所属的区间编号 如1表示[0,100)的编号
print(labels)
之后我们学习pandas后,可以利用groupby函数轻松的对数据集进行拆分/分组:
import pandas as pd
print(pd.Series(data).groupby(labels).mean()) #对data按照labels进行分组 对每一组求平均值