小陈博客-个人分享 - NumPy https://reinness.com/tag/NumPy/ zh-CN Fri, 24 Oct 2025 15:29:00 +0800 Fri, 24 Oct 2025 15:29:00 +0800 用 NumPy 玩转电影评分系统:从随机数据到洞察分析 https://reinness.com/posts/302 https://reinness.com/posts/302 Fri, 24 Oct 2025 15:29:00 +0800 Caleb numpy_3.png

🎬 一、灵感来源:这片我给满分!

如果你经常在豆瓣、IMDb 上冲浪,你会发现有的人动不动就打 10 分,
也有的人连《阿凡达》都只给个 6 分。
那问题来了:

如果我们用 NumPy 来分析一群用户的打分,会发现怎样的规律?

今天,我们就做一个小实验,用 NumPy 还原一个「影评网站」的世界。


🍿 二、项目目标

我们要模拟一份影评数据表,里面包含:

  • 100 个用户
  • 20 部电影
  • 每个用户对每部电影的评分(1~10 分)

目标:用 NumPy 模拟一个 100 个用户、20 部电影的评分矩阵,
并完成平均分、最受欢迎电影、两极分化电影等分析。


🧮 三、用 NumPy 模拟一份评分矩阵

准备工作

pip install numpy matplotlib

别忘了导入:

import numpy as np
import matplotlib.pyplot as plt

展开查看代码

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)

num_users = 100
num_movies = 20

# 用户打分偏好:有人偏高、有人偏低
user_bias = np.random.normal(0, 1, num_users)

# 电影总体质量
movie_quality = np.random.normal(0, 1, num_movies)

# 评分生成公式 = 用户偏好 + 电影质量 + 一点随机噪声
ratings = np.add.outer(user_bias, movie_quality) + np.random.normal(0, 0.8, (num_users, num_movies))

# 归一化到 1~10 之间
ratings = 1 + 9 * (ratings - ratings.min()) / (ratings.max() - ratings.min())
ratings = np.round(ratings, 1)

输出:

[[4.9 4.6 6.3 ... 4.4 7.2 5.4]
 [4.  5.6 3.6 ... 4.4 5.2 6.2]
 [3.8 6.  6.3 ... 5.8 6.6 8.3]
 ...
 [4.8 5.  5.4 ... 5.2 6.3 5.8]
 [4.  5.3 3.9 ... 3.4 5.1 6.4]
 [4.  4.  4.1 ... 3.3 4.2 5.5]]

运行后你会得到一个 100×20 的评分矩阵,
每个数字都像是某个网友的「一己之见」。

四、核心分析:让数据开口说话 🎤

1️⃣ 每部电影的平均分

movie_avg = ratings.mean(axis=0)
print(movie_avg)

# [3.955 4.783 4.802 4.381 4.964 5.408 6.674 5.334 5.324 5.192 3.392 5.06
 5.159 7.327 5.096 5.369 5.079 4.13  6.187 5.861]

这行代码,直接把 100 个用户的打分在“电影维度”上取平均。
不需要任何循环,性能炸裂。

axis=0 → 对列求平均(即每部电影)
axis=1 → 对行求平均(即每个用户)


2️⃣ 找出最受欢迎的电影

best_movie_idx = np.argmax(movie_avg)
print(f"最受欢迎电影编号:{best_movie_idx},平均分:{movie_avg[best_movie_idx]:.2f}")

# 最受欢迎电影编号:13,平均分:7.33

3️⃣ 找出“争议最大”的电影(分数最分散)

movie_std = ratings.std(axis=0)
most_divided_idx = np.argmax(movie_std)
print(f"最两极分化电影编号:{most_divided_idx},标准差:{movie_std[most_divided_idx]:.2f}")

# 最两极分化电影编号:11,标准差:1.17

4️⃣ 哪个用户最严格?哪个最随便?

user_avg = ratings.mean(axis=1)

strict_user = np.argmin(user_avg)
easy_user = np.argmax(user_avg)

print(f"最严格用户:{strict_user},平均打分:{user_avg[strict_user]:.2f}")
print(f"最佛系用户:{easy_user},平均打分:{user_avg[easy_user]:.2f}")

# 最严格用户:74,平均打分:3.01
# 最佛系用户:6,平均打分:6.71

五、可视化评分数据 🎨

1️⃣ 平均分柱状图

plt.figure(figsize=(10,5))
plt.bar(range(num_movies), movie_avg, color='skyblue')
plt.xlabel('电影编号')
plt.ylabel('平均评分')
plt.title('各电影平均评分')
plt.show()

各电影平均评分.png

2️⃣ 评分热力图(用户 × 电影)

plt.figure(figsize=(10,6))
plt.imshow(ratings, cmap='coolwarm', aspect='auto')
plt.colorbar(label='评分')
plt.xlabel('电影编号')
plt.ylabel('用户编号')
plt.title('用户评分热力图')
plt.show()

热力图可以直观看出“哪部电影评分高”以及“谁在疯狂打高分”。

用户评分热力图.png

六、性能对比:循环 VS 向量化

