跳至主要内容

Python 计数:现代用法、常见陷阱与实用示例

使用 Counter、count() 和 pandas 在 Python 中快速计数、验证数据并避免常见错误
· 4分钟 阅读

使用 AI 探索

数一数东西听起来很简单,直到结果有点儿—甚至大幅—不对。我见过用反复调用 list.count() 来做频次检查,或误用 pandas 方法而悄悄跳过缺失值的情况。结果是:代码很慢,数字还错。解决办法是对症下药:做频次映射用 collections.Counter,单值快速查询用内置的 count(),在 DataFrame 环境下用 pandas 的 value_counts()。

Counter 是什么以及它如何工作

collections.Counter 是一个用于为可哈希对象计数的字典子类(多重集)。您可以从可迭代对象或映射构建一个 Counter。缺失键默认返回 0;计数可以递增或递减;还有若干便捷方法让常见任务更简单。

from collections import Counter

# From an iterable (one pass over the data)
nyc_eatery_types = [
    "Mobile Food Truck", "Food Cart", "Snack Bar", "Restaurant", "Food Cart",
    "Restaurant", "Mobile Food Truck", "Snack Bar", "Mobile Food Truck"
]
eatery_type_counts = Counter(nyc_eatery_types)
print(eatery_type_counts)  # dict-like view
print(eatery_type_counts["Restaurant"])  # missing keys return 0 instead of KeyError
print(eatery_type_counts["Kiosk"])
Counter({'Mobile Food Truck': 3, 'Food Cart': 2, 'Snack Bar': 2, 'Restaurant': 2})
2
0

不要依赖 Counter 的迭代顺序。需要排序视图时请使用 most_common()。

用 most_common() 找出最常见的值

Counter.most_common(n) 按计数降序返回 (item, count) 对的列表。提供 n 可限制结果条数,省略则返回所有对。

top_3 = eatery_type_counts.most_common(3)
print(top_3)
[('Mobile Food Truck', 3), ('Food Cart', 2), ('Snack Bar', 2)]

当您需要频次分析、排行榜,或对分类型数据做快速核查时使用它。

用 Counter 做更多事

update 与 subtract 计数

业务数据会变化。update() 会增加计数,而 subtract() 会减少计数。二者都接受可迭代对象或映射。

new_permits = ["Restaurant", "Food Cart", "Restaurant"]
eatery_type_counts.update(new_permits)  # add 1 for each occurrence
print(eatery_type_counts)

closures = {"Snack Bar": 1}
eatery_type_counts.subtract(closures)   # reduce counts (can go negative)
print(eatery_type_counts)
Counter({'Mobile Food Truck': 3, 'Restaurant': 4, 'Food Cart': 3, 'Snack Bar': 2})
Counter({'Restaurant': 4, 'Mobile Food Truck': 3, 'Food Cart': 3, 'Snack Bar': 1})

如果计数降至 0 或以下,elements() 和 most_common() 等方法会忽略它。要彻底移除某个键,请使用 del counter[key]。

计算总量与 Top-N

自 Python 3.10 起,Counter.total() 返回所有计数之和。这在需要计算占比或份额时很有用。

total_permits = eatery_type_counts.total()
for eatery, count in eatery_type_counts.most_common(3):
    share = count / total_permits
    print(f"{eatery}: {count} ({share:.1%})")

用 elements() 重建序列

elements() 会按元素的计数次数依次产出该元素。顺序是任意的。

expanded = list(eatery_type_counts.elements())
print(len(expanded), "items reconstructed from counts")

使用数学与集合风格运算

您可以用算术与最小/最大风格运算组合多个 counter。结果会丢弃为零和负数的计数。

from collections import Counter

a = Counter({"Food Cart": 5, "Restaurant": 2})
b = Counter({"Food Cart": 3, "Snack Bar": 4})

print(a + b)   # add counts
print(a - b)   # subtract (keeps positives only)
print(a & b)   # intersection: min of counts
print(a | b)   # union: max of counts
Counter({'Food Cart': 8, 'Snack Bar': 4, 'Restaurant': 2})
Counter({'Food Cart': 2, 'Restaurant': 2})
Counter({'Food Cart': 3})
Counter({'Food Cart': 5, 'Snack Bar': 4, 'Restaurant': 2})

