Python与Pandas实战:高效清洗人口普查Excel数据的完整指南
当你第一次打开从人口普查网站下载的Excel文件时,可能会被上百个表格文件的数量吓到。每个文件包含城市、镇和乡村三个版本的数据,文件名如"1-1a 各地区户数、人口数和性别比(城市)"这样的结构。面对如此庞大的数据集,如何快速将其转化为可用于分析的整洁格式?本文将带你用Python的Pandas库,一步步解决这个实际问题。
1. 环境准备与数据概览
在开始之前,确保你已经安装了Python 3.7+版本和以下必要的库:
pip install pandas openpyxl xlrdPandas是数据处理的核心库,openpyxl和xlrd则是处理Excel文件所需的引擎。我建议使用Jupyter Notebook进行这类数据探索工作,它能让你交互式地查看每一步的结果。
假设你已经将所有Excel文件下载到本地的一个文件夹中,比如population_data/。首先,我们需要了解数据的组织结构:
import os import pandas as pd # 查看文件夹中的文件 data_dir = 'population_data/' files = os.listdir(data_dir) print(f"共找到{len(files)}个文件,前5个为:{files[:5]}")典型的输出可能类似于:
共找到243个文件,前5个为:['1-1.xlsx', '1-1a.xlsx', '1-1b.xlsx', '1-1c.xlsx', '1-2.xlsx']2. 多文件合并策略与自动化处理
面对数百个结构相似的Excel文件,手动一个个打开处理显然不现实。我们需要设计一个自动化流程:
2.1 文件分类与批量读取
观察文件名模式,我们可以将其分为三类:
- 主文件(如"1-1.xlsx"):包含全国汇总数据
- 城市版(如"1-1a.xlsx"):城市地区数据
- 镇/乡村版(如"1-1b.xlsx"、"1-1c.xlsx")
def classify_files(files): main_files = [f for f in files if re.match(r'^\d+-\d+\.xlsx$', f)] city_files = [f for f in files if re.match(r'^\d+-\d+a\.xlsx$', f)] town_files = [f for f in files if re.match(r'^\d+-\d+b\.xlsx$', f)] village_files = [f for f in files if re.match(r'^\d+-\d+c\.xlsx$', f)] return main_files, city_files, town_files, village_files2.2 数据合并的通用函数
创建一个通用函数来处理同类文件的合并:
def merge_similar_files(file_list, data_dir, area_type='全国'): dfs = [] for file in file_list: # 提取表名作为新列 table_name = os.path.splitext(file)[0] try: df = pd.read_excel(os.path.join(data_dir, file)) df['数据表'] = table_name df['区域类型'] = area_type dfs.append(df) except Exception as e: print(f"处理文件{file}时出错:{str(e)}") return pd.concat(dfs, ignore_index=True)3. 数据清洗实战技巧
合并后的数据往往存在各种问题,需要系统性地清洗:
3.1 处理缺失值与异常数据
人口普查数据中常见的缺失值表现形式多样:
# 识别缺失值模式 def analyze_missing(df): missing_stats = pd.DataFrame({ '缺失值数量': df.isnull().sum(), '缺失值比例': df.isnull().mean().round(4) * 100 }) return missing_stats[missing_stats['缺失值数量'] > 0]对于不同的缺失情况,我们有多种处理策略:
| 缺失类型 | 处理方法 | Pandas代码示例 |
|---|---|---|
| 随机缺失 | 均值/中位数填充 | df.fillna(df.mean()) |
| 整列缺失 | 删除或标记 | df.dropna(axis=1) |
| 特定含义缺失 | 自定义填充 | df.fillna({'列名': -1}) |
3.2 数据类型统一与格式转换
人口数据中常见的数据类型问题包括:
- 数字存储为文本(如"1,234")
- 日期格式不一致
- 分类变量编码混乱
# 清理数字列中的千分位分隔符 def clean_numeric_columns(df): for col in df.select_dtypes(include='object'): if df[col].str.contains(',').any(): df[col] = df[col].str.replace(',', '').astype(float) return df # 统一地区名称 region_mapping = { '北京': '北京市', '上海': '上海市', # 其他映射关系... } df['地区'] = df['地区'].map(region_mapping).fillna(df['地区'])4. 高效数据转换与特征工程
清洗后的数据需要进一步加工才能用于分析:
4.1 创建衍生特征
从原始数据中可以提取更有意义的指标:
# 计算性别比 df['性别比'] = df['男性人口'] / df['女性人口'] # 年龄分段 bins = [0, 14, 64, 120] labels = ['少年', '成年', '老年'] df['年龄分组'] = pd.cut(df['年龄'], bins=bins, labels=labels)4.2 数据透视与聚合
Pandas的pivot_table功能非常适合人口统计分析:
# 各地区不同年龄段人口分布 age_distribution = pd.pivot_table( df, values='人口数', index='地区', columns='年龄分组', aggfunc='sum', margins=True )5. 性能优化与大数据处理技巧
当处理全国范围的人口数据时,性能可能成为瓶颈:
5.1 内存优化方法
# 查看当前内存使用 df.info(memory_usage='deep') # 优化数值列类型 def reduce_memory_usage(df): for col in df.select_dtypes(include=['int64']): df[col] = pd.to_numeric(df[col], downcast='integer') for col in df.select_dtypes(include=['float64']): df[col] = pd.to_numeric(df[col], downcast='float') return df5.2 分块处理大型Excel文件
对于特别大的文件,可以使用分块读取:
chunk_size = 50000 chunks = pd.read_excel('large_file.xlsx', chunksize=chunk_size) processed_chunks = [] for chunk in chunks: # 对每个块应用清洗逻辑 processed_chunk = clean_data(chunk) processed_chunks.append(processed_chunk) final_df = pd.concat(processed_chunks)6. 质量检查与验证
在完成清洗后,必须验证数据的完整性和一致性:
6.1 逻辑校验
# 检查各地区人口总和是否合理 total_population = df.groupby('地区')['人口数'].sum() assert total_population.sum() > 1e9, "总人口数异常低" # 检查性别比范围 assert df['性别比'].between(0.8, 1.2).all(), "存在异常性别比"6.2 可视化快速验证
import matplotlib.pyplot as plt # 年龄金字塔示例 fig, ax = plt.subplots(figsize=(10, 6)) male_data = df[df['性别']=='男'].groupby('年龄')['人口数'].sum() female_data = df[df['性别']=='女'].groupby('年龄')['人口数'].sum() ax.barh(male_data.index, male_data, label='男性') ax.barh(female_data.index, -female_data, label='女性') ax.set_title('人口年龄金字塔') ax.legend() plt.show()7. 自动化流程封装
将上述步骤封装成可重用的管道:
from sklearn.pipeline import Pipeline from sklearn.base import BaseEstimator, TransformerMixin class DataCleaner(BaseEstimator, TransformerMixin): def __init__(self, missing_threshold=0.3): self.missing_threshold = missing_threshold def fit(self, X, y=None): return self def transform(self, X): # 实现所有清洗逻辑 X = self.handle_missing_values(X) X = self.standardize_columns(X) return X # 其他方法实现... # 创建处理管道 pipeline = Pipeline([ ('merger', FileMerger()), ('cleaner', DataCleaner()), ('transformer', FeatureEngineer()) ]) # 执行整个流程 clean_data = pipeline.fit_transform(raw_data)在实际项目中,我发现将城市、镇和乡村数据分别处理后再合并,比一次性处理所有文件更不容易出错。另外,为每个处理步骤添加日志记录非常重要,当处理数百个文件时,能快速定位问题文件。