Pandas + NumPy 数据清洗实战:以长沙二手房数据集为例,搞定缺失值、异常值和数据透视
Pandas NumPy 数据清洗实战以长沙二手房数据集为例搞定缺失值、异常值和数据透视当你第一次拿到从房产网站爬取的长沙二手房数据时可能会被那些混乱的字段、缺失的值和不一致的格式搞得手足无措。这份数据包含了户型、建筑面积、单价等21个字段但真正能直接用于分析的却寥寥无几。本文将带你用Pandas和NumPy一步步完成从脏数据到干净数据的蜕变掌握数据清洗的核心技能。1. 数据初探与基础清洗在开始任何清洗操作前我们需要先了解数据的整体情况。假设我们已经从MySQL数据库读取了原始数据存储在一个名为df的DataFrame中。import pandas as pd import numpy as np # 显示数据概览 print(df.info()) # 查看前5行数据 print(df.head())常见问题初步诊断时间字段如挂牌时间可能是字符串格式区域字段可能包含区-街道的组合信息数值字段如单价、总价可能有异常值分类字段如装修、朝向可能有拼写不一致的情况1.1 时间字段处理挂牌时间是重要的分析维度但原始数据可能以字符串形式存储# 将挂牌时间转换为datetime类型 df[挂牌时间] pd.to_datetime(df[挂牌时间], format%Y-%m-%d) # 提取年份、月份等特征 df[挂牌年份] df[挂牌时间].dt.year df[挂牌月份] df[挂牌时间].dt.month1.2 区域字段拆分原始区域字段通常包含层级信息我们可以拆分为更结构化的形式# 拆分区域字段假设格式为岳麓区-梅溪湖 df[[所在区域, 具体区域]] df[区域].str.split(-, expandTrue) # 删除原始区域列 df.drop(区域, axis1, inplaceTrue)2. 缺失值处理的艺术缺失值是数据清洗中最常见的问题之一。不同字段的缺失值需要采用不同的处理策略。2.1 缺失值检测首先全面检测各字段的缺失情况# 计算各列缺失值比例 missing_ratio df.isnull().mean().sort_values(ascendingFalse) # 可视化缺失情况 import matplotlib.pyplot as plt missing_ratio[missing_ratio 0].plot(kindbarh) plt.title(各字段缺失值比例) plt.show()2.2 分类型缺失值处理对于分类变量如装修、朝向常见的处理方法包括# 用众数填充装修字段 df[装修] df[装修].fillna(df[装修].mode()[0]) # 为缺失的朝向创建未知类别 df[朝向] df[朝向].fillna(未知)2.3 数值型缺失值处理数值字段如建筑面积、单价的缺失处理更为复杂# 按区域分组填充平均单价 df[单价] df.groupby(所在区域)[单价].apply( lambda x: x.fillna(x.mean()) ) # 对于建筑年代可以使用中位数填充 df[建筑年代] df[建筑年代].fillna(df[建筑年代].median())提示对于关键数值字段建议保留缺失记录标记便于后续分析时识别。3. 异常值检测与处理异常值会严重影响分析结果需要系统性地识别和处理。3.1 单变量异常值检测通过统计方法和可视化识别异常# 单价描述性统计 print(df[单价].describe()) # 绘制单价箱线图 df[单价].plot(kindbox) plt.title(单价分布箱线图) plt.show()3.2 基于业务规则的异常值处理结合房地产知识设定合理范围# 定义合理单价范围假设为5000-50000元/平米 valid_price (df[单价] 5000) (df[单价] 50000) # 过滤异常单价 df df[valid_price].copy() # 同样处理建筑面积假设20-500平米为合理范围 valid_area (df[建筑面积] 20) (df[建筑面积] 500) df df[valid_area].copy()3.3 多变量联合异常检测有时需要结合多个字段判断异常# 计算单价与面积的比值 df[单价面积比] df[单价] / df[建筑面积] # 找出比值异常的记录 abnormal_ratio df[单价面积比] df[单价面积比].quantile(0.99) print(df[abnormal_ratio][[小区, 建筑面积, 单价, 单价面积比]])4. 数据转换与增强清洗后的数据可以进行衍生特征创建为分析提供更多维度。4.1 特征工程# 创建价格分段 df[价格分段] pd.cut(df[总价], bins[0, 100, 200, 300, 500, float(inf)], labels[100万以下, 100-200万, 200-300万, 300-500万, 500万以上]) # 计算房龄 current_year pd.Timestamp.now().year df[房龄] current_year - df[建筑年代] # 创建是否学区房特征 df[学区房] df[小区].str.contains(实验|附小|一中|外国语, naFalse)4.2 数据标准化为后续分析准备标准化数值from sklearn.preprocessing import MinMaxScaler # 初始化缩放器 scaler MinMaxScaler() # 对数值字段进行标准化 numeric_cols [单价, 建筑面积, 总价, 房龄] df[numeric_cols] scaler.fit_transform(df[numeric_cols])5. 数据透视与分组分析清洗后的数据可以进行多维度的聚合分析。5.1 基础分组统计# 按区域统计平均单价 area_price df.groupby(所在区域)[单价].agg([mean, median, count]) area_price.sort_values(mean, ascendingFalse)5.2 复杂透视分析使用pivot_table进行多维分析# 区域与装修的交叉分析 pd.pivot_table(df, values总价, index所在区域, columns装修, aggfunc[mean, count], fill_value0)5.3 时间序列分析分析挂牌时间趋势# 按月统计挂牌量 monthly_count df.resample(M, on挂牌时间)[小区].count() # 绘制挂牌趋势图 monthly_count.plot(title长沙二手房月度挂牌量趋势) plt.ylabel(挂牌数量) plt.show()6. 数据存储与后续使用清洗完成后的数据可以输出供后续分析使用。6.1 保存到CSV# 保存清洗后的数据 df.to_csv(cleaned_changsha_houses.csv, indexFalse, encodingutf-8-sig)6.2 存储到数据库from sqlalchemy import create_engine # 创建数据库连接 engine create_engine(mysqlpymysql://user:passwordlocalhost:3306/real_estate) # 存储到MySQL df.to_sql(cleaned_changsha_houses, conengine, if_existsreplace, indexFalse)7. 实战技巧与常见陷阱在实际项目中有几个容易忽视但非常重要的细节数据快照在每一步重大清洗操作前保存数据副本便于回溯# 创建检查点 df_checkpoint df.copy()清洗日志记录所有清洗操作及其影响# 记录删除的异常记录数 removed_records len(df) - len(df_cleaned)验证清洗效果的自动化脚本def validate_data(df): assert df[单价].isnull().sum() 0, 单价仍有缺失值 assert (df[单价] 0).all(), 存在非正数的单价 return True性能优化对于大数据集考虑使用dask替代pandasimport dask.dataframe as dd ddf dd.from_pandas(df, npartitions4)分类字段编码为机器学习准备from sklearn.preprocessing import LabelEncoder le LabelEncoder() df[所在区域_编码] le.fit_transform(df[所在区域])处理长沙二手房数据时最耗时的往往不是技术问题而是对业务逻辑的理解。比如判断某个单价是否真的异常需要结合该区域的市场行情、房屋类型等多方面因素。我曾遇到一个案例某别墅区的单价远高于普通住宅初看像是异常值实则是合理的数据。