第143集:NumPy数组操作
一、广播机制(Broadcasting)
广播是NumPy中一个强大的特性,它允许不同形状的数组之间进行算术运算,而无需显式地扩展数组。
1. 广播的基本规则
- 规则1:如果两个数组的维度数量不同,那么维度较少的数组会在其前面添加维度(广播维度),使其与维度较多的数组具有相同的维度数量。
- 规则2:如果两个数组在某个维度上的大小相同,或者其中一个数组在该维度上的大小为1,那么这两个数组在该维度上是兼容的。
- 规则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进行以下分析:
- 计算每位学生的总成绩
- 计算每位学生的平均成绩
- 找出每门课程的最高分和最低分
- 找出总成绩最高的学生和成绩
- 计算每门课程的平均分和标准差
- 找出数学成绩大于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数组的高级操作,包括:
- 广播机制:允许不同形状的数组进行算术运算
- 条件操作和掩码:基于条件选择和操作数组元素
- 排序和搜索:对数组进行排序和查找元素
- 集合操作:处理数组中的唯一值和集合关系
- 数学运算和函数:丰富的数学和统计函数
- 数组的复制和视图:理解视图和副本的区别,避免意外修改数据
- 实际案例:学生成绩分析,应用所学知识解决实际问题
这些高级操作使得NumPy能够高效地处理和分析数据,是数据分析和科学计算的强大工具。在下一节课中,我们将学习Pandas库,它建立在NumPy基础之上,提供了更高级的数据处理能力。
九、练习
创建一个形状为(5,3)的随机整数数组,然后:
- 计算每行的和
- 计算每列的平均值
- 找出整个数组的最大值和最小值
使用广播机制计算两个形状不同的数组的和:
- 数组1:形状(3,4)
- 数组2:形状(4,)
创建一个随机数组,然后:
- 找出所有大于0.5的元素
- 将这些元素替换为1,其他元素替换为0
对一个二维数组进行排序:
- 按行降序排序
- 按列升序排序
模拟1000个随机数,计算它们的平均值、标准差、中位数和四分位数