NumPy 基础(数组与向量化计算)——《利用Python进行数据分析》第四章阅读笔记
前言
上一次总结了该书第三章的内容,这次带来第四章,也就是NumPy 的基础部分,有关Numpy的历史发展等相关知识点,读者可直接参考该书的87、88页。从这两页,我们知道Numpy主要是在数组部分,其算法库是采用C编写,数据存储在连续内存块上,这一特性,使得Numpy数组方法在性能上要远优于传统的Python方法。
环境依旧是 Pycharm 2020.2.4 社区版, Anconda3
一 NumPy ndarray: 多维数组对象
作为NumPy的核心特征之一,ndarray即N维数组对象,是Python中一个快速、灵活的大型数据集容器。
用下面的随机NumPy数组代码,来感受一下
import numpy as np
data = np.random.randn(2, 3)
print(data)
print(data * 10)
print(data + data)
print(data.shape)
print(data.dtype)
1. 生成 ndarray
几个示例代码可以参考一下
import numpy as np
data1 = [6, 7.5, 8, 0, 1]
arr1 = np.array(data1)
print(arr1, arr1.dtype)
data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
arr2 = np.array(data2)
print(arr2, arr2.dtype)
print(arr2.ndim, arr1.ndim)
print(arr2.shape)
print(np.zeros((3, 6)))
print(np.empty((2, 3, 2)))
print(np.arange(12))
更多的数组生成函数可以参考该书的 表4-1
2. ndarray 的数据类型
数据类型,即dtype,是一个特殊对象,它包含了ndarray 需要为某一种类型数据申明的内存块信息(也称为元数据,即表示数据的数据)
import numpy as np
arr1 = np.array([1, 2, 3], dtype=np.float64)
arr2 = np.array([1, 2, 3], dtype=np.int32)
print(arr1.dtype)
print(arr2.dtype)
更多数组类型请参考该书的 表4-2
import numpy as np
arr1 = np.array([1, 2, 3], dtype=np.float64)
arr1 = arr1.astype(np.int32)
print(arr1.dtype)
numeric_strings = np.array(['1.25', '-9.6', '42'], dtype=np.string_)
numeric_strings = numeric_strings.astype(np.float64)
print(numeric_strings)
empty_unit32 = np.empty(8, dtype='u4')
print(empty_unit32, empty_unit32.dtype)
使用 astype时 会生成一个新的数组,即使你传入的dtype 与之前一样。
3. NumPy 数组算术
数组之所以重要,是因为它允许进行批量操作而无须进行任何 for 循环。这一特性 也称为 向量化。
import numpy as np
arr = np.array([[1., 2., 3.], [4., 5., 6.]])
print(arr * arr)
print(arr - arr)
print(1 / arr)
print(arr ** 0.5)
arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
print(arr2 > arr)
4. 基础索引与切片
NumPy 数组索引 方式有很多种
import numpy as np
arr = np.arange(10)
print(arr)
print(arr[5])
print(arr[5:8])
arr[5:8] = 12
print(arr)
arr_slice = arr[5:8]
arr_slice[1] = 12345
print(arr)
arr_slice[:] = 13
print(arr)
copy = arr[5:8].copy()
copy[:] = 0
print(copy)
print(arr)
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr2d[0])
print(arr2d[0][2], arr2d[0, 2])
arr2d[0][2] = 5
print(arr2d)
4.1 切片索引
其实这种索引方式在上面有所提及,对于高维数组,我们可以使用切片索引获取低维度的切片,并重新赋值
import numpy as np
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr2d[:2, 2])
arr2d[:2, :1] = 0
print(arr2d)
5. 布尔索引
其实就是bool值去索引, 需注意的是,布尔值数组长度应该和数组轴索引长度一致, 且可能是因为用C编写的原因,and和or关键字对布尔值数组没有用,需要使用 & 和 | 替代。
import numpy as np
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
print(names == 'Bob')
print(data[names == 'Bob'])
print(data[~(names == 'Bob')])
mask = (names == 'Bob') | (names == 'Will')
print(data[mask])
data[data < 0] = 0
print(data)
6. 神奇索引
其实就是用整数数组进行索引
import numpy as np
arr = np.empty((8, 4))
print(arr[[4, 3, 0, -6]])
arr = np.arange(32).reshape((8, 4))
print(arr)
print(arr[[1, 5, 7, 2], [0, 3, 1, 2]])
print(arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]])
7. 数组转置和换轴
import numpy as np
arr = np.arange(15).reshape((3, 5))
print(arr)
print(arr.T)
print(np.dot(arr, arr.T))
arr1 = np.arange(16).reshape((2, 2, 4))
print(arr1.transpose((1, 0, 2)))
print(arr1)
print(arr1.swapaxes(1, 2))
以上方法,都是返回数据的底层视图,而不生成新的数组,也不对数据进行复制
二 通用函数
通用函数,ufunc,是一种在 ndarray 数据中 进行逐元素操作的函数,某些简单函数接收一个或多个标量差值, 并产生 一个或多个 标量结果, 而通用函数就是对这些函数是对这些简单函数的向量化封装
import numpy as np
arr = np.arange(10)
print(np.sqrt(arr))
print(np.exp(arr))
x = np.random.randn(8)
y = np.random.randn(8)
print(x, y)
print(np.maximum(x, y))
reminder, whole_part = np.modf(x)
print(reminder)
print(whole_part)
arr = np.random.randn(7) * 5
print(arr)
更多的通用函数请参考该书的表 4-3,4-4
三 使用数组进行面向数组编程
使用Numpy数组,可以使用简单的数组表达式代替显示循环的方法, 这种方法称为向量化,向量化的数组操作会比纯Python的等价实现在速度上更快
import numpy as np
points = np.arange(-5, 5, 0.01)
xs, ys = np.meshgrid(points, points)
print(ys)
z = np.sqrt(xs ** 2 + ys ** 2)
print(z)
import matplotlib.pyplot as plt
plt.imshow(z, cmap=plt.cm.gray)
plt.colorbar()
plt.title("Image plot of $\sqrt{x^2 + y^2}$ for a grid of values")
plt.show()
1. 将条件逻辑作为数组操作
这里主要讲的是where函数, 其实就是三元表达式 x if condition else y 的向量化版本。如果直接使用三元表达式,其需要解释器解释python完成, 速度会很慢,且对高维数组需要修改代码,无法轻易复用。而使用哪np.where就可以非常简单的完成
import numpy as np
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])
result = [(x if c else y)
for x, y, c in zip(xarr, yarr, cond)]
print(result)
result = np.where(cond, xarr, yarr)
print(result)
arr = np.random.randn(4, 4)
print(arr)
print(arr > 0)
print(np.where(arr > 0, 2, -2))
print(np.where(arr > 0, 2, arr))
传递给 np.where 的数组既可以是同等大小的数组,也可以是标量
2. 数学和统计方法
import numpy as np
arr = np.random.randn(5, 4)
print(arr)
print(arr.mean())
print(np.mean(arr))
print(arr.mean(1))
print(arr.sum(axis=0))
arr = np.array([0, 1, 2, 3, 4, 5, 6, 7])
print(arr.cumsum())
arr = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
print(arr.cumsum(axis=0))
print(arr.cumprod(axis=1))
更多地请参考该书的表4-5,然后提一句,该书在该页有一个问题,axis=0 是表示每一行,axis=1 表示每一列
3. 布尔值数组的方法
布尔值会被强制为 1(True)和 0(False)
import numpy as np
arr = np.random.randn(100)
print((arr > 0).sum())
bools = np.array([False, False, True, True])
print(bools.any())
print(bools.all())
这些方法也适用非布尔值数组,所有非0的元素都按 True 处理
4. 排序
import numpy as np
arr = np.random.randn(6)
print(arr)
arr.sort()
print(arr)
arr = np.random.randn(5, 3)
print(arr)
arr.sort(1)
print(arr)
顶层的 np.sort 方法 返回的是已经排序好的数组拷贝,而不是对原数组继续拷贝
5. 唯一值与其他集合逻辑
NumPy 包含一些针对一维nadarray 的基础集合操作,一个常用的方法是 np.unique,返回的是数组中唯一值排序后形成的数组
import numpy as np
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
print(np.unique(names))
ints = np.array([3, 3, 3, 2, 2, 1, 1, 4, 4])
print(np.unique(ints))
另一个函数, np.in1d, 可以检查一个数组中的值是否存在另外一个数组中,并返回一个布尔值数组
import numpy as np
values = np.array([6, 0, 0, 3, 2, 5, 6])
print(np.in1d(values, [2, 3, 6]))
更多的ndarray数组的集合操作,请参考表 4-6
四 使用数组进行文件输入和输出
import numpy as np
arr = np.arange(10)
np.save('som_array', arr)
print(np.load('som_array.npy'))
np.savez('array_archive.npz', a=arr, b=arr)
arch = np.load('array_archive.npz')
print(arch['b'])
np.savez_compressed('array_compressed.npz', a=arr, b=arr)
print(np.load('array_compressed.npz'))
五 线性代数
这部分函数大多集合在了numpy的linalg中
import numpy as np
x = np.array([[1., 2., 3.], [4., 5., 6.]])
y = np.array([[6., 23.], [-1, 7], [8, 9]])
print(x.dot(y))
print(np.dot(x, y))
print(x @ y)
from numpy.linalg import inv, qr
X = np.random.randn(5, 5)
mat = X.T.dot(X)
print(inv(mat))
print(mat.dot(inv(mat)))
q, r = qr(mat)
print(r)
更多的线性代数操作方法,请参考表4-7
六 伪随机数生成
import numpy as np
samples = np.random.normal(size=(4, 4))
print(samples)
np.random.seed(1234)
rng = np.random.RandomState(1234)
print(rng.randn(10))
其他numpy.random 中的部分函数列表参考表 4-8
?
七 示例:随机漫步
一个简单的随机漫步
import matplotlib.pyplot as plt
import numpy as np
import random
position = 0
walk = [position]
steps = 1000
for i in range(steps):
step = 1 if random.randint(0, 1) else -1
position += step
walk.append(position)
nsteps = 1000
draws = np.random.randint(0, 2, size =nsteps)
steps = np.where(draws > 0, 1, -1)
walk = steps.cumsum()
print(walk.min(), walk.max())
print((np.abs(walk) >= 10).argmax())
plt.plot(walk[:100])
plt.show()
1. 一次性模拟多次随机漫步
import matplotlib.pyplot as plt
import numpy as np
import random
nwalks = 5000
nsteps = 1000
draws = np.random.randint(0, 2, size=(nwalks, nsteps))
steps = np.where(draws > 0, 1, -1)
walks = steps.cumsum(1)
print(walks)
print(walks.max(), walks.min())
hits30 = (np.abs(walks) >= 30).any(1)
print(hits30)
print(hits30.sum())
crossing_times = (np.abs(walks[hits30]) >= 30).argmax(1)
print(crossing_times)
print(crossing_times.mean())
小结
本书接下来的部分将会集中在打造 pandas 数据处理技能上,继续在基于数组的风格下处理数据
终于写完了,最近的效率真的低下
_
|