在数据科学与统计分析的日常工作中,我们经常需要处理海量的数据集。面对这些数据,我们首先要做的就是了解其分布特征。虽然平均值能告诉我们数据的中心趋势,但有时候它具有欺骗性;中位数虽然稳健,但往往掩盖了数据的极端情况。这时,“众数”——即数据集中出现频率最高的值——就显得尤为重要。它能直接告诉我们“什么是最常见的”,这在市场篮子分析、图像处理以及异常检测中都有着不可替代的作用。
在这篇文章中,我们将深入探讨 Python 的 scipy.stats.mode 函数。我们将不仅仅满足于调用这个函数,而是要全面理解它的工作原理、参数细节、返回值的含义,以及在多维数组复杂场景下如何正确使用它。无论你是正在处理简单的列表,还是复杂的多维 NumPy 数组,通过这篇文章,你都能掌握高效寻找众数的实战技巧。
理论背景:众数与其计算公式
虽然 scipy.stats.mode 能够直接处理离散数据(直接计数),但在处理分组数据或连续数据时,统计学上有一套特定的插值公式来估算众数。理解这个公式有助于我们在面对分组统计表时,不仅知其然,更知其所以然。
其计算公式如下:
让我们来拆解一下这个公式中的各个符号:
- $l$ (Lower Limit): 众数组的下限边界。这是包含众数的那个区间的起点。
- $h$ (Class Size): 众数组的大小(或者称为组距、类宽度)。
- $f_m$ (Frequency of Modal Class): 对应众数组的频数,也就是这个区间内出现了多少次数据。
- $f_1$ (Frequency Preceding Modal Class): 众数组前一组的频数。
- $f_2$ (Frequency Succeeding Modal Class): 众数组后一组的频数。
对于离散的原始数据,scipy 的实现逻辑更直观:它通过计数找到出现次数最多的元素。让我们来看看如何具体操作。
函数详解:参数与返回值
INLINECODE5ffcd14b 是 INLINECODEd2a16178 模块下的一个核心函数。它的基本语法非常简洁,但功能强大。
#### 参数说明
- array (a): 这是我们的输入数据,可以是一个 Python 列表,或者更常见的,一个 NumPy 数组。它是我们需要分析的对象。
- axis (int, 可选): 这是一个关键参数,用于指定沿着哪个轴计算众数。
– axis=0(默认):沿着列方向计算(垂直操作)。例如,在二维数组中,它会计算每一列的众数。
– axis=1:沿着行方向计算(水平操作)。例如,计算每一行的众数。
– axis=None:不区分轴,将整个数组展平,计算全局唯一的众数。
- nanpolicy (可选): 这个参数决定了当输入数据中包含 INLINECODE08c35922(空值)时的行为。
– INLINECODEcdeac31d:返回 INLINECODE336ffce8。
– INLINECODEc387887b:忽略 INLINECODEa1affcae 进行计算(这在实际清洗数据时非常有用)。
– ‘raise‘:抛出错误。
#### 返回值:ModeResult 对象
很多初学者会误以为这个函数直接返回众数值,但实际上它返回一个具名元组 ModeResult。这个对象包含两个重要的数组:
- mode: 存储计算出的众数值的数组。
- count: 存储对应众数值出现频次的数组。
了解这个结构对于后续的数据提取至关重要。
—
代码实战:从基础到多维
让我们通过一系列由浅入深的代码示例,来掌握 scipy.stats.mode 的用法。
#### 示例 1:基础使用与二维数组的默认行为
首先,我们导入必要的库,并构建一个简单的二维数组。在这个例子中,我们将观察 axis=0(默认值)是如何工作的。它会把每一列看作一个独立的序列,找出该列中出现最多的数字。
# 导入 scipy.stats 和 numpy 库
from scipy import stats
import numpy as np
# 构建一个 2x6 的二维数组
arr1 = np.array([[1, 3, 27, 13, 21, 9],
[8, 12, 8, 4, 7, 10]])
print("输入数组:")
print(arr1)
# 计算众数,默认 axis=0(沿列计算)
# 我们将返回值赋给 result 变量,以便查看其结构
result = stats.mode(arr1)
print("
计算结果 (ModeResult 对象):")
print(result)
# 访问具体的众数值
print("
具体的众数:", result.mode)
print("对应的出现次数:", result.count)
输出结果:
输入数组:
[[ 1 3 27 13 21 9]
[ 8 12 8 4 7 10]]
计算结果 :
ModeResult(mode=array([[1, 3, 8, 4, 7, 9]]), count=array([[1, 1, 1, 1, 1, 1]]))
具体的众数: [[1 3 8 4 7 9]]
对应的出现次数: [[1 1 1 1 1 1 1]]
代码解读:
在这里,因为我们没有指定 INLINECODEa5769e2d,函数默认按列计算。对于每一列,由于只有两个元素且互不相同(除了第3列,这里 INLINECODEba38af39 和 8 也是不同的),所以众数就是该列第一个出现的元素(或者任选其一,取决于具体实现,通常是最小的索引),频次均为 1。虽然在这个简单的数据集中众数看起来不那么“显著”,但这展示了函数的基本行为。
#### 示例 2:多维数据处理与轴参数的威力
在实际应用中,数据往往是多维的。让我们创建一个包含重复元素的矩阵,并通过改变 INLINECODEdf208531 参数来观察结果的变化。这是理解 INLINECODEbba2ec20 参数最好的方式。
# 导入库
from scipy import stats
import numpy as np
# 创建一个 4x3 的数组,包含明显的重复元素
# 观察每一列和每一行,众数应该是不同的
arr2 = [[1, 3, 27],
[3, 4, 6],
[7, 6, 3],
[3, 6, 3]]
print("--- 原始数组 ---")
print(np.array(arr2))
# 1. 默认情况:axis=0 (沿列计算)
print("
1. 沿列计算 (axis=0):")
# 比如第一列 [1, 3, 7, 3],3 出现了两次,是众数
print("众数:", stats.mode(arr2).mode)
print("频次:", stats.mode(arr2).count)
# 2. axis=None (全局计算)
print("
2. 全局计算:")
# 整个数组展平,3 出现的次数最多(5次),其次是 6 和其他
print("众数:", stats.mode(arr2, axis=None).mode)
print("频次:", stats.mode(arr2, axis=None).count)
# 3. axis=1 (沿行计算)
print("
3. 沿行计算:")
# 比如第一行 [1, 3, 27],每个只出现一次,取第一个
# 第四行 [3, 6, 3],3 出现两次,是众数
result_axis1 = stats.mode(arr2, axis=1)
print("众数 (每行):")
print(result_axis1.mode)
print("频次 (每行):")
print(result_axis1.count)
输出结果:
--- 原始数组 ---
[[ 1 3 27]
[ 3 4 6]
[ 7 6 3]
[ 3 6 3]]
1. 沿列计算 (axis=0):
众数: [[3 6 3]]
频次: [[2 2 2]]
2. 全局计算:
众数: [3]
频次: [5]
3. 沿行计算:
众数 (每行):
[[1]
[3]
[6]
[3]]
频次 (每行):
[[1]
[1]
[1]
[2]]
深度解析:
- 沿列计算 (INLINECODE8b8017da):请观察第一列 INLINECODE843919f0。数字 INLINECODE34a4db86 出现了两次,其他数字只出现一次。因此第一列的众数是 INLINECODEd03f72de,频次是 INLINECODE7b26eb73。第三列的情况类似(有两个 INLINECODE9bde50ae)。
- 全局计算 (INLINECODE28240171):这是一个“压平”的操作。不管数据在几行几列,我们把所有数字放在一个袋子里。数字 INLINECODEd50f3e97 在整个矩阵中出现了
5次(第1列2次,第2列1次,第3列2次),是绝对的主角。 - 沿行计算 (INLINECODEc9c68f63):这里我们关注的是每一行内部的“霸主”。比如最后一行 INLINECODEf5d59e93,INLINECODE213fa6dd 出现了两次,所以该行的众数是 INLINECODE097e12f9。而前三行所有数字都是唯一的,所以函数返回了该行的第一个元素作为众数(频次均为 1)。
#### 示例 3:处理空值
真实世界的数据往往是脏数据。NaN(Not a Number)是我们在处理缺失值时最常遇到的敌人。如果不正确处理,它会像病毒一样污染计算结果。
from scipy import stats
import numpy as np
# 创建包含 NaN 的数组
data_with_nan = np.array([[1.5, np.nan, 4],
[np.nan, 2.5, 4],
[1.5, 2.5, np.nan]])
print("--- 包含空值的数组 ---")
print(data_with_nan)
# 默认情况:NaN 通常会被传播
print("
默认处理 (propagate):")
print(stats.mode(data_with_nan))
# 使用 nan_policy=‘omit‘ 忽略 NaN
print("
忽略空值:")
# 这里的众数计算会跳过 NaN,只计算有效数字
print(stats.mode(data_with_nan, nan_policy=‘omit‘))
输出结果:
--- 包含空值的数组 ---
[[1.5 nan 4. ]
[nan 2.5 4. ]
[1.5 2.5 nan]]
默认处理:
ModeResult(mode=array([[nan, nan, nan]]), count=array([[2, 2, 2]]))
忽略空值:
ModeResult(mode=array([[1.5, 2.5, 4. ]]), count=array([[2, 2, 2]]))
实战见解: 看到了吗?在默认情况下,如果一列中有 INLINECODE91f42a3c,且 INLINECODE419cdb74 出现的频率最高(或者与其他值并列),它可能被当作众数。这通常不是我们想要的。通过设置 nan_policy=‘omit‘,我们可以告诉 scipy:“请假装这些空值不存在,只根据剩下的有效数据告诉我谁是最常见的”。这在数据预处理阶段是一个必不可少的操作。
—
最佳实践与常见陷阱
在与数百名开发者的交流中,我发现大家在使用 mode 函数时,常会遇到一些共性的问题。这里分享几个避坑指南。
#### 1. 提取值的正确方式
很多新手会写出这样的代码:
print(stats.mode(arr))
然后困惑于为什么会得到 ModeResult(mode=..., count=...) 这一长串字符。
建议: 始终记住解包或者通过属性访问。
如果你只想要众数值,请使用:
most_common = stats.mode(arr).mode
或者更 Pythonic 的写法(利用 ModeResult 的特性):
mode, count = stats.mode(arr)
#### 2. 多众数的处理
scipy.stats.mode 的一个特点是:如果有多个值具有相同的最高频率,它只返回其中的最小值(或者最先遇到的那个)。
例如,在数组 INLINECODE45cf911b 中,INLINECODE51198bdf 和 INLINECODE97ff1cbd 都出现了两次。INLINECODE60b9ede1 会返回 INLINECODE8effcf52。如果你需要获取所有的众数(即 INLINECODEf32d8714),INLINECODE0d174fec 可能无法直接满足需求,你可能需要结合 INLINECODE13faa4bc 和自定义逻辑来实现,或者使用 Pandas 的 mode() 方法(它会返回所有的众数)。
#### 3. 性能考量
对于极小的数组,性能差异可以忽略不计。但如果你要在数百万行的数据上计算众数,INLINECODE2b3fcb3b 是基于 C 语言优化的,速度非常快。然而,如果你是在 Pandas DataFrame 中操作,建议直接使用 INLINECODEbc25018b,因为它针对表格结构做了更多优化,且处理 NaN 的策略更符合数据分析的直觉。
总结
在这篇文章中,我们一起解锁了 INLINECODE37702425 的全部潜力。我们不仅学习了基本的语法,还深入探讨了 INLINECODEd4b9d8ec 参数在多维数组中的神奇表现,以及如何利用 nan_policy 来处理真实世界中不完美的数据。
掌握这个函数,意味着你在面对复杂的分类数据统计时,不再需要手写繁琐的循环和计数逻辑。你可以一行代码搞定列分析、行分析甚至全局分析。
下一步建议:
现在,我建议你打开你的 Python 编辑器,尝试用 INLINECODE767b6ff1 分析一下你手头的数据集,或者结合 INLINECODE8fc6818e 可视化一下众数的分布情况。如果你正在处理更复杂的统计需求,不妨去探索一下 INLINECODEfe3e197e 下的其他宝藏函数,比如 INLINECODE706544e0 或 scoreatpercentile。
祝你在数据科学的道路上越走越远!如果有任何问题,欢迎随时交流。