Lab 2.1 高效学习NumPy实验

(date: 2025-11-24)

通过这个2小时的实验,你可以快速掌握NumPy的核心功能。NumPy是Python科学计算的基础库,主要用于数组操作和数学运算。

在本实验中,你会需要先阅读和运行基础示例,然后按 [step] 要求修改代码完成实验。

实验环境准备

# 安装NumPy(如果尚未安装)
# pip install numpy

import numpy as np
import time

print("NumPy版本:", np.__version__)

第1部分:NumPy数组基础

数组属性

[step 1] 要求: 在 analysis.md 中问答 arr2 的形状、维度、元素总数、数据类型分别是什么(每行一个答案)?

# 从Python列表创建
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([[1, 2, 3], [4, 5, 6]])

print("一维数组:", arr1)
print("二维数组:\n", arr2)
print("数组形状:", arr2.shape)
print("数组维度:", arr2.ndim)
print("元素总数:", arr2.size)
print("数据类型:", arr2.dtype)

数组改变形状

[step 2] 要求:

  1. 将 arr2 改变为形状为 (6, 1) 的数组,并打印重塑后的数组。再转换成 (1,6) 的数组,并打印。

  2. 在 analysis.md 中问答:(6, 1) 的数组和 (1,6) 的数组,形状和维度是否相同?

# 数组属性
arr = np.array([[1, 2, 3], [4, 5, 6]])
print("原始数组:\n", arr)
# 改变形状
reshaped = arr.reshape(3, 2)
print("重塑后:\n", reshaped)

数值类型转换

[step 3] 要求: 将 float_arr 的数据类型转换为复数,并打印转换后的数组及其数据类型。

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

# 数据类型转换
float_arr = arr.astype(np.float64)
print("转换为浮点数:", float_arr.dtype)

特殊数组创建方法

[step 4] 要求:

  1. 创建一个形状为 (4, 2) 的全0数组,并打印。

  2. 创建一个形状为 (3, 3) 的全5数组,并打印。

  3. 创建一个从 0 到 9,步长为 2 的数组,并打印。

  4. 创建一个从 0 到 1,等间距有5个元素的数组,并打印。

# 特殊数组创建方法
zeros_arr = np.zeros((3, 4))  # 全0数组
ones_arr = np.ones((2, 3))    # 全1数组
range_arr = np.arange(0, 10, 2)  # 类似range
linspace_arr = np.linspace(0, 1, 5)  # 等间距数组
random_arr = np.random.random((2, 2))  # 随机数组

print("全0数组:\n", zeros_arr)
print("等间距数组:", linspace_arr)

基本索引和切片

[step 5] 要求:

  1. 对于 arr,打印前2个元素;打印后4个元素;打印每隔3个取一个元素。

  2. 对于 arr_2d,打印首行;打印第2行第1列;打印后两行的前两列。

  3. 对于 arr_2d,打印大于5的元素。

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

# 基本切片
print("前5个元素:", arr[:5])
print("后3个元素:", arr[-3:])
print("每隔2个取一个:", arr[::2])

# 多维数组索引
arr_2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("第二行:", arr_2d[1])
print("第一行第二列:", arr_2d[0, 1])
print("前两行的后两列:\n", arr_2d[:2, 1:])

# 布尔索引
bool_idx = arr > 5
print("大于5的元素:", arr[bool_idx])

第2部分:NumPy数组操作

数学运算

1 数组算术运算

[step 6] 要求:计算并打印 sin[(a*b)/(a+b)]

# 创建示例数组
a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])

# 算术运算
print("加法:", a + b)
print("乘法:", a * b)
print("平方:", a ** 2)
print("三角函数:", np.sin(a))

2 数组矩阵运算

[step 7] 要求: 对于数组 a = [[1, 2, 3], [4, 5, 6]]

  1. 计算 x = a 和 a.T 的矩阵乘法,以及 y = a.T 和 a 的矩阵乘法

  2. 计算 x 的所有元素之和, y 的所有元素之和

  3. x 和 y 的所有元素之后哪个大?为什么? [在 analysis.md 中回答]

# 矩阵运算
matrix_a = np.array([[1, 2], [3, 4]])
matrix_b = np.array([[5, 6], [7, 8]])

print("矩阵乘法:\n", np.dot(matrix_a, matrix_b))
print("矩阵转置:\n", matrix_a.T)

3 数组统计运算

[step 8] 要求:先产生数组 x = [[1,2,3],[4,5,6],[7,8,9]],计算:

  1. x 的平均值、标准差、最大值、求和

  2. x*x 的平均值、标准差、最大值、求和

  3. x在 axis = 0 上的平均值、标准差、最大值、求和

  4. x在 axis = 1 上的平均值、标准差、最大值、求和

  5. 分析指定axis 和不指定 axis 的结果有何区别? [在analysis.md中回答]

# 统计运算
data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print("平均值:", np.mean(data))
print("标准差:", np.std(data))
print("最大值:", np.max(data))
print("求和:", np.sum(data))

广播机制

广播示例1:标量与数组

[step 9] 要求:产生元素均为9的 4x4 数组

# 广播示例1:标量与数组
arr = np.array([[1, 2, 3], [4, 5, 6]])
result = arr + 10  # 标量10被广播到整个数组
print("数组加标量:\n", result)

广播示例2:不同形状数组

[step 10] 要求:

  1. 计算 a * b

  2. 在[analysis.md] 分析 a*b 的广播规则与 a+b 有何异同

# 广播示例2:不同形状数组
a = np.array([[1], [2], [3]])  # 形状 (3, 1)
b = np.array([10, 20, 30])     # 形状 (3,)
result = a + b  # 广播为 (3, 3)
print("不同形状数组相加:\n", result)

