3.1 结合 NumPy 进行数据处理 第三章:Matplotlib 与数据分析领域 - 3.1 结合 NumPy 进行数据处理 数据可视化是数据分析流程中至关重要的一环,它能够帮助我们从数据中发现模式、趋势和异常值,从而更好地理解数据背后的故事。在 Python 的数据科学生态系统中,Matplotlib 是最基础且功能强大的绘图库之一。而要高效地利用 Matplotlib 进行数据可视化,就离不开 NumPy 这个强大的数值计算库的支持。 NumPy (Numerical Python) 是 Python 科学计算的核心库,提供了高性能的多维数组对象 ,以及丰富的数学函数库,能够进行快速的数值运算。
数据可视化是数据分析流程中至关重要的一环,它能够帮助我们从数据中发现模式、趋势和异常值,从而更好地理解数据背后的故事。在 Python 的数据科学生态系统中,Matplotlib 是最基础且功能强大的绘图库之一。而要高效地利用 Matplotlib 进行数据可视化,就离不开 NumPy 这个强大的数值计算库的支持。
NumPy (Numerical Python) 是 Python 科学计算的核心库,提供了高性能的多维数组对象 ndarray,以及丰富的数学函数库,能够进行快速的数值运算。Matplotlib 的设计理念就是与 NumPy 紧密结合,它接受 NumPy 数组作为输入,并利用 NumPy 的高效运算能力进行数据处理和图形渲染。
Matplotlib 的绘图功能几乎都围绕着 NumPy 数组 展开。无论是绘制折线图、散点图、柱状图,还是更复杂的图像和三维图形,其底层数据都必须以 NumPy 数组的形式存在。这是因为:
性能优势: NumPy 数组采用连续的内存块存储数据,并使用优化的 C 语言底层代码实现,这使得 NumPy 数组在数值计算方面拥有远超 Python 列表的性能。Matplotlib 利用 NumPy 的高性能,能够快速处理大量数据并生成图形。
广播机制: NumPy 的广播机制允许对不同形状的数组进行运算,这在数据处理和可视化中非常有用。例如,可以将一个标量值与整个数组相加,或者对两个形状兼容的数组进行逐元素运算,这简化了代码并提高了效率。
丰富的功能: NumPy 提供了大量的数学函数、统计函数、线性代数函数等,这些函数可以直接应用于 NumPy 数组,方便我们进行数据清洗、转换、分析和处理,为 Matplotlib 绘图准备高质量的数据。
我们可以通过一个简单的例子来感受 NumPy 数组在 Matplotlib 中的作用:
import matplotlib.pyplot as plt import numpy as np # 使用 NumPy 创建数据 x = np.linspace(0, 10, 100) # 创建 0 到 10 之间的 100 个均匀分布的点 y = np.sin(x) # 使用 Matplotlib 绘制折线图 plt.plot(x, y) plt.xlabel("x 轴") plt.ylabel("y 轴") plt.title("正弦曲线") plt.grid(True) plt.show()
这段代码首先导入了 matplotlib.pyplot 和 numpy 库。然后,使用 np.linspace() 函数创建了一个包含 100 个均匀分布在 0 到 10 之间的数值的 NumPy 数组 x,并使用 np.sin() 函数计算了 x 数组中每个元素的正弦值,得到了 NumPy 数组 y。最后,plt.plot(x, y) 函数直接接受这两个 NumPy 数组作为输入,绘制了正弦曲线。
这个例子简洁明了地展示了 NumPy 数组作为 Matplotlib 数据输入的基础作用。
在实际数据分析中,原始数据往往需要经过一系列的处理才能用于可视化。NumPy 提供了强大的工具,可以帮助我们高效地完成这些数据处理任务。下面我们将通过几个常见的场景,结合代码实践来详细讲解如何使用 NumPy 进行数据处理,并为 Matplotlib 绘图做好准备。
真实世界的数据经常包含缺失值(NaN)和异常值,这些数据会影响数据分析和可视化的结果。NumPy 提供了便捷的方法来处理这些问题。
处理缺失值(NaN):
NumPy 使用 np.nan 表示缺失值。我们可以使用 np.isnan() 函数检测数组中的 NaN 值,并使用其他值(如平均值、中位数或特定值)进行填充。
import numpy as np import matplotlib.pyplot as plt # 创建包含 NaN 值的数组 data = np.array([1, 2, np.nan, 4, 5, np.nan, 7, 8]) # 检测 NaN 值 nan_mask = np.isnan(data) print("NaN 值的位置:", np.where(nan_mask)) # 输出 NaN 值的索引 # 使用平均值填充 NaN 值 data_filled = np.copy(data) # 创建副本,避免修改原始数据 data_filled[nan_mask] = np.nanmean(data) print("填充 NaN 值后的数据:", data_filled) # 绘制原始数据和填充后的数据 x = np.arange(len(data)) plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.plot(x, data, marker='o', linestyle='-', label='原始数据 (含 NaN)') plt.title('原始数据') plt.legend() plt.subplot(1, 2, 2) plt.plot(x, data_filled, marker='o', linestyle='-', label='填充 NaN 值后的数据') plt.title('填充 NaN 值后的数据') plt.legend() plt.tight_layout() plt.show()
mermaid graph TD
A[原始数据 (含 NaN)] --> B{检测 NaN 值}; B --> C[定位 NaN 值索引]; B --> D{计算平均值}; D --> E[使用平均值填充 NaN]; E --> F[填充后的数据]; C --> G[绘制原始数据图表]; F --> H[绘制填充后数据图表]; G --> I[展示图表]; H --> I;
代码详解:
我们首先创建了一个包含 np.nan 值的 NumPy 数组 data。
np.isnan(data) 返回一个布尔数组 nan_mask,其中 True 表示对应位置是 NaN 值,False 表示不是。
np.where(nan_mask) 返回 NaN 值在数组中的索引。
np.nanmean(data) 计算数组的平均值,忽略 NaN 值。
我们创建了原始数据 data 的副本 data_filled,然后使用布尔索引 nan_mask 将 data_filled 中 NaN 值的位置替换为平均值。
最后,我们使用 Matplotlib 绘制了原始数据和填充 NaN 值后的数据,对比展示了数据清洗的效果。
处理异常值:
异常值是指明显偏离正常范围的数据点。可以使用多种方法检测和处理异常值,例如:
箱线图(Boxplot): 通过箱线图可以直观地识别异常值,通常将超出箱线图上下须线的数据点视为异常值。
标准差方法: 将偏离平均值一定标准差范围外的数据点视为异常值。
IQR 方法: 使用四分位距(IQR)来定义正常范围,超出 IQR 一定倍数范围外的数据点视为异常值。
下面是一个使用标准差方法检测和处理异常值的例子:
import numpy as np import matplotlib.pyplot as plt # 创建包含异常值的数组 data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 50]) # 计算平均值和标准差 mean = np.mean(data) std = np.std(data) # 定义异常值阈值 (例如:3 个标准差) threshold = 3 * std # 检测异常值 outlier_mask = np.abs(data - mean) > threshold print("异常值的位置:", np.where(outlier_mask)) # 输出异常值的索引 print("异常值:", data[outlier_mask]) # 移除异常值 data_cleaned = data[~outlier_mask] # 使用 ~ 取反布尔掩码 # 绘制原始数据和清理后的数据 x = np.arange(len(data)) x_cleaned = np.arange(len(data_cleaned)) plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.plot(x, data, marker='o', linestyle='-', label='原始数据 (含异常值)') plt.title('原始数据') plt.legend() plt.subplot(1, 2, 2) plt.plot(x_cleaned, data_cleaned, marker='o', linestyle='-', label='清理异常值后的数据') plt.title('清理异常值后的数据') plt.legend() plt.tight_layout() plt.show()
mermaid graph TD
A[原始数据 (含异常值)] --> B{计算均值和标准差}; B --> C[定义异常值阈值]; C --> D{检测异常值}; D --> E[定位异常值索引]; E --> F[移除异常值]; F --> G[清理后的数据]; E --> H[绘制原始数据图表]; G --> I[绘制清理后数据图表]; H --> J[展示图表]; I --> J;
代码详解:
我们创建了一个包含一个明显异常值 (50) 的 NumPy 数组 data。
np.mean(data) 和 np.std(data) 分别计算数组的平均值和标准差。
我们定义了一个异常值阈值,这里设置为 3 个标准差。
np.abs(data - mean) > threshold 创建一个布尔掩码 outlier_mask,其中 True 表示数据点偏离平均值超过阈值,即为异常值。
data[outlier_mask] 使用布尔索引提取异常值。
data[~outlier_mask] 使用 ~ 取反布尔掩码,提取非异常值,得到清理后的数据 data_cleaned。
最后,我们使用 Matplotlib 绘制了原始数据和清理异常值后的数据,展示了异常值处理的效果。
在数据可视化之前,有时需要对数据进行转换,以改善数据的分布特性或突出数据的某些特征。NumPy 提供了丰富的函数来进行数据转换。
标准化(Standardization):
标准化将数据转换为均值为 0,标准差为 1 的分布,也称为 Z-score 标准化。它可以消除不同特征之间量纲的影响,使数据更易于比较。
import numpy as np import matplotlib.pyplot as plt # 创建数据 data = np.array([10, 20, 30, 40, 50]) # 标准化 mean = np.mean(data) std = np.std(data) data_standardized = (data - mean) / std print("标准化后的数据:", data_standardized) # 绘制原始数据和标准化后的数据 x = np.arange(len(data)) plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.plot(x, data, marker='o', linestyle='-', label='原始数据') plt.title('原始数据') plt.legend() plt.subplot(1, 2, 2) plt.plot(x, data_standardized, marker='o', linestyle='-', label='标准化后的数据') plt.title('标准化后的数据') plt.legend() plt.tight_layout() plt.show()
mermaid graph TD
A[原始数据] --> B{计算均值和标准差}; B --> C[标准化公式: (data - mean) / std]; C --> D[标准化后的数据]; A --> E[绘制原始数据图表]; D --> F[绘制标准化后数据图表]; E --> G[展示图表]; F --> G;
代码详解:
我们创建了一个简单的 NumPy 数组 data。
np.mean(data) 和 np.std(data) 分别计算数组的平均值和标准差。
标准化公式 (data - mean) / std 将原始数据转换为标准化数据 data_standardized。
我们使用 Matplotlib 绘制了原始数据和标准化后的数据,可以看到标准化后的数据值范围发生了变化,但数据分布的形状保持不变。
归一化(Normalization):
归一化将数据缩放到一个特定的范围,通常是 [0, 1] 或 [-1, 1] 区间。常用的归一化方法是 Min-Max 归一化,将数据线性缩放到 [0, 1] 区间。
import numpy as np import matplotlib.pyplot as plt # 创建数据 data = np.array([10, 20, 30, 40, 50]) # Min-Max 归一化 min_val = np.min(data) max_val = np.max(data) data_normalized = (data - min_val) / (max_val - min_val) print("归一化后的数据:", data_normalized) # 绘制原始数据和归一化后的数据 x = np.arange(len(data)) plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.plot(x, data, marker='o', linestyle='-', label='原始数据') plt.title('原始数据') plt.legend() plt.subplot(1, 2, 2) plt.plot(x, data_normalized, marker='o', linestyle='-', label='归一化后的数据') plt.title('归一化后的数据') plt.legend() plt.tight_layout() plt.show()
mermaid graph TD
A[原始数据] --> B{计算最小值和最大值}; B --> C[归一化公式: (data - min) / (max - min)]; C --> D[归一化后的数据]; A --> E[绘制原始数据图表]; D --> F[绘制归一化后数据图表]; E --> G[展示图表]; F --> G;
代码详解:
我们创建了一个简单的 NumPy 数组 data。
np.min(data) 和 np.max(data) 分别计算数组的最小值和最大值。
Min-Max 归一化公式 (data - min_val) / (max_val - min_val) 将原始数据转换为归一化数据 data_normalized,范围在 [0, 1] 之间。
我们使用 Matplotlib 绘制了原始数据和归一化后的数据,可以看到归一化后的数据值范围被缩放到 [0, 1] 区间。
对数变换(Log Transformation):
对数变换常用于处理数据分布偏斜的情况,特别是右偏分布的数据。它可以将数据压缩到更小的范围,并使数据分布更接近正态分布。
import numpy as np import matplotlib.pyplot as plt # 创建右偏分布数据 (例如:指数分布) data = np.random.exponential(scale=10, size=100) # 对数变换 (使用自然对数 np.log) data_log = np.log(data) # 绘制原始数据和对数变换后的数据直方图 plt.figure(figsize=(10, 5)) plt.subplot(1, 2, 1) plt.hist(data, bins=20, edgecolor='black') plt.title('原始数据直方图') plt.subplot(1, 2, 2) plt.hist(data_log, bins=20, edgecolor='black') plt.title('对数变换后的数据直方图') plt.tight_layout() plt.show()
mermaid graph TD
A[原始数据 (右偏分布)] --> B{对数变换 (np.log)}; B --> C[对数变换后的数据]; A --> D[绘制原始数据直方图]; C --> E[绘制对数变换后数据直方图]; D --> F[展示图表]; E --> F;
代码详解:
我们使用 np.random.exponential() 创建了一个右偏分布的 NumPy 数组 data,模拟真实世界中常见的右偏数据。
np.log(data) 对原始数据进行自然对数变换,得到对数变换后的数据 data_log。
我们使用 Matplotlib 的 plt.hist() 函数绘制了原始数据和对数变换后数据的直方图,可以看到对数变换后,数据的分布更接近对称分布,偏斜程度降低。
NumPy 提供了丰富的统计函数,可以方便地对数据进行聚合统计,例如计算平均值、中位数、标准差、总和等。此外,结合 NumPy 的布尔索引和条件筛选功能,可以实现更复杂的数据分组和聚合操作。
基本统计函数:
NumPy 提供了 np.mean(), np.median(), np.std(), np.sum(), np.max(), np.min() 等函数,用于计算数组的平均值、中位数、标准差、总和、最大值和最小值等统计量。
import numpy as np import matplotlib.pyplot as plt # 创建数据 data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # 计算统计量 mean_val = np.mean(data) median_val = np.median(data) std_val = np.std(data) sum_val = np.sum(data) print("平均值:", mean_val) print("中位数:", median_val) print("标准差:", std_val) print("总和:", sum_val) # 使用柱状图展示统计量 stats_names = ['平均值', '中位数', '标准差', '总和'] stats_values = [mean_val, median_val, std_val, sum_val] plt.figure(figsize=(8, 5)) plt.bar(stats_names, stats_values) plt.ylabel('数值') plt.title('数据统计量') plt.show()
mermaid graph TD
A[原始数据] --> B{计算统计量 (均值, 中位数, 标准差, 总和)}; B --> C[统计量结果]; C --> D[绘制柱状图展示统计量]; D --> E[展示图表];
代码详解:
我们创建了一个简单的 NumPy 数组 data。
我们使用 np.mean(), np.median(), np.std(), np.sum() 函数分别计算了数据的平均值、中位数、标准差和总和。
我们使用 Matplotlib 的 plt.bar() 函数绘制了柱状图,直观地展示了这些统计量的值。
数据分组和聚合:
结合 NumPy 的布尔索引和条件筛选功能,我们可以实现更复杂的数据分组和聚合操作。例如,假设我们有一个包含类别和数值的数据集,我们可以按照类别对数据进行分组,并计算每个类别的统计量。
import numpy as np import matplotlib.pyplot as plt # 创建数据 (类别和数值) categories = np.array(['A', 'B', 'A', 'C', 'B', 'A', 'C', 'B']) values = np.array([10, 20, 15, 25, 18, 12, 28, 22]) # 获取唯一类别 unique_categories = np.unique(categories) # 存储每个类别的平均值 category_means = {} # 循环遍历每个类别,计算平均值 for category in unique_categories: category_mask = categories == category # 创建布尔掩码,筛选当前类别的数据 category_values = values[category_mask] # 使用布尔索引筛选数值 category_mean = np.mean(category_values) # 计算当前类别的平均值 category_means[category] = category_mean print("各类别平均值:", category_means) # 使用柱状图展示各类别平均值 plt.figure(figsize=(8, 5)) plt.bar(category_means.keys(), category_means.values()) plt.xlabel('类别') plt.ylabel('平均值') plt.title('各类别平均值') plt.show()
mermaid graph TD
A[原始数据 (类别和数值)] --> B{获取唯一类别}; B --> C{循环遍历每个类别}; C --> D{创建类别布尔掩码}; D --> E{筛选当前类别数值}; E --> F{计算类别平均值}; F --> G[存储类别平均值]; G --> H{循环结束?}; H -- 否 --> C; H -- 是 --> I[各类别平均值结果]; I --> J[绘制柱状图展示各类别平均值]; J --> K[展示图表];
代码详解:
我们创建了两个 NumPy 数组 categories 和 values,分别表示数据的类别和数值。
np.unique(categories) 获取数组 categories 中的唯一类别。
我们使用循环遍历每个唯一类别。
categories == category 创建一个布尔掩码 category_mask,用于筛选当前类别的数据。
values[category_mask] 使用布尔索引 category_mask 筛选出属于当前类别的数值。
np.mean(category_values) 计算当前类别数值的平均值。
我们将每个类别的平均值存储在字典 category_means 中。
最后,我们使用 Matplotlib 的 plt.bar() 函数绘制柱状图,展示了各类别数据的平均值。
核心要点回顾:
NumPy 数组是 Matplotlib 的核心数据结构。 Matplotlib 的绘图函数直接接受 NumPy 数组作为输入,利用 NumPy 的高性能进行数据处理和图形渲染。
NumPy 提供了丰富的数据处理工具。 包括处理缺失值、异常值、数据标准化、归一化、对数变换以及数据聚合统计等功能,可以为 Matplotlib 绘图准备高质量的数据。
结合 NumPy 和 Matplotlib 可以构建强大的数据分析和可视化流程。 NumPy 负责高效的数据处理和分析,Matplotlib 负责将处理后的数据以直观的方式呈现出来,两者协同工作,能够有效地探索和理解数据。
掌握 NumPy 的数据处理技巧,将极大地提升 Matplotlib 数据可视化的效率和质量,为深入的数据分析打下坚实的基础。在后续的章节中,我们将继续学习 Matplotlib 的各种绘图技巧,并进一步探索 NumPy 在数据分析中的应用。