第143集:NumPy数组操作

一、广播机制(Broadcasting)

广播是NumPy中一个强大的特性,它允许不同形状的数组之间进行算术运算,而无需显式地扩展数组。

1. 广播的基本规则

  1. 规则1:如果两个数组的维度数量不同,那么维度较少的数组会在其前面添加维度(广播维度),使其与维度较多的数组具有相同的维度数量。
  2. 规则2:如果两个数组在某个维度上的大小相同,或者其中一个数组在该维度上的大小为1,那么这两个数组在该维度上是兼容的。
  3. 规则3:如果两个数组在某个维度上既不相同也不为1,则广播失败。

2. 广播示例

import numpy as np

# 示例1:一维数组与标量相加
arr = np.array([1, 2, 3, 4, 5])
result = arr + 10
print(result)  # 输出:[11 12 13 14 15]

# 示例2:二维数组与一维数组相加
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr1d = np.array([10, 20, 30])
result = arr2d + arr1d
print(result)
# 输出:
# [[11 22 33]
#  [14 25 36]
#  [17 28 39]]

# 示例3:不同形状的数组相加
arr1 = np.array([[1, 2], [3, 4], [5, 6]])  # 形状(3,2)
arr2 = np.array([10, 20])  # 形状(2,)
result = arr1 + arr2  # arr2广播到形状(3,2)
print(result)
# 输出:
# [[11 22]
#  [13 24]
#  [15 26]]

3. 广播的优势

  • 减少内存消耗:无需显式复制数据
  • 提高代码可读性:简化不同形状数组之间的运算
  • 提升性能:底层优化的广播运算比Python循环更快

二、条件操作和掩码

NumPy提供了强大的条件操作功能,可以轻松地基于条件选择数组元素。

1. 条件表达式

import numpy as np

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

# 创建条件掩码
mask = arr > 5
print(mask)  # 输出:[False False False False False  True  True  True  True  True]

# 使用掩码选择元素
result = arr[mask]
print(result)  # 输出:[ 6  7  8  9 10]

# 直接在索引中使用条件
result = arr[arr > 5]
print(result)  # 输出:[ 6  7  8  9 10]

# 复合条件
result = arr[(arr > 5) & (arr < 9)]
print(result)  # 输出:[6 7 8]

2. np.where()函数

np.where()函数是一个强大的条件选择工具,可以根据条件从两个数组中选择元素。

import numpy as np

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

# 语法:np.where(condition, x, y)
# 如果condition为True,选择x中的对应元素,否则选择y中的对应元素

# 示例1:简单条件替换
result = np.where(arr > 3, 10, 0)
print(result)  # 输出:[ 0  0  0 10 10]

# 示例2:两个数组之间的条件选择
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([10, 20, 30, 40, 50])
result = np.where(arr1 > 3, arr1, arr2)
print(result)  # 输出:[10 20 30  4  5]

# 示例3:处理二维数组
arr2d = np.array([[1, 2], [3, 4], [5, 6]])
result = np.where(arr2d > 3, arr2d, 0)
print(result)
# 输出:
# [[0 0]
#  [0 4]
#  [5 6]]

三、排序和搜索

1. 排序

NumPy提供了多种排序函数,可以对数组进行排序。

import numpy as np

arr = np.array([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5])
print(f"原始数组:{arr}")

# 1. 原地排序
arr.sort()
print(f"原地排序后:{arr}")

# 2. 返回排序后的数组副本
arr = np.array([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5])
sorted_arr = np.sort(arr)
print(f"排序副本:{sorted_arr}")
print(f"原始数组:{arr}")  # 原始数组不变

# 3. 二维数组排序
arr2d = np.array([[3, 1, 4], [1, 5, 9], [2, 6, 5]])
print(f"\n二维数组:\n{arr2d}")

# 按行排序
row_sorted = np.sort(arr2d, axis=1)
print(f"按行排序:\n{row_sorted}")

# 按列排序
col_sorted = np.sort(arr2d, axis=0)
print(f"按列排序:\n{col_sorted}")

# 4. 稳定排序
arr = np.array([(3, 'a'), (1, 'b'), (3, 'c')], dtype=[('value', int), ('label', 'U1')])
stable_sorted = np.sort(arr, kind='stable', order='value')
print(f"\n稳定排序:{stable_sorted}")

2. 搜索

import numpy as np

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

# 1. 查找最大值和最小值的索引
max_idx = np.argmax(arr)
min_idx = np.argmin(arr)
print(f"最大值索引:{max_idx},值:{arr[max_idx]}")
print(f"最小值索引:{min_idx},值:{arr[min_idx]}")

# 2. 查找元素的位置
# 注意:np.where返回的是一个元组,包含每个维度的索引
indices = np.where(arr > 5)
print(f"大于5的元素索引:{indices}")
print(f"大于5的元素:{arr[indices]}")

# 3. 查找排序后的索引
arr = np.array([3, 1, 4, 1, 5, 9, 2, 6])
sorted_indices = np.argsort(arr)
print(f"\n原始数组:{arr}")
print(f"排序索引:{sorted_indices}")
print(f"按排序索引访问:{arr[sorted_indices]}")

