NumPy 算术函数
在 NumPy 中,算术函数不仅仅是简单的加减乘除,它们背后有一套强大的机制(ufunc),而且有很多 Python 原生运算不支持的“隐藏技能”,比如处理复数、大数运算或者更精确的取整。
一般把 NumPy 的算术函数分为三类来讲:基础算术、进阶算术和数值处理。
基础算术函数 (Basic Arithmetic)
虽然可以直接用 +, -, *, /,但 NumPy 提供了对应的函数形式。使用函数的好处是可以通过参数控制输出(比如指定输出数组),且语义更清晰。
| | |
|---|
+ | np.add(x, y) | |
- | np.subtract(x, y) | |
* | np.multiply(x, y) | |
/ | np.divide(x, y) | |
** | np.power(x, y) | |
% | np.mod(x, y) | |
import numpy as np
a = np.array([2,4,5])
b = np.array([10,20,30])
# 使用函数形式
print("加法:", np.add(a, b)) # [12 24 35]
print("乘法:", np.multiply(a, b)) # [20 80 150]
print("幂运算:", np.power(a, 2)) # [ 4 16 25] (每个元素的平方)
print('-------------------')
重点:整除与取模在处理数据索引或分组时,这两个函数非常有用:
np.floor_divide(x, y) (对应 //):向下取整除法。- 例如:
np.floor_divide(5, 2) → 2
np.remainder(x, y) (对应 %):求余数。- 例如:
np.remainder(5, 2) → 1
进阶算术函数 (Advanced Arithmetic)
这些函数在数据分析中非常常见,尤其是处理指数增长或对数刻度时。
指数与对数
np.log10(x):计算以 10 为底的对数。np.log2(x):计算以 2 为底的对数 (计算机领域常用)。
x = np.array([1,2,3])
print("e^x:", np.exp(x)) # [2.718, 7.389, 20.085]
print("ln(x):", np.log(x)) # [0, 0.693, 1.098]
print('-------------------')
数值稳定性特供:log1p 和 expm1这是一个高阶技巧! 当你的数据中包含非常小的数字(接近 0)时,直接计算 log(1+x) 会因为浮点数精度问题丢失准确性。
np.log1p(x):计算 ,但在 很小时更精确。np.expm1(x):计算 ,同样用于处理小数。
# 当 x 极小时,log(1+x) 可能会变成 0,但 log1p 能算出准确值
x_tiny = 1e-10
print(np.log(1 + x_tiny)) # 可能输出 0.0 (精度丢失)
print(np.log1p(x_tiny)) # 输出 1e-10 (精确)
print('-------------------')
数值处理与取整 (Rounding & Modifying)
处理浮点数时,我们经常需要控制精度。NumPy 提供了一整套“取整”工具。
| | |
|---|
np.round(x) | | 4.0 |
np.ceil(x) | | 4.0 |
np.floor(x) | | 3.0 |
np.trunc(x) | | 3.0 |
np.rint(x) | | 4.0 |
arr = np.array([3.14, -3.14, 3.9, -3.9])
print("向下取整 (floor):", np.floor(arr)) # [ 3. -4. 3. -4.]
print("向上取整 (ceil):", np.ceil(arr)) # [ 4. -3. 4. -3.]
print("截断 (trunc):", np.trunc(arr)) # [ 3. -3. 3. -3.]
print('-------------------')
分离整数与小数:np.modf这个函数很有趣,它能一次性把数组的小数部分和整数部分分开,返回两个数组。
arr = np.array([1.25, 3.75, -2.5])
decimal_part, integer_part = np.modf(arr)
print("小数部分:", decimal_part) # [ 0.25 0.75 -0.5 ]
print("整数部分:", integer_part) # [ 1. 3. -2. ]
print('-------------------')
符号与绝对值 (Sign & Absolute)
np.fabs(x):计算绝对值(始终返回浮点数,速度稍快)。
arr = np.array([-10, 0, 10])
print("符号:", np.sign(arr)) # [-1 0 1]
print('-------------------')
聚合运算 (Accumulation)
除了计算单个元素,NumPy 还可以计算“累积”结果。
arr = np.array([1,2,3,4,5,6,7,8,9])
print("累积和:", np.cumsum(arr)) # (1, 1+2, 1+2+3, ...)
print("累积积:", np.cumprod(arr)) # (1, 1*2, 1*2*3, ...)
print('-------------------')
总结
| | |
|---|
| 基础 | np.add | |
| 整除/余数 | np.floor_divide | |
| 指数/对数 | np.exp | |
| 取整 | np.ceil | |
| 分离 | np.modf | |
| 累积 | np.cumsum | |
这些函数是 NumPy 数学能力的基石。熟练掌握它们,你在处理数值数据时就会像搭积木一样得心应手。
NumPy 的核心领域:统计函数
如果说数学函数是处理单个数据的工具,那么统计函数就是“上帝视角”。它们不再关注单个数字,而是把整个数组看作一个整体,提取出数据的特征(比如平均水平、波动大小、分布情况)。在数据分析中,这一步通常被称为聚合。
基础统计:描述数据的“中心”
这些函数最常用,用来回答“数据大概是多少”这个问题。
假设有这样一个二维数组(比如 3 个学生,2 门课的成绩):
import numpy as np
scores = np.array([[80, 90], # 学生A: 语文80, 数学90
[70, 60], # 学生B: 语文70, 数学60
[90, 80]]) # 学生C: 语文90, 数学80
求和与均值
print("全班总分:", np.sum(scores)) # 470
print("全班平均分:", np.mean(scores)) # 78.33
最大值与最小值
np.max(arr) / np.min(arr):找出极值。np.argmax(arr) / np.argmin(arr):找出极值的索引位置。
print("最高分:", np.max(scores)) # 90
print("最低分:", np.min(scores)) # 60
# 注意:argmax 返回的是“展平后”的索引
# 展平后是 [80, 90, 70, 60, 90, 80],索引 1 是 90
print("最高分的索引:", np.argmax(scores)) # 1
核心难点:理解 axis (轴)在处理二维数组时,新手最容易晕的就是 axis。 请记住这句心法:
指定哪个轴,就是把哪个轴“压扁”(压缩/折叠)。
axis=0:压扁“行”(垂直方向)。保留列的信息(即:按列算)。axis=1:压扁“列”(水平方向)。保留行的信息(即:按行算)。
# 场景 A:我想算“每门课”的平均分(保留科目/列的信息)
# 动作:把“行”压扁 (axis=0)
print("每门课平均分:", np.mean(scores, axis=0))
# 输出: [80. 76.66] -> (语文平均80, 数学平均76.66)
# 场景 B:我想算“每个学生”的平均分(保留学生/行的信息)
# 动作:把“列”压扁 (axis=1)
print("每个学生平均分:", np.mean(scores, axis=1))
# 输出: [85. 65. 85.] -> (学生A平均85, 学生B平均65...)
print('-------------------')
离散程度:描述数据的“波动”
平均值只能代表中心,不能代表稳定性。比如两个班平均分都是 75,但一个班大家都是 75 分,另一个班一半考 100 一半考 50。这就需要方差和标准差。
np.var(arr):方差。衡量数据离平均值有多远。np.std(arr):标准差。方差的平方根。因为方差的单位是“平方分”,不好理解,开根号变回“分”,更符合直觉。
stable = np.array([74, 75, 76]) # 成绩很稳
volatile = np.array([50, 75, 100]) # 成绩大起大落
print("稳定班方差:", np.var(stable)) # 0.66 (很小)
print("波动班方差:", np.var(volatile)) # 416.66 (很大)
print('-------------------')
位置统计:中位数与百分位数
当数据中有异常值(比如马云的工资和我的工资放在一起算平均)时,平均值会失真。这时要用中位数。
np.median(arr):中位数。排序后位于中间的数。np.percentile(arr, q):百分位数。
income = np.array([3000, 4000, 5000, 6000, 1000000]) # 混入了一个亿万富翁
print("平均收入:", np.mean(income)) # 203600 (被拉高了,没参考价值)
print("中位数收入:", np.median(income)) # 5000 (更能代表普通人)
# 查看 75% 分位数(比 75% 的人收入都高是多少?)
print("75%分位收入:", np.percentile(income, 75)) # 6000
print('-------------------')
累积统计
如果你想看“累计销量”或“复利增长”,用这两个函数。
sales = np.array([1,2,3,4,5]) # 1月卖1万,2月卖2万...
print("累积销量:", np.cumsum(sales))
# 输出: [1 3 6 10 15] -> (1月累计1, 2月累计1+2=3, 3月累计1+2+3=6...)
growth = np.array([1.1,1.1,1.1]) # 每年增长 1.1 倍
print("复利增长:", np.cumprod(growth))
# 输出: [1.1 1.21 1.331]
print('-------------------')
避坑指南:处理 NaN (空值)
这是实际工作中最最重要的一点。如果你的数组里有一个 np.nan(比如某次考试缺考),普通的统计函数会直接返回 nan,导致结果全废。
data = np.array([10, 20, np.nan, 40])
print("普通均值:", np.mean(data)) # nan (结果不可用!)
解决方案:使用 nan 专用函数NumPy 提供了一套忽略 nan 的函数,通常带有 nan 前缀。
| | |
|---|
np.sum() | np.nansum() | |
np.mean() | np.nanmean() | |
np.std() | np.nanstd() | |
np.max() | np.nanmax() | |
print("忽略空值均值:", np.nanmean(data)) # 23.33 (正确计算了 10, 20, 40 的平均)
print('-------------------')
总结速查表
| | |
|---|
| 中心趋势 | np.mean() | axis |
| 极值 | np.max() | axis |
| 波动/离散 | np.var() | |
| 位置/分位 | np.percentile(arr, q) | q |
| 累积 | np.cumsum() | |
| 处理空值 | np.nanmean() | |
NumPy 排序函数
在数据分析中,排序是最基础的操作之一。你可能需要找出销售额最高的前 10 名,或者按时间顺序排列日志。NumPy 的排序功能非常强大,不仅速度快(基于 C 语言实现),而且支持多维数组和特殊的排序逻辑。
基础排序:np.sort()
这是最常用的函数。它的特点是“不修改原数组”,而是返回一个排序后的副本。
import numpy as np
arr = np.array([2,5,1,8,3])
# 返回一个新的排序数组
sorted_arr = np.sort(arr)
print("原数组:", arr) # [2 5 1 8 3] (原数组没变)
print("排序后:", sorted_arr) # [1 2 3 5 8]
print('-------------------')
原地排序:ndarray.sort()如果你处理的是超大数组,为了节省内存,不想复制一份,可以直接调用数组对象的方法。这会直接修改原数组。
arr.sort()
print("原地排序后:", arr) # [1 2 3 5 8] (原数组被修改了)
print('-------------------')
二维数组排序:理解 axis
在处理表格数据时,你需要决定是“按行排”还是“按列排”。
心法回顾:
axis=1 (默认):沿着列的方向排(行内排序)。每一行自己排自己的序。axis=0:沿着行的方向排(列内排序)。每一列自己排自己的序。
matrix = np.array([[4,5,3],
[8,1,7]])
# 1. 默认 axis=1 (每一行内部从小到大排)
print("按行排序:\n", np.sort(matrix, axis=1))
# 输出:
# [[3 4 5]
# [1 7 8]]
# 2. axis=0 (每一列内部从小到大排)
print("按列排序:\n", np.sort(matrix, axis=0))
# 输出:
# [[4 1 3]
# [8 5 7]]
注意:这种排序会打乱行与行之间的关联。比如“张三的语文和数学成绩”可能会被拆散,张三的语文变成了李四的。所以在处理结构化数据(DataFrame)时要小心。
间接排序:np.argsort()
这是数据分析中的神技。argsort 返回的不是排序后的值,而是排序后的索引(位置)。
场景:你想知道“谁是考第一名的学生”,而不仅仅是“最高分是多少”。
scores = np.array([85,92,78,95])
# 返回索引数组
indices = np.argsort(scores)
print("排序后的索引:", indices)
# 输出: [2 0 1 3]
# 解释:
# 最小的是索引2 (78分)
# 第二小的是索引0 (85分)
# ...最大的是索引3 (95分)
# 利用索引还原排序后的数组
print("还原后的数组:", scores[indices])
# 输出: [78 85 92 95]
print('-------------------')
实战应用:取前 N 名如果你想取分数最高的前 2 名:
# argsort 是从小到大排,所以取最后两个索引 [-2:]
top2_indices = np.argsort(scores)[-2:]
print("前两名的索引:", top2_indices) # [1 3]
print("前两名的分数:", scores[top2_indices]) # [92 95]
print('-------------------')
部分排序:np.partition()
如果你只需要找“前 3 名”或者“最小的 5 个数”,对整个数组进行完全排序其实有点浪费算力。
np.partition(arr, k) 可以保证第 k 小的元素在正确的位置上,比它小的都在它左边,比它大的都在它右边(但左右两边内部不一定有序)。
arr = np.array([7,2,1,5,4,9])
# 找第 3 小的数 (k=3, 索引从0开始,实际是找第3小的位置)
# 结果保证前3个元素是最小的3个
result = np.partition(arr, 3)
print("部分排序:", result)
# 可能的输出: [2 1 4 5 9 7]
# 解释: 前3个 是最小的三个数(顺序不一定),5 是分界线。
print('-------------------')
排序算法的选择 (kind)
np.sort 默认使用 'quicksort'(快速排序),因为它通常最快。但在某些情况下,你可能需要其他算法:
| | | |
|---|
'quicksort' | | | |
'mergesort' | | | 如果需要保持相等元素的原始顺序(比如先按班级排,再按分数排),选这个。 |
'heapsort' | | | |
总结速查表
| | |
|---|
np.sort(arr) | | |
arr.sort() | | None |
np.argsort(arr) | | |
np.partition(arr, k) | | |
掌握了排序,特别是 argsort,你就掌握了数据排名的核心技能。
NumPy 搜索与筛选
在数据分析中,经常需要“大海捞针”:比如找出所有销售额大于 100 万的订单,或者定位某个特定用户的 ID 在哪里。NumPy 提供了非常高效的搜索工具,主要分为三类:条件搜索(最常用)、数值搜索(针对有序数组)和集合搜索。
1. 条件搜索:np.where()
这是 NumPy 中最强大的搜索函数,没有之一。它的作用相当于:“告诉我,哪些位置满足这个条件?”
基本用法:找索引它会返回一个元组,里面包含满足条件的元素的索引。
import numpy as np
arr = np.array()[[source_group_web_1]]
# 查找值等于 4 的元素的索引
indices = np.where(arr == 4)
print("索引结果:", indices)
# 输出: (array(),)
# 解释: 4 出现在索引 3, 5, 6 的位置
print('-------------------')
进阶用法:复杂条件你可以组合多个条件(使用 & 表示“与”,| 表示“或”)。注意:每个条件都要加括号!
arr = np.array()
# 查找大于 4 且小于 8 的元素
# 语法重点:(条件1) & (条件2)
indices = np.where((arr > 4) & (arr < 8))
print("符合条件的索引:", indices)
# 输出: (array(),) -> 对应值 5, 6, 7
print('-------------------')
替代用法:直接取值(布尔索引)如果你不需要索引,只需要“满足条件的数据本身”,可以直接用布尔数组筛选(这在 Pandas 中非常常用)。
# 直接获取满足条件的值
values = arr[arr > 5]
print("符合条件的值:", values)
# 输出: [6 7 8 9]
print('-------------------')
2. 数值搜索:np.searchsorted()
这个函数专门用于已经排好序的数组。它使用的是二分查找算法,速度极快(比线性扫描快得多)。
它的作用不是“找这个数在哪”,而是“如果要把这个数插进去,应该插在哪个位置才能保持顺序?”
sorted_arr = np.array()[[source_group_web_3]]
# 查找数字 7 应该插入的位置
index = np.searchsorted(sorted_arr, 7)
print("插入位置:", index)
# 输出: 3
# 解释: 7 应该插在 6 后面,8 前面,也就是索引 3 的位置
print('-------------------')
侧边选择:side='left' vs side='right'如果数组里已经有这个数字了,你是想插在它前面(左边),还是后面(右边)?
arr = np.array()[[source_group_web_4]]
# 默认 side='left' (插在第一个 7 的前面)
print("左侧索引:", np.searchsorted(arr, 7, side='left')) # 1
# side='right' (插在最后一个 7 的后面)
print("右侧索引:", np.searchsorted(arr, 7, side='right')) # 3
应用场景:当需要根据分数划分等级(比如 60 分以下、60-80、80-100),或者在时间序列中查找某个时间点应该插入的位置时,这个函数非常有用。
3. 集合搜索:np.isin()
如果想知道数组中的元素是否存在于另一个列表中,用这个函数。它返回一个布尔数组(True/False)。
场景:你有一份全班名单,想看看哪些人交了作业(作业提交名单)。
all_students = np.array(['Alice', 'Bob', 'Charlie', 'David'])
handed_in = np.array(['Bob', 'David'])
# 检查 all_students 里的每个人是否在 handed_in 列表中
mask = np.isin(all_students, handed_in)
print("布尔掩码:", mask)
# 输出: [False True False True]
# 解释: Alice没交, Bob交了, Charlie没交, David交了
# 结合布尔索引,直接提取交了作业的人
result = all_students[mask]
print("已交作业名单:", result)
# 输出: ['Bob' 'David']
print('-------------------')
4. 最大值与最小值的搜索:argmax 和 argmin
虽然在统计函数里提过,但在搜索章节,它们属于“极值定位”工具。
arr = np.array()
max_idx = np.argmax(arr)
min_idx = np.argmin(arr)
print("最大值索引:", max_idx) # 1 (对应值 30)
print("最小值索引:", min_idx) # 0 (对应值 10)
print('-------------------')
总结速查表
| | | |
|---|
np.where(condition) | | | |
arr[condition] | | | |
np.searchsorted(arr, val) | | | |
np.isin(arr, values) | | | |
np.argmax(arr) | | | |
NumPy 拷贝、视图与赋值
这是 NumPy 中最容易踩坑的地方。理解这三者的区别,直接决定了你是在“高效处理数据”,还是在“不知不觉中改坏了原始数据”。
核心概念:内存是什么?
在开始之前,请把内存想象成一个仓库,数组就是仓库里的货物。变量名(如 a, b)是提货单。
平常的操作,就是看这些“提货单”到底指向了哪堆货物。NumPy 有三种处理提货单的方式:
- 不拷贝(赋值):给货物贴个新标签(两张单子指同一堆货)。
- 视图(浅拷贝):透过新窗户看旧货物(看到的还是同一堆货)。
- 副本(深拷贝):把货物复制一份,放到新仓库(完全独立)。
1. 不拷贝:简单赋值
当你使用 = 号时,NumPy 不会创建新数组。它只是创建了一个新的变量名,指向同一块内存地址。
特点
import numpy as np
a = np.array([1,2,3])
b = a # 只是把 a 的内存地址赋给了 b
b[0] = 999# 修改 b[0]
print("b:", b) # [999 2 3]
print("a:", a) # [999 2 3] <-- 糟糕!a 也被改了!
print('-------------------')
2. 视图:浅拷贝
视图是 NumPy 为了节省内存而设计的。它创建了一个新的数组对象,但是数据还是共享的。
常见场景:切片操作(如 a[1:3])默认返回视图。
特点
- 内存:几乎不占新内存(只存了元数据,如形状、步长)。
a = np.array([1,2,3,4,5])
v = a[1:4] # 切片!这是一个视图
v[0] = 888# 修改视图 v[0]
print("v:", v) # [888 3 4]
print("a:", a) # [1 888 3 4 5] <-- 原数组又被改了!
print('-------------------')
为什么这样设计?为了性能。如果你有一个 10GB 的大数组,只想看其中一小部分,创建视图只需要几微秒,且不消耗额外内存;如果复制一份,既慢又爆内存。
3. 副本:深拷贝
这是最“安全”的操作。它会申请一块全新的内存,把数据完完全全复制一遍。
常见场景:使用 .copy() 方法。
特点
a = np.array([1,2,3,4,5])
c = a[1:4].copy() # 显式调用 .copy()
c[0] = 777# 修改副本 c[0]
print("c:", c) # [777 3 4]
print("a:", a) # [1 2 3 4 5] <-- 安全!原数组没变
print('-------------------')
侦探工具:如何判断是哪种?
有时候不确定手里的数组是视图还是副本,可以用以下方法检查。
方法 1:np.shares_memory() (最推荐)直接问 NumPy:你俩是不是共用内存?
a = np.array([1,2,3])
b = a # 赋值
v = a.view() # 视图
c = a.copy() # 副本
print(np.shares_memory(a, b)) # True (是同一个)
print(np.shares_memory(a, v)) # True (是视图)
print(np.shares_memory(a, c)) # False (是副本)
方法 2:.base 属性如果一个数组是视图,它的 .base 属性会指向原数组;如果是副本,.base 是 None。
print(c.base) # None (副本没有爸爸)
print(v.base) # [1 2 3] (视图指向原数组 a)
print('-------------------')
总结与避坑建议
| | | | |
|---|
| 赋值 | b = a | | | |
| 视图 | v = a[1:3] | | | |
| 副本 | c = a.copy() | | | |
避坑建议
- 如果要修改数据(特别是做数据清洗时),为了安全起见,建议先用
.copy() 备份一份,或者对切片使用 .copy(),防止误伤原始数据。
NumPy I/O 操作(输入/输出)
在现实工作中,数据通常不会直接以 NumPy 数组的形式出现在内存里,而是存储在硬盘上的文件中(比如 Excel、CSV、数据库等)。
NumPy 提供了两套主要的读写方案:
- 二进制文件(.npy):NumPy 专用,速度最快,精度无损(推荐用于保存中间结果)。
- 文本文件(.csv/.txt):通用格式,人类可读(推荐用于数据交换)。
1. 二进制文件:np.save() 和 np.load()
这是 NumPy 的“原生”格式。如果你只是在 Python 程序之间传递数据,或者想保存训练好的模型参数,一定要用这个。
保存单个数组:np.save()它会把数组的形状(shape)、数据类型(dtype)和数据本身打包成一个 .npy 文件。
import numpy as np
arr = np.array([1,2,3,4,5,6,8])
# 保存(即使你不写后缀,它也会自动加上 .npy)
np.save('my_array', arr)
# 或者
np.save('my_array.npy', arr)
loaded_arr = np.load('my_array.npy')
print("加载的数据:", loaded_arr)
print("数据类型:", loaded_arr.dtype) # 自动恢复为 int32
优点
- 省事:不需要指定分隔符或数据类型,加载后和原来一模一样。
2. 保存多个数组:np.savez()
如果你想一次性保存训练集的 X 和标签 y,不想存成两个文件,可以用这个。它会把多个数组打包成一个 .npz 文件(本质是一个未压缩或压缩的 ZIP 包)。
a = np.array([1,2,3,4,5,6,7,8,9,10])
b = np.array([9,8,7,6,5,4,3,2,1,0])
# 保存多个数组,并给它们起别名
np.savez('data_archive.npz', features=a, labels=b)
# 加载
data = np.load('data_archive.npz')
# 读取时像字典一样通过别名访问
print(data['features']) # [1 2 3]
print(data['labels']) # [4 5 6]
print('-------------------')
3. 文本文件:np.savetxt() 和 np.loadtxt()
这是为了和 Excel、其他软件或人类交互用的。
保存为文本:np.savetxt()注意:savetxt 通常只能处理 1D 或 2D 数组。
arr = np.array([1,2.222,3.333,4.444,5.555])
# fmt='%.2f' 表示保留两位小数
# delimiter=',' 表示用逗号分隔(存成 CSV 格式)
np.savetxt('data.csv', arr, fmt='%.2f', delimiter=',')
loaded_txt = np.loadtxt('data.csv', delimiter=',')
print(loaded_txt)
缺点
- 精度丢失:浮点数可能会被截断(比如 0.123456789 变成 0.12)。
4. 进阶工具:np.genfromtxt()
np.loadtxt() 有个毛病:如果文件里有缺失值(比如空单元格)或者表头,它容易报错。np.genfromtxt() 是它的增强版,更智能,但速度稍慢。
场景:处理带有缺失数据的 CSV 文件。
# 假设 data_missing.csv 内容是:
# 1.0, 2.0
# , 4.0 <-- 第一列缺了个数据
data = np.genfromtxt('data_missing.csv', delimiter=',', filling_values=0)
print(data)
# 输出: [[1. 2.] [0. 4.]] <-- 自动把缺失值填为 0
print('-------------------')
总结对比表
| | |
|---|
| 文件格式 | .npy | .csv |
| 可读性 | | |
| 读写速度 | | |
| 文件大小 | | |
| 精度 | | |
| 维度支持 | | |
| 推荐场景 | | |
避坑指南
- 路径问题:在 Windows 上写路径时,记得用正斜杠
/ 或者在字符串前加 r(如 r'D:\data\file.npy'),防止反斜杠 \ 被当成转义字符。 - 不要手动修改 .npy:千万不要用记事本打开
.npy 文件修改,一旦破坏二进制结构,文件就废了。 - 大数据怎么办?:如果文件大到内存装不下(比如 100GB),可以使用内存映射(
np.memmap),但这属于进阶用法,通常我们直接用 Pandas 处理大数据。
餐厅订单数据分析实战 (NumPy 版)
1. 数据准备 (模拟生成)
假设我们有一份包含 1000 条记录的订单数据,包含以下字段:
import numpy as np
# 设置随机种子,保证每次运行结果一致
np.random.seed(42)
# 模拟生成 1000 条数据
n_records = 1000
# 1. 订单ID (1001 到 2000)
order_ids = np.arange(1001, 1001 + n_records)
# 2. 菜品价格 (随机生成 20 到 120 元之间的整数)
prices = np.random.randint(20, 121, n_records)
# 3. 购买数量 (随机生成 1 到 5 份)
quantities = np.random.randint(1, 6, n_records)
# 4. 订单时间 (模拟营业时间 10点 到 22点)
hours = np.random.randint(10, 23, n_records)
print('数据生成完毕,前5条价格数据:', prices[:5])
print('-------------------')
2. 分析任务一:计算总营业额 (GMV)
目标:计算餐厅这段时间的总流水。NumPy 知识点:数组运算、求和 np.sum。 我们需要先计算每一行的“总价”(单价 × 数量),然后求和。
# 1. 计算每一行的总消费 (利用 NumPy 的广播机制,直接数组相乘)
row_totals = prices * quantities
# 2. 计算总营业额
total_gmv = np.sum(row_totals)
print('餐厅总营业额:', total_gmv, '元')
print('-------------------')
3. 分析任务二:筛选“高价值订单”
目标:找出单笔消费超过 200元 的订单,并计算这些“大单”的平均消费金额。NumPy 知识点:布尔索引、条件筛选。
# 1. 创建布尔掩码:找出总金额 > 200 的行
high_value_mask = row_totals > 200
# 2. 筛选出高价值订单的金额
high_value_orders = row_totals[high_value_mask]
# 3. 统计大单的数量
count_high = high_value_orders.shape[0]
# 4. 计算大单的平均金额 (使用 np.mean)
avg_high_value = np.mean(high_value_orders)
print('高价值订单数量:', count_high)
print('高价值订单平均金额:', round(avg_high_value, 2), '元')
print('-------------------')
4. 分析任务三:分析菜品价格分布
目标:了解餐厅菜价的定位。计算平均菜价、最高价、最低价以及价格的波动情况。NumPy 知识点:统计函数 mean, max, min, std。
avg_price = np.mean(prices)
max_price = np.max(prices)
min_price = np.min(prices)
std_price = np.std(prices) # 标准差,反映价格波动大小
print('--- 菜品价格分析 ---')
print('平均菜价:', round(avg_price, 2))
print('最高菜价:', max_price)
print('最低菜价:', min_price)
print('价格标准差:', round(std_price, 2))
print('-------------------')
5. 分析任务四:找出“最贵的一道菜”的索引
目标:我们不仅要知道最贵的菜多少钱,还想知道它在数据表中的位置(索引),以便后续排查。NumPy 知识点:np.argmax。
# 获取最大值的索引位置
max_price_index = np.argmax(prices)
print('最贵菜品的索引位置:', max_price_index)
print('该菜品的价格是:', prices[max_price_index])
print('该菜品所在的订单ID是:', order_ids[max_price_index])
print('-------------------')
6. 分析任务五:分时段统计 (逻辑聚合)
目标:分析哪个小时段的订单量最多?NumPy 知识点:循环与条件计数 (虽然 Pandas 做这个更方便,但用 NumPy 可以练习逻辑)。
print('--- 繁忙时段分析 ---')
max_count = 0
busy_hour = 0
# 遍历 10点 到 22点
for hour inrange(10, 23):
# 统计当前小时出现的次数
# np.sum(布尔数组) 相当于统计 True 的个数
count = np.sum(hours == hour)
# 打印当前时段订单量
print('时段 ' + str(hour) + ':00 - 订单量: ' + str(count))
if count > max_count:
max_count = count
busy_hour = hour
print('最繁忙的时段是:', busy_hour, '点,共', max_count, '单')
print('-------------------')
案例总结
通过这个案例,你使用了 NumPy 的以下核心能力:
- 向量化运算:
prices * quantities 替代了繁琐的循环,速度极快。 - 布尔索引:
row_totals > 200 快速筛选数据,这是数据分析中最常用的操作。 - 统计聚合:
np.sum, np.mean, np.std 快速提取数据特征。 - 极值索引:
np.argmax 帮助定位具体数据位置。
这就是用 NumPy 进行数据分析的基本逻辑:把数据变成数组 -> 向量化计算 -> 统计/筛选 -> 得出结论。