何时使用 list.count()、Counter 或 pandas

选择满足您需求与性能约束的最简单工具。

使用 count() 进行单值检查

当您只需要知道某个值在序列中出现的次数,或字符串中非重叠子串的计数时,使用内置的 count()。

numbers = [1, 2, 2, 3, 2]
print(numbers.count(2))  # 3

word = "banana"
print(word.count("a"))   # 3

text = "aaa"
print(text.count("aa"))  # 1: non-overlapping matches only
print("cat".count(""))   # 4: empty string counts len(s)+1 positions

当心布尔值与整数:True == 1 且 False == 0。混合类型时这会抬高计数。

mixed = [1, True, 0, False, True]
print(mixed.count(1))  # 3 (counts 1 and True)
print(mixed.count(0))  # 2 (counts 0 and False)

避免在大数据上在循环里反复调用 count();每次调用都会扫描整个序列。

# Inefficient: O(n^2) for large lists
# freq = {x: numbers.count(x) for x in numbers}

# Efficient: one pass
from collections import Counter
freq = Counter(numbers)

使用 Counter 统计多个频次

当您需要许多唯一值的计数时,构建一次 Counter 并按需查询。它清晰、快速,而且就是为此设计的。

在 pandas 中处理表格数据

在 pandas 中,DataFrame.count() 和 Series.count() 计算非缺失值的数量。要获取取值的频次表,请使用 Series.value_counts()。

import pandas as pd
df = pd.DataFrame({
    "line": ["A", "A", "B", None, "B", "B"],
    "ridership": [100, None, 120, 130, None, 150],
})

print(df["line"].count())         # 5 (non-missing)
print(df["line"].value_counts())  # value frequencies

# For full row count, use len(df), not df.count()
print(len(df))                    # 6

用 Counter 验证并清洗数据

在分析前,我会检查分类一致性、意外取值以及重复项。Counter 让这些检查快速且可复用。

from collections import Counter

# Example: validate allowed categories
allowed_types = {"Mobile Food Truck", "Food Cart", "Snack Bar", "Restaurant"}
type_counts = Counter(nyc_eatery_types)

unexpected = {t: c for t, c in type_counts.items() if t not in allowed_types}
if unexpected:
    print("Unexpected categories found:", unexpected)

# Example: flag duplicates (e.g., station IDs appearing more than once)
station_ids = ["ST-001", "ST-002", "ST-003", "ST-002", "ST-004", "ST-002"]
dup_counts = Counter(station_ids)
duplicates = [sid for sid, c in dup_counts.items() if c > 1]
print("Duplicate station IDs:", duplicates)

实用备注与常见陷阱

这些细节能节省时间,避免隐蔽的错误。

  • Counter 类似字典:缺失键返回 0;用 del counter[key] 移除条目。除非临时需要,否则避免存储 0。
  • 不要依赖迭代顺序。按排名输出请使用 most_common()。
  • str.count() 统计非重叠匹配。若要统计重叠子串,请使用 re.finditer()。
  • 避免在同一个大序列上反复调用 list.count()。请改为构建一次 Counter。
  • 在 pandas 中,.count() 用于非缺失数量;取值频次用 .value_counts()。

我常用的最小化模式

以下是我在生产与教学中最常用的片段。

  • 单次遍历的频次映射:Counter(iterable)
  • Top-N 类别:Counter(data).most_common(n)
  • 增量更新:counter.update(batch) 或 counter.subtract(batch)
  • 单值检查: sequence.count(x)(避免在循环中使用)
  • 表格频次: df["col"].value_counts()(不是 df.count())

结论

collections.Counter 是快速而清晰地统计多种取值的正确工具。临时的一次性检查用内置的 count(),做频次映射与 Top-N 分析用 Counter,在 pandas 中工作时用 Series.value_counts()。留意字符串计数的边界情况、布尔与整数的等价性,以及非缺失数量与取值频次的区别。掌握这些模式,您的计数将既快又准。

主题