import time

start = time.time()
avg_scores = []
for i in range(num_movies):
    avg_scores.append(sum(ratings[:, i]) / num_users)
print("循环耗时:", time.time() - start)

# 循环耗时: 7.82012939453125e-05
start = time.time()
movie_avg = ratings.mean(axis=0)
print("向量化耗时:", time.time() - start)

# 向量化耗时: 1.5020370483398438e-05

💡 实测差距可达 ​30~100 倍​。
循环是跑步,NumPy 是坐高铁。

七、总结:NumPy,从数组到洞察的旅程

模块技术点
数据生成np.random.randint()
聚合分析mean()std()argmax()
性能优化向量化计算
可视化matplotlib 热力图与柱状图

恭喜!到这里,你已经能独立用 NumPy 处理一份真实数据集,
从生成、分析、到可视化,闭环打通。

🏁 系列目录

  1. NumPy 入门:别再用 for 循环折磨自己了
  2. NumPy 进阶:搞懂广播机制,才能真正玩转数组
  3. NumPy 实战:用数组玩转图像与数据分析
]]>
0 https://reinness.com/posts/302#comments https://reinness.com/feed/tag/NumPy/
NumPy 进阶:搞懂广播机制,才能真正玩转数组 https://reinness.com/posts/299 https://reinness.com/posts/299 Fri, 24 Oct 2025 15:12:00 +0800 Caleb numpy_2.png

一、前情回顾:数组的“超能力”从哪来?

上一章我们已经知道,NumPy 数组能直接做加减乘除:

import numpy as np

arr = np.array([1, 2, 3, 4, 5])
print(arr * 2)
# [ 2  4  6  8 10]

但你有没有想过,为什么这个操作不报错?

Python 原生列表可做不到这事儿。

答案就是:广播机制(Broadcasting)
NumPy 允许不同形状的数组在数学运算时“自动对齐”。

二、广播机制的核心原理

简单一句话总结:

广播就是 NumPy 在维度不匹配时自动扩展数组,使它们形状兼容。

来,我们先用一个经典例子开胃 👇

a = np.array([[1, 2, 3],
              [4, 5, 6]])

b = np.array([10, 20, 30])

print(a + b)

输出:

[14 25 36]]

看似理所当然,但实际上这背后 NumPy 悄悄做了扩展 👇

📊 广播过程可视化

数组原始形状扩展形状
a(2, 3)(2, 3)
b(3,)(1, 3) → (2, 3)

NumPy 自动在前面补了一个维度 (1, 3),然后复制两次让它变成 (2, 3),于是它们形状一致,就能运算了!

三、广播规则总结

判断两个数组是否能广播,其实就三步:

  1. 尾部维度 开始对齐;
  2. 每个维度要么相等,要么其中一个是 1;
  3. 不满足规则时,广播失败(直接报错)。

A.shape = (4, 1, 3)
B.shape = (1, 5, 3)
# 结果 -> (4, 5, 3)
A.shape = (3, 2)
B.shape = (2, 3)
# 维度都不匹配,直接报错!

四、copy vs view:NumPy 的内存诡计

你可能遇到过这种情况 👇

arr = np.arange(6).reshape(2, 3)
sub = arr[:, 1:]
sub[0, 0] = 99

print(arr)

输出:

[[ 0 99  2]
 [ 3  4  5]]

为什么我改了 subarr 也跟着变了?

那是因为 NumPy 返回的不是拷贝(copy),而是视图(view)
两者共用底层内存,一改俱改。

🔍 如何判断是否为 view?

sub.base is arr  # True -> 表示 sub 是 arr 的视图

如果想要真正复制一份独立的副本:

sub = arr[:, 1:].copy()

五、性能优化:让你的代码飞起来 🚀

1️⃣ 避免循环

# 慢
for i in range(len(arr)):
    arr[i] *= 2

# 快
arr *= 2

循环会触发 Python 解释器逐元素执行;
而向量化运算在底层 C 实现中批量计算,速度往往快几十倍。


2️⃣ 使用 numexpr 进行表达式优化

import numexpr as ne

a = np.random.rand(10_000_000)
b = np.random.rand(10_000_000)
c = np.random.rand(10_000_000)

# 普通写法
res1 = a * b + c

# numexpr 写法
res2 = ne.evaluate("a * b + c")

numexpr 会自动进行并行和缓存优化,CPU 利用率更高。


3️⃣ 就地操作(In-place)

arr *= 2  # 就地修改,不创建新数组

这种写法可以节省内存分配,尤其在大数组时效果显著。

六、实用技巧锦集 🧰

场景方法
随机数np.random.rand(3,3)
拼接np.concatenate((a,b), axis=0)
转置arr.T
排序np.sort(arr)
求唯一值np.unique(arr)

这些都是项目中常用的“手上活儿”。

七、性能演示:到底快多少?

import time

a = list(range(10_000_000))
start = time.time()
b = [x * 2 for x in a]
print("耗时:", time.time() - start)

# 耗时: 0.13944315910339355
import numpy as np
import time

