跳转至

数据科学 1 · NumPy

先说清楚:数据科学/数值计算是 Python 的绝对统治领域,而 Java 在这里几乎没有等价生态ND4J/EJML 等冷门、社区小)。所以本篇的"Java 对照"会变弱——不是"翻译 Java",而是"带你进入 Java 程序员陌生的、Python 最强的一面"。会用"近邻概念"(数组、循环、SQL、Excel)帮你建立直觉,但很多地方没有 Java 等价物,那正是要学的。

NumPy 是 Python 数据科学生态的地基(Pandas、scikit-learn、PyTorch 都建立在它之上)。核心是 ndarray——多维、固定类型、向量化运算的数组

uv add numpy            # 或 pip install numpy

1.1 ndarray:和 Java 数组是两回事

  • Java 数组

    int[] a = {1, 2, 3};          // 固定长度、固定类型
    // 加元素要新建数组;没有逐元素运算
    
  • NumPy ndarray

    import numpy as np
    a = np.array([1, 2, 3])       # 固定类型、支持向量化
    a + 10                        # array([11, 12, 13]) 逐元素
    

ndarray 的关键特性:

  • 固定 dtype(所有元素同类型,如 int64/float64)——这是性能的来源(连续内存、C 层操作)。
  • 多维np.array([[1,2,3],[4,5,6]]) 是 2×3 矩阵(.shape.ndim.dtype)。
  • 逐元素运算a + ba * 2a > 0 自动作用到每个元素。
a = np.array([[1, 2, 3], [4, 5, 6]])
a.shape        # (2, 3)
a.ndim         # 2
a.dtype        # int64
a.sum()        # 21(所有元素)
a.mean(axis=0) # 每列均值

1.2 向量化:NumPy 的灵魂(vs for 循环)

这是 NumPy 最该理解的一点:不要写 for 循环遍历数组,用向量化运算——底层 C 批量处理,快 50–100 倍。

  • Python for 循环(慢)

    a = list(range(1_000_000))
    b = list(range(1_000_000))
    c = [x + y for x, y in zip(a, b)]   # 解释器逐个
    
  • NumPy 向量化(快)

    a = np.arange(1_000_000)
    b = np.arange(1_000_000)
    c = a + b                            # C 层批量,快 ~100x
    

为什么快:固定 dtype → 连续内存 + 向量化 SIMD 指令 + 无解释器开销。Java 程序员的直觉:像 C 数组运算,而非 ArrayList 逐个装箱。

NumPy 铁律

能向量化就别写循环。看到一个对大数组的 for,先想"能不能用 ndarray 运算替掉"。


1.3 广播(broadcasting):没有 Java 对应

形状不同的数组相加,NumPy 会广播——把小数组"拉伸"到大数组的形状,无需复制。这是 Java 程序员完全陌生的能力。

a = np.array([[1, 2, 3],
              [4, 5, 6]])      # 形状 (2, 3)
b = np.array([10, 20, 30])     # 形状 (3,)

a + b
# array([[11, 22, 33],
#        [14, 25, 36]])         # b 被广播到每一行

规则:从右向左对齐维度,各维度要么相等、要么其中一个是 1(或缺失),才能广播。

a = np.ones((3, 4))
a + np.array([1, 2, 3, 4])      # (3,4) + (4,) → 广播,OK
a + np.array([1, 2, 3])         # (3,4) + (3,) → 维度不匹配,ValueError

实战用途:数据标准化(每列减均值除标准差)、批量加偏置——一行搞定,无需循环。


1.4 索引与切片

a = np.arange(12).reshape(3, 4)
# array([[ 0,  1,  2,  3],
#        [ 4,  5,  6,  7],
#        [ 8,  9, 10, 11]])

a[1, 2]        # 6(第 1 行第 2 列)
a[0]           # 第 0 行:array([0,1,2,3])
a[:, 1]        # 第 1 列:array([1,5,9])
a[0:2, 1:3]    # 子矩阵

布尔索引(按条件筛选,对照 SQL 的 WHERE):

a = np.array([5, 12, 8, 3, 17])
a[a > 10]              # array([12, 17])
a[(a > 5) & (a < 15)]  # array([12, 8])

花式索引(按索引数组取):

a[[0, 2, 4]]           # 取第 0/2/4 个

切片是视图

NumPy 切片返回视图(共享内存),修改会影响原数组(和 Python list 切片的副本不同)。要独立副本用 .copy()


1.5 通用函数(ufunc)

元素级运算,都是 C 实现的 ufunc:

np.sqrt(a)       # 每个元素开方
np.exp(a)        # e^x
np.sin(a)        # 三角函数
np.maximum(a, 0) # 逐元素取 max(对照 ReLU)

1.6 线性代数

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

A @ B               # 矩阵乘法(@ 运算符)
A.T                 # 转置
np.linalg.inv(A)    # 逆矩阵
np.linalg.solve(A, b)  # 解线性方程组 Ax=b

对照 Java:你大概会用 Apache Commons Math 或 EJML——但 NumPy 是事实标准、API 远更简洁、生态环绕它。


1.7 与 Java 对照小结

概念 Java 近邻 NumPy
多维数组 int[][](原始)/ EJML 矩阵 ndarray(统一、向量化)
逐元素运算 手写循环 a + b(向量化)
广播 无(手写循环) 自动
布尔筛选 Stream filter a[a > 0]
矩阵运算 Commons Math/EJML A @ Bnp.linalg.*

诚实结论:Java 没有等价物。学 NumPy 不是"换个语法",是"获得一种 Java 给不了的能力"。


本章练习

练习 1.1

生成 100 万个随机数,分别用 Python 循环和 NumPy 向量化求平方和,对比耗时。

参考答案

import numpy as np, time
# 循环
data = list(np.random.rand(1_000_000))
t = time.perf_counter(); s = sum(x*x for x in data); print(time.perf_counter()-t)
# 向量化
a = np.random.rand(1_000_000)
t = time.perf_counter(); s = (a*a).sum(); print(time.perf_counter()-t)
向量化通常快 50–100 倍。

练习 1.2

用广播把一个 (3,4) 的数组每列减去该列均值(标准化第一步)。

参考答案
a = np.random.rand(3, 4)
col_means = a.mean(axis=0)      # 形状 (4,)
centered = a - col_means         # 广播:(3,4) - (4,) → 每行减
练习 1.3

给定成绩数组,用布尔索引选出 60–80 分(含)的学生。

参考答案
scores = np.array([55, 60, 72, 80, 90, 65])
selected = scores[(scores >= 60) & (scores <= 80)]

← 回首页 | 下一章:数据科学 2 · Pandas