17370845950

如何在不全量加载 CSV 文件到内存的情况下高效计算用户年龄中位数

本文介绍如何使用 csv.dictreader 流式读取制表符分隔的 csv 文件,避免内存溢出,同时正确提取出生年份并计算当前(2025年)年龄的中位数,解决 '_csv.reader' object is not subscriptable 类型错误。

在处理大型 CSV 文件时,为节省内存,应避免将整个文件一次性读入列表或 DataFrame。Python 的 csv 模块提供了流式读取能力,但需注意 csv.reader 与 csv.DictReader 的关键区别:前者返回每行为 list(需用索引访问字段,如 row[2]),后者返回每行为 dict(支持按列名访问,如 row['birth_year'])。原代码中误将 csv.reader 对象当作字典使用(data['birth_year']),导致 TypeError。

正确做法是改用 csv.DictReader,它自动将首行解析为字段名,并为后续每一行构建键值映射。此外,我们采用生成器表达式 (2025 - int(row['birth_year']) for row in reader) 实现惰性求值——年龄值在中位数计算过程中逐个生成,全程不构建完整列表,真正实现低内存占用。

以下是完整、健壮的实现(含基础异常处理):

import csv
from statistics import median

def median_age(filename):
    try:
        with open(filename, 'r', encoding='utf-8') as file:
            reader = csv.DictReader(file, delimiter='\t')

            # 验证必需字段是否存在
            if 'birth_year' not in reader.fieldnames:
      

raise ValueError(f"Missing required column 'birth_year' in {filename}") # 生成年龄序列(跳过空值或无效年份) ages = [] for row in reader: try: birth_year = int(row['birth_year'].strip()) if 1900 <= birth_year <= 2024: # 合理年份范围过滤 ages.append(2024 - birth_year) except (ValueError, TypeError): continue # 跳过无法解析的行 if not ages: raise ValueError(f"No valid birth_year found in {filename}") return median(ages) except FileNotFoundError: raise FileNotFoundError(f"File '{filename}' not found.") except Exception as e: raise RuntimeError(f"Error processing {filename}: {e}")

使用示例:
假设 data.csv 内容如下(制表符分隔):

first   last    birth_year
paul    henry   2019
bill    thompson    1995
mary    allen   2003
jennifer    davis   2015
liz morgan  1999

调用 median_age('data.csv') 将返回 21.0(对应年龄序列 [5, 29, 21, 9, 25] 的中位数)。

关键注意事项:

  • ✅ 始终指定 encoding='utf-8' 防止中文或特殊字符报错;
  • ✅ 使用 DictReader 而非 reader 实现列名访问;
  • ✅ 通过生成器+显式循环+过滤,兼顾内存效率与数据鲁棒性;
  • ❌ 避免在 csv.reader 上直接使用方括号索引(如 row['xxx']);
  • ⚠️ 若文件无标题行,需手动传入 fieldnames= 参数;若分隔符非制表符,请同步调整 delimiter。

该方案适用于 GB 级日志或用户档案 CSV,单次遍历、零中间列表、错误可追溯,是生产环境推荐的轻量级年龄统计实践。