a = np.arange(10_000_000)
start = time.time()
b = a * 2
print("耗时:", time.time() - start)

# 耗时: 0.05504918098449707

实测下来:NumPy 版本比纯 Python 快 30~100 倍
一旦数据量大,优势就像核弹一样炸裂 💥。

八、小结

模块要点
广播机制自动扩展维度,按尾部对齐
内存视图view 共用底层数据,copy 独立内存
性能优化向量化、numexpr、就地操作
实用技巧拼接、随机数、转置、排序、唯一值

下一篇(第 3 篇)我们将正式上手项目实战:
用 NumPy 做 图像处理与数据分析,把理论变成肌肉记忆。

🏁 系列目录

  1. NumPy 入门:别再用 for 循环折磨自己了
  2. NumPy 进阶:搞懂广播机制,才能真正玩转数组
  3. 🔜 NumPy 实战:用数组玩转图像与数据分析
]]>
0 https://reinness.com/posts/299#comments https://reinness.com/feed/tag/NumPy/
NumPy 入门:别再用 for 循环折磨自己了 https://reinness.com/posts/293 https://reinness.com/posts/293 Fri, 24 Oct 2025 14:36:00 +0800 Caleb numpy_1.png

一、为什么我们需要 NumPy?

还记得第一次在 Python 里写一堆 for 循环的时候的感受吗?
一行行循环、一层层嵌套,看起来像炼丹。

# 把列表每个元素平方
data = [1, 2, 3, 4, 5]
result = []
for x in data:
    result.append(x ** 2)
print(result)

结果当然是 [1, 4, 9, 16, 25]
但执行速度慢得让人想弃疗。

这时候登场的主角就是 NumPy(Numerical Python)
一个让你在 Python 里写出媲美 C 语言性能的科学计算库。

二、NumPy 的安装与导入

直接用 pip 安装就行:

pip install numpy

安装好后,你一般都会用一个简称:

import numpy as np

如果你看到别人写 np.array(),别慌,这只是大家约定俗成的简写。

三、创建数组的几种方式

NumPy 的核心是 ndarray(N 维数组)。
它像 Python 的列表,但更高效、更方便。

🧩 1. 从列表创建

import numpy as np

arr = np.array([1, 2, 3, 4, 5])
print(arr)

输出:

[1 2 3 4 5]

注意到没?打印的时候没有逗号——这就是 NumPy 的“数组风格”。

🧮 2. 创建特殊数组

np.zeros((2, 3))   # 2x3 的全零矩阵
np.ones((3, 3))    # 全一矩阵
np.eye(3)          # 单位矩阵
np.arange(0, 10, 2) # 等差数组 [0, 2, 4, 6, 8]
np.linspace(0, 1, 5) # 等分数组 [0., 0.25, 0.5, 0.75, 1.]

这些函数会在之后的项目里频繁使用。

四、数组运算的魔法

还在写循环?别傻了。
NumPy 可以直接对数组进行“广播式运算”:

data = [1, 2, 3, 4, 5]
result = []
for x in data:
    result.append(x * 2)
print(result)

输出:

[2, 4, 6, 8, 10]
import numpy as np

arr = np.array([1, 2, 3, 4, 5])
print(arr * 2)

输出:

[2, 4, 6, 8, 10]

是不是神清气爽?
向量化运算让你不写循环也能搞定所有数学操作。

五、索引与切片

NumPy 的索引比 Python list 强大得多:

arr = np.array([[1, 2, 3], [4, 5, 6]])

print(arr[0, 1])   # 访问第1行第2列 -> 2
print(arr[:, 1])   # 所有行的第2列 -> [2 5]
print(arr[1, :])   # 第2行 -> [4 5 6]

还可以用布尔索引:

arr[arr > 3]

六、常用数学函数

NumPy 内置了几乎所有数学函数:

np.mean(arr)     # 平均值
np.sum(arr)      # 求和
np.max(arr)      # 最大值
np.min(arr)      # 最小值
np.std(arr)      # 标准差

七、数组形状与维度

arr = np.array([[1,2,3], [4,5,6]])
arr.shape   # (2, 3)
arr.ndim    # 维度数 -> 2
arr.size    # 元素总数 -> 6

改变形状也很方便:

arr.reshape(3, 2)
⚠️ 注意:reshape 不会修改原数组,而是返回一个新数组。

八、小结

知识点说明
np.array()创建数组
np.zeros()/np.ones()创建特殊矩阵
arr.shape/arr.ndim查看形状与维度
arr * 2/arr + arr向量化运算
arr[arr > 3]布尔筛选

下一篇我们将深入 NumPy 的“隐藏魔法”——广播机制、内存布局与性能优化。
别眨眼,数组还能比你想的更聪明。

🏁 系列目录

  1. NumPy 入门:别再用 for 循环折磨自己了
  2. NumPy 进阶:搞懂广播机制,才能真正玩转数组
  3. 🔜 NumPy 实战:用数组玩转图像与数据分析
]]>
0 https://reinness.com/posts/293#comments https://reinness.com/feed/tag/NumPy/