系列文章:Python 数据分析
NumPy(Numerical Python的简称)是Python数值计算最重要的基础包。大多数提供科学计算的包都是用NumPy的数组作为构建基础。
部分功能:多维数组 ndarray;标准数学函数(对整组数据快速运算不用 for 循环);读写磁盘工具;线性代数、随机生成以及傅里叶变换功能;可集成 C、C++ 代码的 API。
Numpy 本身没有很高级的数据分析功能,理解 Numpy 数组和面向数组的计算有助于高效使用 pandas 等工具。
n 维数组对象(即ndarray)是一个快速而灵活的大数据集容器,每个数组有 shape(一个表示各维度大小的元组) 和 dtype(一个用于说明数组数据类型的对象)
import numpy as np
data = np.random.randn(2, 3)
data * 10 # 矩阵的数乘
data + data # 矩阵的加法
data.shape # (2, 3)
data.dtype # dtype('float64')
array 函数:接受一切序列型的对象(包括其他数组),产生一个新的含有传入数据的 NumPy 数组。
import numpy as np
data = [1,2,3,4]
arr = np.array(data)
arr # array([1, 2, 3, 4])
data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
arr2 = np.array(data2)
arr2 # array([[1, 2, 3, 4], [5, 6, 7, 8]])
arr2.ndim # 2
arr2.shape # (2, 4)
arr2.dtype # dtype('int64')
一些数组创建函数,数据类型默认为 float64(浮点数),arange是Python内置函数range的数组版。
dtype(数据类型)是一个特殊的对象,它含有ndarray将一块内存解释为特定数据类型所需的信息。
Numpy数据类型
你可以通过 ndarray 的 astype 方法明确地将一个数组从一个 dtype 转换成另一个 dtype:
arr = np.array([1, 2, 3, 4, 5])
arr.dtype # dtype('int32')
float_arr = arr.astype(np.float64)
float_arr.dtype # dtype('float64')
创建数组指定 dtype
int_array = np.arange(10)
calibers = np.array([.22, .270, .357, .380, .44, .50], dtype=np.float64)
int_array.astype(calibers.dtype) # int_array.astype(calibers.dtype)
可以用简洁的类型代码来表示 dtype
empty_uint32 = np.empty(8, dtype='u4')
empty_uint32
# array([ 0, 1075314688, 0, 1075707904, 0.1075838976, 0, 1072693248], dtype=uint32)
调用astype总会创建一个新的数组(一个数据的备份),即使新的dtype
与旧的dtype相同。
不用编写循环即可对数据执行批量运算,矢量化。
大小相等的数组之间的任何算术运算都会将运算应用到元素级;数组与标量的算术运算会将标量值传播到各个元素;大小相同的数组之间的比较会生成布尔值数组:
arr = np.array([[1., 2., 3.], [4., 5., 6.]])
arr
array([[1., 2., 3.],
[4., 5., 6.]])
arr * arr
array([[ 1., 4., 9.],
[16., 25., 36.]])
3 * arr
array([[ 3., 6., 9.],
[12., 15., 18.]])
arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
arr2 > arr
array([[False, True, False],
[ True, False, True]], dtype=bool)
不同大小的数组之间的运算叫做广播(broadcasting)。
一维数组的索引很简单,看起来和 Python 列表的功能差不多。
arr = np.arange(10)
arr # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr[5] # 5
arr[5:8] # array([5, 6, 7])
与列表不同,数组切片是原始数组的视图,视图上的任何修改都会直接反映到原数组上。
arr[5:8] = 12
arr # array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
# 创建一个切片
arr_slice = arr[5 : 8]
arr_slice # array([12, 12, 12])
# 修改切片的值,变动也体现在原始数组中。
arr_slice[1] = 123456
arr # array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])
# 切片[ : ]会给数组中的所有值赋值
arr_slice[:] = 64
arr # array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
如果想得到 ndarray 切片的副本而不是视图,可以用 arr[5:8].copy()。
二维数组的索引位置的元素是一维数组。
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d[2] # array([7, 8, 9])
# 访问数组元素,下面两者等价
arr2d[0][2] # 3
arr2d[0, 2] # 3
二维数组的切片,切片是沿着一个轴向选取元素。
arr2d[:2]
array([[1, 2, 3],
[4, 5, 6]])
arr2d[:2, 1:]
array([[2, 3],
[5, 6]])
arr2d[1, :2] # 第二行的前两列
arr2d[:2, 2] # 第三列的前两行
arr2d[:, :1] # 只有冒号表示选取整个轴
对切片表达式的赋值操作会被扩散到整个选区。
布尔型索引就是把布尔型数组应用于 ndarray 的索引和切片。
假设每个名字对应 data 数组的一行,选出对应于名字“Bob”的所有行。
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
首先对 names 和字符串"Bob"的比较运算将会产生一个布尔型数组:
names == Bob
# array([ True, False, False, True, False, False, False], dtype=bool)
这个布尔型数组可用于数组索引:
data[names == 'Bob']
# array([[ 0.0929, 0.2817, 0.769 , 1.2464], [ 1.669 , -0.4386, -0.5397, 0.477 ]])
布尔型数组的长度必须跟被索引的轴长度一致。
布尔型数组与索引切片混合使用:
data[names == 'Bob', 2:]
data[names == 'Bob', 3]
选择除"Bob"以外的其他值,使用不等号 != 或~对条件进行否定:
names != 'Bob'
data[~(names == 'Bob')]
选取名字的组合需要布尔算数运算符,和为 & 或为 |:(Python关键字and和or在布尔型数组中无效)
mask = (names == 'Bob') | (names == 'Will')
data[mask]
通过布尔型索引选取数组中的数据,会创建数据的副本。
应用:通过布尔型数组设置值
# data 数组所有负值设为0
data[data < 0] = 0
# 设置整行或整列的值
data[names != 'Joe'] = 7
花式索引利用整数数组进行索引。
arr = np.empty((8, 4))
for i in range(8):
arr[i] = i
为了以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray即可:
arr[[4, 3, 0, 6]]
使用负数索引将会从末尾开始选取行:
arr[[-3, -5, -7]]
花式索引跟切片不一样,它总是将数据复制到新数组中。
数组不仅有transpose方法,还有一个特殊的T属性:
在进行矩阵计算时,经常需要用到该操作,比如利用np.dot计算矩阵内积:
np.dot(arr.T, arr)
对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置。
arr = np.arange(16).reshape((2, 2, 4))
arr.transpose((1, 0, 2))
这里,第一个轴被换成了第二个,第二个轴被换成了第一个,最后一个轴不变。
简单的转置可以使用.T,它其实就是进行轴对换而已。
ndarray还有一个 swapaxes 方法,它需要接受一对轴编号。(返回源数据的视图)
arr
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
arr.swapaxes(1, 2)
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])
通用函数(即ufunc)是一种对ndarray中的数据执行元素级运算的函数,可看作简单函数(接受一个或多个标量值,并产生一个或多个标量值)的矢量化包装器。
np.sqrt(arr)
np.maximum(x, y)
NumPy数组使你可以将许多种数据处理任务表述为简洁的数组表达式(否则需要编写循环)。用数组表达式代替循环的做法,通常被称为矢量化。
numpy.where函数是三元表达式x if condition else y的矢量化版本。
例如:根据 cond 中的值选取 xarr 和 yarr 的值:当 cond 中的值为 True 时,选取 xarr 的值,否则从 yarr 中选取。
# 列表推导式 缺点:大数组处理速度慢,无法用于多维数组
result = [(x if c else y) for x, y, c in zip(xarr, yarr, cond)]
# np.where
result = np.where(cond, xarr, yarr)
np.where的第二个和第三个参数不必是数组,它们都可以是标量值。在数据分析工作中,where 通常用于根据另一个数组而产生一个新的数组。
例如:把矩阵的所有正值替换为 2,所有负值替换为 -2
np.where(arr > 0, 2, -2)
也可以将标量和数组结合起来。
例如,用常数2替换arr中所有正的值:
np.where(arr > 0, 2, arr)
可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。
sum、mean以及标准差 std 既可以当做数组的实例方法调用,也可以当做顶级NumPy 函数使用。
arr = np.random.randn(5, 4)
arr.mean()
np.mean(arr)
arr.sum()
mean 和 sum 这类的函数可以接受一个 axis 选项参数,用于计算该轴向上的统计值,最终结果是一个少一维的数组:
arr.mean(axis=1)
arr.sum(axis=0)
sum 用来对布尔型数组中的True值计数:
arr = np.random.randn(100)
(arr > 0).sum() # 42
any 用于测试数组中是否存在一个或多个 True,而 all 则检查数组中所有值是否都是 True:
bools = np.array([False, False, True, False])
bools.any() # True
bools.all() # False
这两个方法也能用于非布尔型数组,所有非0元素将会被当做True
arr = np.random.randn(6)
arr.sort()
多维数组可以在任何一个轴向上进行排序,只需将轴编号传给 sort 即可:
arr = np.random.randn(5, 3)
arr.sort(1)
顶级方法np.sort返回的是数组的已排序副本,而就地排序则会修改数组本身。
例如:计算数组分位数
large_arr = np.random.randn(1000)
large_arr.sort()
large_arr[int(0.05 * len(large_arr))]
np.unique 用于找出数组中的唯一值并返回已排序的结果:
ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
np.unique(ints)
NumPy 能够读写磁盘上的文本数据或二进制数据。这一小节只讨论NumPy的内置二进制格式,因为更多的用户会使用 pandas 或其它工具加载文本或表格数据。
读写磁盘数据,数组是以未压缩的原始二进制格式保存在扩展名为 .npy 的文件中。如果文件路径末尾没有扩展名.npy,则该扩展名会被自动加上。
arr = np.arange(10)0
np.save('some_array', arr)
np.load('some_array.npy')
np.savez 可以将多个数组保存到一个未压缩文件 .npz 中,将数组以关键字参数的形式传入。加载.npz文件时,你会得到一个类似字典的对象,该对象会对各个数组进行延迟加载:
np.savez('array_archive.npz', a=arr, b=arr)
arch = np.load('array_archive.npz')
arch['b'] # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
将数据压缩
np.savez_compressed('arrays_compressed.npz', a=arr, b=arr)
线性代数(如矩阵乘法、矩阵分解、行列式以及其他方阵数学等)是任何数组库的重要组成部分。
NumPy 提供了一个用于矩阵乘法的 dot 函数(既是一个数组方法也是numpy命名空间中的一个函数):
x.dot(y)
np.dot(x, y)
numpy.linalg 中有一组标准的矩阵分解运算以及诸如求逆和行列式之类的函数:
from numpy.linalg import inv, qr
numpy.random 模块对 Python 内置的 random(一次生成一个样本值
)进行了补充,增加了一些用于高效生成多种概率分布的样本值的函数。
生成的样本值都是伪随机数,可以用NumPy的np.random.seed更改随机数生成种子。
samples = np.random.normal(size=(4, 4))
np.random.seed(1234)