为什么用数组,数组是什么
Python 列表可以装任何东西,所以灵活,但处理数字很慢:每个元素都是一个独立对象。NumPy 提供 ndarray,一块内存里依次排列着同一种类型的数字。在编译代码中对整块数据运算,通常比 Python 循环快几十倍,占用的内存也少得多。几乎所有量化库(pandas、SciPy、scikit-learn)都建立在它之上。按惯例用 import numpy as np 导入。
import numpy as np
# Create arrays
prices = np.array([184.25, 185.0, 183.2])
print(prices) # [184.25 185. 183.2 ]
print(np.zeros(3)) # [0. 0. 0.]
print(np.arange(0, 10, 3)) # [0 3 6 9]
print(np.linspace(0, 1, 5)) # [0. 0.25 0.5 0.75 1. ]
# An array knows its shape
table = np.array([[1, 2, 3], [4, 5, 6]])
print(table.shape, table.ndim, table.size) # (2, 3) 2 6创建数组
np.array(列表) 把列表(二维则用列表的列表)转换成数组。np.zeros(n) 和 np.ones(n) 创建填充好的数组,np.zeros((行, 列)) 接收一个形状元组。np.arange(起点, 终点, 步长) 与 range 类似,但允许浮点步长,且不包含终点;由于舍入误差,更推荐 np.linspace(起点, 终点, num),它恰好给出 num 个等间距的点,并且包含两端。每个数组都有 .shape(各维大小)、.ndim(维数)和 .size(元素总数)。
import numpy as np
# arange with floats can surprise; linspace gives exactly the points you ask for
print(len(np.arange(0, 1, 0.1))) # 10 (1.0 is excluded)
print(len(np.linspace(0, 1, 11))) # 11 (both ends included)
# Shapes and sizes
grid = np.zeros((3, 4))
print(grid.shape, grid.ndim, grid.size) # (3, 4) 2 12
print(np.ones(2) * 5) # [5. 5.]dtype:整个数组只有一种类型
每个数组都有一个 dtype:int64(整数)、float64(小数)、bool,以及更小或更大的变体,如 float32、int8。NumPy 根据数据选择 dtype:全是整数得到 int64,只要有一个小数就全部变成 float64,而 np.nan(非数,缺失值的标记)是浮点数,所以会强制 float64。可以用 dtype=... 自己指定,或用返回新数组的 .astype(...) 转换。更小的 dtype 省内存(float32 只占 float64 的一半),但会牺牲精度。
import numpy as np
print(np.array([1, 2, 3]).dtype) # int64
print(np.array([1, 2.5, 3]).dtype) # float64 (one decimal upgrades everything)
print(np.array([True, False]).dtype) # bool
print(np.array([1, 2, np.nan])) # [ 1. 2. nan] (nan is a float)
# astype returns a new array; smaller dtypes use less memory
prices = np.zeros(1000)
print(prices.nbytes) # 8000
print(prices.astype(np.float32).nbytes) # 4000
print(np.array(["1.5", "2"], dtype=float)) # [1.5 2. ] (text converted to numbers)类型转换的陷阱:截断与溢出
用 .astype(int) 把小数转成整数会向零截断,而浮点数无法精确表示大多数小数,所以 19.99 * 100 是 1998.9999999999998,被截断成 1998,少了一分钱。要把价格转换成整数分,应先四舍五入:np.round(prices * 100).astype(np.int64)。整数 dtype 的范围也是固定的,并且会悄悄回绕:int8 的范围是 -128 到 127,所以 100 * 2 变成 -56。金额和计数请用 int64;并记住整数用 / 相除总是得到浮点数,而 // 向下取整。
import numpy as np
# Truncation: astype(int) cuts the decimals off
print(np.array([1.9, -1.9]).astype(int)) # [ 1 -1]
print(19.99 * 100, int(19.99 * 100)) # 1998.9999999999998 1998
print(np.round(np.array([19.99]) * 100).astype(np.int64)) # [1999]
# Overflow: an int8 wraps around silently
small = np.array([100, 50], dtype=np.int8)
print(small * 2) # [-56 100]
# / always gives floats, // floors
counts = np.array([1, 2, 3])
print(counts / 2, counts // 2) # [0.5 1. 1.5] [0 1 1].shape 和 .dtype:本该是 float64 却出现 int64,或形状是 (5, 1) 而不是 (5,),能解释大多数 bug。构建数组时优先用 NumPy 函数(np.linspace、np.asarray、astype),而不是 Python 循环;实验拒绝循环正是出于这个原因。np.array、zeros、ones、arange、linspace创建数组;.shape、.ndim、.size描述数组。- 每个数组一种 dtype:
int64、float64、bool;astype转换成新数组。 astype(int)会截断(先四舍五入);小整数 dtype 会悄悄回绕;nan强制使用浮点数。
下一节:数组的索引与切片,选取行、列、窗口以及满足条件的值。
实验:评分内容
- 用
make_grid生成等间距的点25 分 - 用
to_float_array把混合的原始值转为浮点数25 分 - 用
describe_array报告形状、大小与 dtype25 分 - 用
to_cents安全地把价格转换为整数分25 分