广播示例3:实际应用

[step 11] 要求:在 analysis 分析说明下面的代码所完成的实际运算的数学表达式

# 广播示例3:实际应用
# 计算每个点到原点的距离
points = np.array([[1, 2], [3, 4], [5, 6]])
distances = np.sqrt(np.sum(points**2, axis=1))
print("各点到原点距离:", distances)

数组合并与分割

堆叠数组

[step 12] 要求: 先建立3个1维数组 a = [1,2], b=[3, 4], c=[5,6]

  1. a,b,c 三者 vstack

  2. a,b,c 三者 hstack

# 堆叠数组
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

print("垂直堆叠:\n", np.vstack((a, b)))
print("水平堆叠:\n", np.hstack((a, b)))

分割数组

[step 13] 要求:

  1. 建立有 1 到 24 组成的 4x6 的数组

  2. 划分成上下两个一样大的数组

  3. 划分成左右两个一样大的数组

# 分割数组
arr = np.arange(12).reshape(3, 4)
print("原数组:\n", arr)
print("水平分割:\n", np.hsplit(arr, 2))
print("垂直分割:\n", np.vsplit(arr, 3))

排序和搜索

[step 14] 要求:

  1. 最小的数是多少?

  2. 最小的数在哪个位置?

  3. 用最小的数的位置取出最小的数,验证它是不是最小

# 排序和搜索
random_data = np.random.randint(0, 100, 10)
print("原始数据:", random_data)
print("排序后:", np.sort(random_data))
print("最大值索引:", np.argmax(random_data))

条件操作

[step 15] 要求:

  1. 找到所有大于等于3的成员

  2. 找到所有小于3的成员

  3. 将所有大于等于3的置为1

  4. 将所在小于3的成员置0

  5. 同时将所有大于等于3的置为1,将所在小于3的成员置0

# 条件操作
arr = np.array([1, 2, 3, 4, 5, 6])
print("条件筛选:", np.where(arr > 3, arr, -1))

第3部分:实战应用-图像处理模拟

[step 16]

图像处理示例

运行并学习这段示例代码

# 模拟灰度图像处理
# 创建一个模拟的28x28像素图像(灰度值0-255)
image = np.random.randint(0, 256, (28, 28))
print("原始图像形状:", image.shape)
print("像素值范围:", np.min(image), "-", np.max(image))

# 图像处理操作
# 1. 调整亮度
brightened = np.clip(image + 50, 0, 255)

# 2. 图像翻转
flipped = np.fliplr(image)

# 3. 裁剪中心区域
cropped = image[10:18, 10:18]

print("亮度调整后范围:", np.min(brightened), "-", np.max(brightened))
print("裁剪后形状:", cropped.shape)

字符亮度模拟打印

用5种字符来表示不同的亮度级别。

' ' → 最暗
'.' → 较暗  
'-' → 中等
'+' → 较亮
'#' → 最亮

下面是相应的量化及终端打印函数:

def quantize_to_chars(image, levels=5):
    """
    将图像量化为指定级别并用字符表示
    """
    # 字符对应亮度:''最暗,'#'最亮
    chars = [' ', '.', '-', '+', '#']
    
    # 计算量化边界
    min_val = np.min(image)
    max_val = np.max(image)
    step = (max_val - min_val) / levels
    
    # 创建量化后的字符图像
    char_image = []
    for i in range(image.shape[0]):
        row = []
        for j in range(image.shape[1]):
            value = image[i, j]
            # 确定所属级别
            level = min(int((value - min_val) / step), levels - 1)
            row.append(chars[level])
        char_image.append(''.join(row))
    
    return char_image

# 对原始图像进行5组不同的量化
print("\n" + "="*50)
print("5组不同的量化结果:")
print("="*50)

# 第1组:均匀量化
quantized_1 = quantize_to_chars(image_32x32)
print("\n第1组 - 均匀量化:")
for row in quantized_1:
    print(row)

任务描述

想象你要创建一个特殊的"乐高风格"数字图像,并用简单的字符来展示它的亮度变化。

第1步:创建特殊图像

  • 制作一个 32×32 像素 的字符模拟图像

  • 将这个图像想象成由 4行×4列 的彩色积木块组成

  • 每个积木块大小是 8×8 像素

  • 关键特性:每个积木块内部的所有像素颜色完全一样,但不同积木块的颜色随机变化

第2步:5种不同的处理实验

对同一个图像进行5种不同的处理,看看字符显示如何变化:

  1. 基础版本 - 直接显示原图

  2. 调亮版本 - 让整张图变得更亮

  3. 调暗版本 - 让整张图变得更暗

  4. 增强对比度 - 让亮的更亮、暗的更暗

  5. 局部特写 - 只显示图像的中心区域

第3步:预期打印效果示例

最终你会看到5组字符画,每组都是一个32×32的字符方阵(除了特写版本是8×8),能够清晰地看出:

  • 积木块的分块结构

  • 不同处理方式对亮度分布的影响

  • 字符如何有效地表示灰度层次

        ........########--------
        ........########--------
        ........########--------
        ........########--------
        ........########--------
        ........########--------
        ........########--------
        ........########--------
++++++++########        ........
++++++++########        ........
++++++++########        ........
++++++++########        ........
++++++++########        ........
++++++++########        ........
++++++++########        ........
++++++++########        ........
........########        ########
........########        ########
........########        ########
........########        ########
........########        ########
........########        ########
........########        ########
........########        ########
----------------################
----------------################
----------------################
----------------################
----------------################
----------------################
----------------################
----------------################