# 4. 二维数组搜索
arr2d = np.array([[3, 1, 4], [1, 5, 9], [2, 6, 5]])
print(f"\n二维数组:\n{arr2d}")

# 按行查找最大值索引
row_max_idx = np.argmax(arr2d, axis=1)
print(f"按行最大值索引:{row_max_idx}")

# 按列查找最小值索引
col_min_idx = np.argmin(arr2d, axis=0)
print(f"按列最小值索引:{col_min_idx}")

四、集合操作

NumPy提供了一系列用于集合操作的函数,可以处理数组中的唯一值和集合关系。

import numpy as np

# 1. 唯一值
arr = np.array([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
unique_values = np.unique(arr)
print(f"原始数组:{arr}")
print(f"唯一值:{unique_values}")

# 2. 计算每个唯一值的出现次数
unique_values, counts = np.unique(arr, return_counts=True)
print(f"唯一值:{unique_values}")
print(f"出现次数:{counts}")

# 3. 集合交集
arr1 = np.array([1, 2, 3, 4, 5])
arr2 = np.array([3, 4, 5, 6, 7])
intersection = np.intersect1d(arr1, arr2)
print(f"\n数组1:{arr1}")
print(f"数组2:{arr2}")
print(f"交集:{intersection}")

# 4. 集合并集
union = np.union1d(arr1, arr2)
print(f"并集:{union}")

# 5. 集合差集(arr1 - arr2)
difference = np.setdiff1d(arr1, arr2)
print(f"差集(arr1 - arr2):{difference}")

# 6. 集合对称差集(arr1 ^ arr2)
symmetric_diff = np.setxor1d(arr1, arr2)
print(f"对称差集:{symmetric_diff}")

# 7. 检查元素是否在数组中
arr = np.array([1, 2, 3, 4, 5])
element = 3
is_in = np.isin(element, arr)
print(f"\n元素{element}是否在数组中:{is_in}")

# 检查多个元素
 elements = np.array([3, 6, 7])
 is_in = np.isin(elements, arr)
 print(f"元素{elements}是否在数组中:{is_in}")

五、数学运算和函数

NumPy提供了丰富的数学函数,可以对数组进行各种数学运算。

1. 基本数学运算

import numpy as np

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

print(f"原始数组:{arr}")
print(f"数组 + 10:{arr + 10}")
print(f"数组 * 2:{arr * 2}")
print(f"数组 ** 2:{arr ** 2}")
print(f"数组 / 2:{arr / 2}")
print(f"数组 // 2:{arr // 2}")
print(f"数组 % 2:{arr % 2}")
print(f"数组的平方根:{np.sqrt(arr)}")
print(f"数组的指数:{np.exp(arr)}")
print(f"数组的对数:{np.log(arr)}")

2. 三角函数

import numpy as np

# 角度转弧度
angles = np.array([0, 30, 45, 60, 90])
radians = np.radians(angles)

print(f"角度:{angles}")
print(f"弧度:{radians}")
print(f"正弦值:{np.sin(radians)}")
print(f"余弦值:{np.cos(radians)}")
print(f"正切值:{np.tan(radians)}")
print(f"反正弦值:{np.degrees(np.arcsin(np.sin(radians)))}")

3. 统计函数

import numpy as np

arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(f"数组:{arr}")

print(f"\n求和:{np.sum(arr)}")
print(f"平均值:{np.mean(arr)}")
print(f"中位数:{np.median(arr)}")
print(f"最大值:{np.max(arr)}")
print(f"最小值:{np.min(arr)}")
print(f"标准差:{np.std(arr)}")
print(f"方差:{np.var(arr)}")
print(f"百分位数(50%):{np.percentile(arr, 50)}")
print(f"百分位数(25%和75%):{np.percentile(arr, [25, 75])}")
print(f"累计和:{np.cumsum(arr)}")
print(f"累计积:{np.cumprod(arr)}")

# 二维数组的统计函数
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"\n二维数组:\n{arr2d}")
print(f"按行求和:{np.sum(arr2d, axis=1)}")
print(f"按列求和:{np.sum(arr2d, axis=0)}")
print(f"按行平均值:{np.mean(arr2d, axis=1)}")
print(f"按列平均值:{np.mean(arr2d, axis=0)}")

六、数组的复制和视图

在NumPy中,数组的赋值、切片等操作可能返回视图(view)或副本(copy),理解它们的区别对于避免意外修改数据非常重要。

1. 视图(View)

视图是原始数组的一个引用或窗口,共享底层数据。修改视图会影响原始数组。

import numpy as np

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

# 创建视图
view = arr.view()
print(f"原始数组:{arr}")
print(f"视图:{view}")

# 修改视图
view[0] = 100
print(f"\n修改视图后:")
print(f"原始数组:{arr}")  # 原始数组被修改
print(f"视图:{view}")

2. 副本(Copy)

副本是原始数组的一个完整拷贝,不共享底层数据。修改副本不会影响原始数组。

import numpy as np

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

# 创建副本
copy = arr.copy()
print(f"原始数组:{arr}")
print(f"副本:{copy}")

# 修改副本
copy[0] = 100
print(f"\n修改副本后:")
print(f"原始数组:{arr}")  # 原始数组不变
print(f"副本:{copy}")

3. 切片操作的返回值

import numpy as np

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

# 切片返回视图
slice_view = arr[3:7]
print(f"原始数组:{arr}")
print(f"切片视图:{slice_view}")

# 修改切片视图
slice_view[0] = 400
print(f"\n修改切片视图后:")
print(f"原始数组:{arr}")  # 原始数组被修改
print(f"切片视图:{slice_view}")

# 使用copy()方法创建副本
slice_copy = arr[3:7].copy()
print(f"\n切片副本:{slice_copy}")

# 修改切片副本
slice_copy[0] = 300
print(f"\n修改切片副本后:")
print(f"原始数组:{arr}")  # 原始数组不变
print(f"切片副本:{slice_copy}")

七、案例分析:学生成绩分析

让我们通过一个实际案例来应用所学的NumPy数组操作知识。

问题描述

假设有一个班级的学生成绩数据,包含5名学生的3门课程成绩:

学生 数学 语文 英语
学生1 85 90 78
学生2 76 88 92
学生3 92 85 88
学生4 88 92 85
学生5 95 88 90

请使用NumPy进行以下分析:

  1. 计算每位学生的总成绩
  2. 计算每位学生的平均成绩
  3. 找出每门课程的最高分和最低分
  4. 找出总成绩最高的学生和成绩
  5. 计算每门课程的平均分和标准差
  6. 找出数学成绩大于90分的学生

解决方案

import numpy as np

# 创建成绩数组
grades = np.array([
    [85, 90, 78],  # 学生1
    [76, 88, 92],  # 学生2
    [92, 85, 88],  # 学生3
    [88, 92, 85],  # 学生4
    [95, 88, 90]   # 学生5
])

# 课程名称
courses = ['数学', '语文', '英语']
# 学生名称
students = ['学生1', '学生2', '学生3', '学生4', '学生5']

# 1. 计算每位学生的总成绩
total_scores = np.sum(grades, axis=1)
print(f"每位学生的总成绩:{total_scores}")

# 2. 计算每位学生的平均成绩
avg_scores = np.mean(grades, axis=1)
print(f"每位学生的平均成绩:{avg_scores}")

# 3. 找出每门课程的最高分和最低分
max_scores = np.max(grades, axis=0)
min_scores = np.min(grades, axis=0)
for i, course in enumerate(courses):
    print(f"{course}最高分:{max_scores[i]},最低分:{min_scores[i]}")

# 4. 找出总成绩最高的学生和成绩
top_student_idx = np.argmax(total_scores)
top_student = students[top_student_idx]
top_score = total_scores[top_student_idx]
print(f"总成绩最高的学生:{top_student},成绩:{top_score}")

# 5. 计算每门课程的平均分和标准差
course_avg = np.mean(grades, axis=0)
course_std = np.std(grades, axis=0)
for i, course in enumerate(courses):
    print(f"{course}平均分:{course_avg[i]:.2f},标准差:{course_std[i]:.2f}")

# 6. 找出数学成绩大于90分的学生
math_grades = grades[:, 0]
mask = math_grades > 90
top_math_students = np.array(students)[mask]
top_math_grades = math_grades[mask]
print(f"数学成绩大于90分的学生:{top_math_students}")
print(f"对应的数学成绩:{top_math_grades}")

八、总结

本节课我们学习了NumPy数组的高级操作,包括:

  1. 广播机制:允许不同形状的数组进行算术运算
  2. 条件操作和掩码:基于条件选择和操作数组元素
  3. 排序和搜索:对数组进行排序和查找元素
  4. 集合操作:处理数组中的唯一值和集合关系
  5. 数学运算和函数:丰富的数学和统计函数
  6. 数组的复制和视图:理解视图和副本的区别,避免意外修改数据
  7. 实际案例:学生成绩分析,应用所学知识解决实际问题

这些高级操作使得NumPy能够高效地处理和分析数据,是数据分析和科学计算的强大工具。在下一节课中,我们将学习Pandas库,它建立在NumPy基础之上,提供了更高级的数据处理能力。

九、练习

  1. 创建一个形状为(5,3)的随机整数数组,然后:

    • 计算每行的和
    • 计算每列的平均值
    • 找出整个数组的最大值和最小值
  2. 使用广播机制计算两个形状不同的数组的和:

    • 数组1:形状(3,4)
    • 数组2:形状(4,)
  3. 创建一个随机数组,然后:

    • 找出所有大于0.5的元素
    • 将这些元素替换为1,其他元素替换为0
  4. 对一个二维数组进行排序:

    • 按行降序排序
    • 按列升序排序
  5. 模拟1000个随机数,计算它们的平均值、标准差、中位数和四分位数

扩展阅读

« 上一篇 NumPy数组基础 下一篇 » Pandas Series