第 1.1 节 核心知识点#
预计学习时长: 35 分钟
本节目标:
- 会用
np.array()把列表变成数组,也知道二维数组怎么造;- 会用
zeros/ones/full生成指定形状的数组;- 分得清
arange()(数几个)和linspace()(间隔多大);- 会生成随机整数和随机浮点数,并且能锁住随机种子让结果复现;
- 会用
reshape()变形,并且知道它对元素总数有什么硬要求。
先说清楚一件事:NumPy 数组到底是什么#
在动手之前,先建立一个直觉,不然每个函数都会显得很突兀。
NumPy 数组(ndarray)就是「一堆同类型数字排成的方块」。
拿 Excel 打个比方:
- Excel 的一个单元格,可以放文字、数字、日期;
- NumPy 数组的每一个格子只能是数字(或者全是文字,或者全是布尔值),不能混着来。
这个限制听起来很烦,但换来的是巨大的速度优势——因为所有元素类型一样,NumPy 就能把它们打包成一块连续的内存,用 C 语言那种「批量算」的方式一次处理 thousands 个数字,而不是像 Python 循环那样一个一个调用。
一句话记住三个必查属性
arr.ndim # 几维 arr.shape # 每个维度多长 arr.dtype # 元素类型任何一个陌生的 NumPy 数组,先打这三行,八成就知道它是什么了。
顺便提一句常见的「兄弟」:
| 结构 | 是什么 | 长什么样 |
|---|---|---|
np.ndarray | 多维数组 | [120., 135., 150.] |
np.array(一维时) | 就是普通 ndarray | shape = (3,) |
Python list | 什么都能装的清单 | [120, 135, 150] |
数组和列表最大的区别:数组里所有元素类型必须一样,而且它支持「整体运算」——arr * 2 是每个数都乘 2,而 list * 2 是把列表复制两份。
知识点 1:从列表创建数组(Create Arrays from Lists)#
最常见的做法:np.array()#
import numpy as np
scores = np.array([80, 90, 75, 100])
print(scores)输出:
[ 80 90 75 100]注意输出里没有逗号——这是 NumPy 数组和 Python 列表最直观的区别。
看看它的属性:
print(scores.shape) # (4,) 一维,4 个元素
print(scores.ndim) # 1 一维
print(scores.dtype) # int64 整数
shape的读法(4,)读作「4 个,每个一维」。逗号是关键:
(4,)—— 一维数组,4 个元素;(4, 1)—— 二维数组,4 行 1 列;(4, 3)—— 二维数组,4 行 3 列。一维数组的
shape里那个逗号不能省,这就是为什么np.array([1,2,3]).shape是(3,)而不是(3)。
造二维数组:嵌套列表#
matrix = np.array([
[1, 2, 3],
[4, 5, 6],
])
print(matrix)输出:
[[1 2 3]
[4 5 6]]关键点:外层列表有几个元素,就是几行;每个内层列表有几个元素,就是几列。
print(matrix.shape) # (2, 3) 2 行 3 列
print(matrix.ndim) # 2三维数组:再套一层#
cube = np.array([
[[1, 2], [3, 4]],
[[5, 6], [7, 8]],
])
print(cube.shape) # (2, 2, 2)读法:「2 块,每块 2 行,每行 2 个」。
本课程要用的示例表#
后面 4 章一直用它,先建好:
# 5 个城市 × 4 个季度的销售额(万元)
sales = np.array([
[120.0, 135.0, 150.0, 165.0],
[ 80.0, 92.0, 88.0, 105.0],
[200.0, 210.0, 195.0, 230.0],
[ 60.0, 72.0, 85.0, 78.0],
[150.0, 145.0, 160.0, 175.0],
])
city_names = np.array(["北京", "上海", "广州", "深圳", "成都"])
quarter_names = np.array(["Q1", "Q2", "Q3", "Q4"])
print(sales.shape) # (5, 4)
print(sales.dtype) # float64数组和列表的运算差别有多大#
a_list = [1, 2, 3]
a_array = np.array([1, 2, 3])
print(a_list * 2) # [1, 2, 3, 1, 2, 3] ← 列表复制了两遍!
print(a_array * 2) # [2 4 6] ← 每个数都乘 2这是初学者最容易懵的地方。 列表的 * 是「重复」,数组的 * 是「逐元素相乘」。
再加一个:
print(a_list + a_list) # [1, 2, 3, 1, 2, 3] ← 拼接
print(a_array + a_array) # [2 4 6] ← 每个数加一遍从列表转数组的正确姿势
np.array(my_list) # ✅ np.asarray(my_list) # ✅ 如果已经是数组就不复制 np.array(my_list, dtype=float) # ✅ 顺便指定类型记住一句话:
np.array()接受「像数组一样的东西」,包括嵌套列表。
指定数据类型 dtype#
print(np.array([1, 2, 3], dtype=np.float64).dtype) # float64
print(np.array([1, 2, 3], dtype=np.int32).dtype) # int32
print(np.array([1, 2, 3], dtype=str).dtype) # <U1一个真实的坑
np.array([1, 2, 3.5]) # dtype 是 float64只要数组里有一个小数,整个数组都会变成浮点数。这是 NumPy「所有元素同类型」的直接后果。 你的金额列如果混进了一个
None或者空字符串,NumPy 会变成object类型(装 Python 对象的万能类型),后面所有运算都会变慢变难写。所以造数组前先确认数据是干净的。
知识点 2:创建全零数组与全一数组(Zeros and Ones)#
有时候你需要先开一块「空地」,往里面填东西。
np.zeros()#
print(np.zeros((3, 4)))输出:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]注意参数是元组 (3, 4)——括号不能省,写成 np.zeros(3, 4) 会被理解成两个参数,是另一个意思。
np.ones()#
print(np.ones((2, 3)))输出:
[[1. 1. 1.]
[1. 1. 1.]]np.full()#
指定一个固定值:
print(np.full((2, 3), 7.5))输出:
[[7.5 7.5 7.5]
[7.5 7.5 7.5]]三个函数的对比#
| 函数 | 填什么 | 例子 |
|---|---|---|
np.zeros(shape) | 全 0 | np.zeros((3, 4)) |
np.ones(shape) | 全 1 | np.ones((3, 4)) |
np.full(shape, value) | 全是你给的值 | np.full((3, 4), 9) |
默认类型是浮点数#
print(np.zeros((2,)).dtype) # float64
print(np.ones((2,)).dtype) # float64想要整数数组,显式写 dtype
np.zeros((3, 4), dtype=int) np.ones((3, 4), dtype=np.int32)
np.full((2, 2), 3, dtype=int)也是同理。
np.empty():只占位不赋值#
print(np.empty((2, 2)))输出(每次可能不同):
[[1.2e-34 3.4e+38]
[1.2e-34 3.4e+38]]empty 只是申请一块内存,里面的内容是上次用过的垃圾数据。
别把
empty当zeros用。 它的速度优势(省一次清零)在实际业务里几乎没用,但「读到随机垃圾值」的 bug 会让你查半天。初学阶段统一用zeros。
知识点 3:生成等差数列与等间隔序列(Arange and Linspace)#
np.arange():我告诉你数几个#
print(np.arange(5))输出:
[0 1 2 3 4]带起点和步长:
print(np.arange(0, 10, 2))输出:
[0 2 4 6 8]也可以只给终点:
print(np.arange(3, 7))输出:
[3 4 5 6]
arange()的结束值不包含在内(和 Python 的range()一样)。list(range(0, 10, 2)) # [0, 2, 4, 6, 8] np.arange(0, 10, 2) # [0 2 4 6 8]想数清楚「从 0 到 10(含 10)一共 11 个」这类问题,永远用
np.arange(0, 11),别用np.arange(0, 10)然后硬算。
np.linspace():我告诉你间隔多大#
print(np.linspace(0, 1, 5))输出:
[0. 0.25 0.5 0.75 1. ]参数含义:linspace(起点, 终点, 要几个数)。
它保证首尾一定包含、相邻间隔一定相等。所以:
print(np.linspace(0, 10, 5))[ 0. 2.5 5. 7.5 10. ]两个怎么选?#
np.arange(start, stop, step) | np.linspace(start, stop, num) | |
|---|---|---|
| 你指定的是 | 间隔(step) | 个数(num) |
| 包含终点吗 | 不包含 | 包含 |
| 输出类型 | 整数或浮点 | 一定是浮点 |
记忆方法:
arange—— 像range,「从 0 数到几」,数几个;linspace—— line space,把线段等分成几份。
np.arange(0, 10, 2) # 5 个数:0 2 4 6 8
np.linspace(0, 10, 5) # 5 个数:0 2.5 5 7.5 10还有一个细节:
linspace默认包含终点。如果你想「等分成 5 段」而不是「取 5 个点」,加endpoint=False:print(np.linspace(0, 1, 5, endpoint=False)) # [0. 0.2 0.4 0.6 0.8]
知识点 4:生成随机数组(Random Array Generation)#
先设个种子,后面所有数字才好对照 随机数默认每次跑都不一样,你没法判断「我看到的输出对不对」。 所以本文下面每个随机例子的第一行都写了
np.random.seed(42)—— 你原样复制过去跑,数字跟我写的一模一样。
随机整数:np.random.randint()#
np.random.seed(42)
print(np.random.randint(0, 10, size=5))输出:
[6 3 7 4 6]三个参数:randint(下限, 上限, 个数)。下限包含,上限不包含——和 range 一个规矩。
指定形状:
np.random.seed(42)
print(np.random.randint(1, 100, size=(3, 4)))输出:
[[52 93 15 72]
[61 21 83 87]
[75 75 88 24]]随机浮点数:np.random.rand()#
np.random.seed(42)
print(np.random.rand(3))输出:
[0.37454012 0.95071431 0.73199394]rand 生成的是 0 到 1 之间的浮点数,不给下限上限。
想要任意区间,用 uniform():
np.random.seed(42)
print(np.random.uniform(10, 20, size=5))输出:
[13.74540119 19.50714306 17.31993942 15.98658484 11.5601864 ]正态分布:np.random.normal()#
真实数据里很多指标是「中间多、两头少」的,比如人的身高、测量误差。这时候用正态分布更像真的:
np.random.seed(42)
print(np.random.normal(loc=100, scale=15, size=5))输出:
[107.4507123 97.92603548 109.71532807 122.84544785 96.48769938]loc是中心(这里是 100);scale是标准差(这里是 15)。
让随机结果可以复现:np.random.seed()#
上面加了 np.random.seed(42) 之后,每次运行的结果都一模一样。如果不设种子,每次跑出来的数字都会变——这在调试的时候很烦:代码跑对了,但结果每次都不同,你没法判断是逻辑错了还是随机性导致的。
用种子锁住随机序列:
np.random.seed(42)
print(np.random.rand(3))[0.37454012 0.95071431 0.73199394]再跑一次同样的种子,结果完全一样:
np.random.seed(42)
print(np.random.rand(3))[0.37454012 0.95071431 0.73199394]种子的价值:让 bug 可复现。 「我这个模型昨天跑出来 0.92,今天跑出来 0.71」——如果设了种子,两次结果一定一样,你就能确定是数据变了而不是「随机」。 写实验代码、做课程作业、跟别人协作,第一行就写
np.random.seed(42),这个习惯能省你很多时间。
新版 NumPy 的推荐写法 NumPy 1.17 之后推荐用独立的随机数生成器,好处是不同实验可以用不同的种子,互不干扰:
rng = np.random.default_rng(42) print(rng.random(3)) print(rng.integers(0, 10, size=5))老写法
np.random.seed()依然能用,只是全局的种子会互相影响。初学阶段两种都可以,记住「设了种子结果就固定」这一点就行。
知识点 5:调整数组形状(Reshaping Arrays)#
reshape():换个角度看同一堆数字#
nums = np.arange(12)
print(nums)[ 0 1 2 3 4 5 6 7 8 9 10 11]把它摆成 3 行 4 列:
print(nums.reshape(3, 4))输出:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]元素总数必须完全对得上。 12 个元素变 3×4 = 12 ✓。
如果对不上,会直接报错:
nums.reshape(5, 3)ValueError: cannot reshape array of size 12 into shape (5,3)
reshape的硬规则:元素总数必须守恒。 12 个元素能摆成的形状:(12,)、(6,2)、(4,3)、(3,4)、(2,6)、(2,2,3)…… 不能摆成(5,3)(15 个)。 报错了先算一下「总数 ÷ 行数」能不能整除。
用 -1 代替「我不知道」的那个维度#
12 个元素,你想摆成 3 行几列?不用算:
print(nums.reshape(3, -1).shape) # (3, 4)-1 的意思是「这一维你帮我算」,一个 -1 就够了,不能出现两个。
reshape 和 resize 的区别#
a = np.arange(6)
b = a.reshape(2, 3) # b 是新「视角」,元素还是那 6 个
print(b)[[0 1 2]
[3 4 5]]reshape 不会改变元素总数,也不会修改原数组(它只是换个姿势看同一批数据,底层内存还是那块)。
而 resize() 是真的往里塞东西或丢东西:
a = np.arange(6)
a.resize((3, 3)) # 元素不够会补 0,多了会丢
print(a)[[0 1 2]
[3 4 5]
[0 0 0]]初学阶段只用
reshape,别碰resize。resize会真的改数据(可能补零可能截断),一不小心就出 bug。
flatten() 和 ravel():拍扁成一条#
m = np.array([[1, 2], [3, 4]])
print(m.flatten()) # [1 2 3 4]
print(m.ravel()) # [1 2 3 4]两个输出一样,区别在内存:
flatten() | ravel() | |
|---|---|---|
| 返回 | 一定是一份新副本 | 通常是原数组的视图(能省则省) |
| 改结果会改原数组吗 | 不会 | 可能会 |
m = np.array([[1, 2], [3, 4]])
f = m.flatten()
f[0] = 99
print(m) # [[1 2] [3 4]] ← 原数组没变
r = m.ravel()
r[0] = 99
print(m) # [[99 2] [3 4]] ← 原数组被改了!需要副本就写
flatten(),或者显式.copy()。r = m.ravel().copy() # 保险写法这个「视图 / 副本」的区别是 NumPy 最大的坑之一,第 2 章会专门讲透。
顺便认识一下转置#
print(sales.T.shape) # (4, 5) 5 行 4 列 → 4 行 5 列.T 是「转置」的意思,把行和列对调。这个在第 4 章会经常用到。
本节小结#
- 三个必查属性:
ndim(几维)、shape(每维多长)、dtype(元素类型)。 np.array(列表)造数组,嵌套几层就是几维;np.array([1,2,3]).shape是(3,),逗号不能省。np.zeros(shape)/np.ones(shape)/np.full(shape, 值),默认是浮点数,要整数显式写dtype=int。np.arange(start, stop, step)数几个、不含终点;np.linspace(start, stop, num)分几份、含终点。- 随机数:
randint(低, 高, size)、rand(n)、uniform(低, 高, n)、normal(loc, scale, n)。 np.random.seed(42)让结果可复现——写实验代码时第一行就写它。reshape要求元素总数守恒,-1可以让 NumPy 帮你算;flatten()给副本,ravel()给视图。
下一节预告 下一节用 5 道练习把上面这些练熟——包括「把一维数组掰成二维」「用
linspace生成一整年的月份刻度」这种实战里天天干的活。