Python二手房数据分析:Pandas清洗与可视化全流程

发布时间:2026/9/11 20:42:36
Python二手房数据分析:Pandas清洗与可视化全流程 简介面向计算机专业毕业设计、期末大作业以及数据分析实战训练的一套Python二手房数据分析项目资料内容覆盖源代码、详细解析文档和答辩讲解PPT。整套资料围绕二手房市场数据展开完整演示数据读取、清洗、转换、挖掘与可视化的流程并应用NumPy、Pandas、Matplotlib等常用数据分析库适合已有Python基础并希望提升项目经验的学习者参考使用。压缩包共一百一十四个文件其中包括十八个Python脚本文件作为核心功能实现十七个CSV数据文件存放原始与清洗后的数据十五个HTML网页文件便于展示分析结果同时配备讲解PPT、PNG图片等辅助材料整体大小约二十九点三一兆目录结构清晰。项目曾获九十八分评价并得到导师指导与认可解析文档针对每个处理环节作出详细说明可帮助理解算法思路、排查常见问题所有源码已在本地编译调试并确认可运行。目前已有四十二人浏览学习适合用作课程设计或毕业设计的完整范本。1. 一份二手房分析项目先绕开两个最常见的坑拿到这份项目资料时我先注意到的是那一串 CSV 文件名既有ershoufang-origin-utf8.csv又有ershoufang-origin-ansi.csv后面还跟着_old和clean-v1.1两个版本。这种命名方式很真实说明数据集的清洗过程是分阶段完成的而不是一次成型。对于做 Python 数据分析的人来说这正是业务场景的常态——原始数据永远是脏的、乱的、编码不统一的真正的分析工作有一半以上耗在整理上而不是跑模型。这个项目的价值不在于算法多复杂而在于它把一条完整的数据分析链路走通了从原始 CSV 读取、编码识别、数据清洗、字段类型转换到 Pandas 聚合和 Matplotlib 可视化最后形成可以直接答辩的 PPT 素材。以分析室内面积、朝向和总价的关系为例这个项目既覆盖期末大作业要求的完整流程又提供了可复现的代码思路。如果你手头也有一批不规整的数据表这篇内容能帮你直接替换字段名复用整套逻辑。我在拆解时把重心放在两个最影响结果质量的环节——编码处理和重复值清洗这两处做好了后续的统计图和分析结论才站得住脚。2. 读取原始 CSV编码识别与 Pandas 的 encoding 参数2.1 UTF-8 与 ANSI 混存文件读取的第一个分岔口数据文件里有ershoufang-origin-utf8.csv和ershoufang-origin-ansi.csv两份内容相同但编码不同。这种情况在实际项目中非常常见有人用 Windows 上的 Excel 保存数据默认落盘就是 ANSI在中文 Windows 下通常对应 GBK有人从网页端导出拿到的是 UTF-8。如果 Pandas 的read_csv不加 encoding 参数直接读会遇到UnicodeDecodeError代码在执行到读取这一步就会中断后面的清洗和分析全部没法跑。处理这类问题之前先搞清楚一个判断路径。GBK 是双字节编码UTF-8 是变长编码二者的字节序列有区别。推荐先用二进制方式打开文件观察前几个字节是否带 BOM\xef\xbb\xbf或者让 Python 用chardet库自动检测这是快速确定编码的常见做法。在这个项目里文件命名已经帮我们标注了编码所以读取逻辑可以很清晰import pandas as pd def load_house_data(path: str, encoding: str utf-8) - pd.DataFrame: 读取二手房原始 CSV返回 DataFrame。 路径里带 ansi 的文件用 gbk 编码带 utf8 的用 utf-8。 if ansi in path.lower(): encoding gbk df pd.read_csv(path, encodingencoding) print(floaded {path}, shape{df.shape}) return df # 读取两份不同编码的原始数据 df_utf8 load_house_data(ershoufang-origin-utf8.csv) df_ansi load_house_data(ershoufang-origin-ansi.csv)代码里的判断逻辑很简单文件名中出现ansi字符串时把编码切换成gbk否则使用默认的utf-8。shape打印出来可以第一时间确认数据的行数和列数是否与预期一致。这里有一个值得注意的细节read_csv的encoding参数只影响文本解码方式不影响后续的数值计算但编码搞错所有字段都可能变成乱码类似于把人名、小区名和朝向全部打上错误标签分析出来的区域均价自然没有参考价值。2.2 读取之后先看 dtypes 和缺失值分布编码问题解决之后不要急着做清洗。先运行下面几行命令搞清楚数据表的长相# 查看各列数据类型和缺失情况 print(df_utf8.dtypes) print(df_utf8.isnull().sum()) # 只显示前 5 行检查列名是否正常 print(df_utf8.head())这个步骤的意义在于确认列名的规范性。有些 CSV 文件的首行会带空字符或者隐藏的\ufeffBOM导致第一列列名变成\ufeff标题后续按列名取数时会报KeyError。如果发现列名异常可以用df.columns [col.strip().replace(\ufeff, ) for col in df.columns]批量修正。另外isnull().sum()会给出每一列缺失值的数量这个数字决定了清洗策略——缺失占比低于 5% 的列可以直接删行高于 30% 的列要考虑字段本身是否有保留价值。这个环节通常还会暴露一个隐蔽问题数值列被读成了 object 类型。原因是原始数据里混入了空字符串或者单位符号比如面积列里出现78.5平这种带单位的写法Pandas 就会整列降级为字符串。dtypes 的输出能让你一眼发现这种异常。列类型常见误读原因处理方式object混入单位、空格、逗号用pd.to_numeric(..., errorscoerce)强制转换float64正常数值直接使用无需额外处理int64楼层、室数检查是否有 NaN有的话用中位数填充pd.to_numeric的errorscoerce参数是这里的关键当某个字段无法转换为数字时它会将该位置置为 NaN而不是让整个转换过程抛异常。这种方式最稳妥比逐个单元格去replace要高效得多。2.3 用 sample 代替 head 检查数据分布head()只能看到前 5 行如果原始数据是按区域排序的前 5 行只能反映一个区域的房源情况无法代表全局。这个项目里有ershoufang_old.csv这个文件说明数据可能是分多次收集后合并的顺序上可能带着批次特征。我一般会用df.sample(10, random_state42)随机抽 10 行来检查这样能更大概率看到不同区域、不同价位的记录。random_state固定随机种子保证每次抽查结果一致方便对照清洗前后的变化。3. 用 Pandas 做实质性清洗去重、缺失值填充与字段规整3.1 重复数据不是简单的 drop_duplicates这个项目提供了ershoufang-clean-utf8-v1.1.csv和ershoufang-clean-ansi-v1.1.csv两个清洗后的版本说明清洗过程经过了迭代。v1.1 相对于原始文件核心变化之一肯定是去重。但直接调用drop_duplicates()有一个隐患它会把所有列完全一致的记录视为重复而实际业务里两条房源记录的发布时间不同、带看次数不同但房屋本身是同一套并不算真正意义上的重复。3.1.1 按关键字段去重对于二手房数据判断同一套房源最靠谱的字段组合是小区名称 户型 面积 朝向。总价和单价在一段时间内可能调整但房屋本身的属性不会变。所以去重逻辑应该这样写# 按核心属性判断重复保留第一次出现的记录 df_dedup df_utf8.drop_duplicates( subset[小区, 户型, 面积, 朝向], keepfirst ).copy() print(f去重前: {len(df_utf8)} 行, 去重后: {len(df_dedup)} 行)subset参数定义了判断重复的列集合keepfirst表示保留首次出现的记录。之所以加.copy()是因为 Pandas 的链式操作容易触发视图警告后续如果对df_dedup做修改可能会影响原 DataFrame。使用copy()可以把数据复制一份避免副作用。3.1.2 重复率过高时检查字段取值如果去重后行数减少了 20% 以上复查逻辑是否过于宽松。有可能朝向字段的取值不统一比如同一套房一条记录写南北另一条写南 北语义相同但字符串不同导致漏去重。处理方式是先把字段内容规范化先用str.strip()去掉首尾空格再str.replace( , )去掉内部空格最后在去重。# 清洗字符串字段中的杂散空格 for col in [朝向, 户型]: df_utf8[col] df_utf8[col].str.strip().str.replace( , ) # 重新去重 df_dedup df_utf8.drop_duplicates(subset[小区, 户型, 面积, 朝向], keepfirst)这里的str.strip()去除首尾空白str.replace( , )去除字符串内部的空格避免南 北和南北被当成两个不同值。这些字符串操作往往不为人注意却直接决定了去重是否彻底。3.2 缺失值处理能填的不用 dropna 删行原始数据集里总价、单价、面积、楼层这几个关键字段缺失比例通常较低直接dropna()删掉整行影响不大。但有些字段比如装修情况或者建筑年代可能缺失率高达 40% 以上把这些行全删掉会损失大量样本导致后续分组统计时某些区域没有数据。这时应该分列处理# 按列处理缺失值 df_clean df_dedup.copy() # 数值列缺失值用该列中位数填充 numeric_cols [总价, 单价, 面积] for col in numeric_cols: if col in df_clean.columns: median_val df_clean[col].median() df_clean[col] df_clean[col].fillna(median_val) # 类别列缺失值填充为未知 category_cols [朝向, 装修, 建筑年代] for col in category_cols: if col in df_clean.columns: df_clean[col] df_clean[col].fillna(未知)fillna(median_val)用的是该列的中位数不是平均数。原因是房价、面积这类数据有长尾分布个别豪宅会把平均值拉高中位数更能代表普通房源的典型水平。类别列填充未知而不是删行是为了保留该房源的其余属性比如朝向未知但面积、价格都真实存在分组分析时仍然有用。3.3 单位统一把平米万元等字符剥离干净在真实的二手房 CSV 里面积列经常长这样78.5平、89㎡、120.3平米总价列可能是560万或560万元。这些带单位的字符串无法参与数值计算必须先清洗。import re def clean_area(value): 提取字符串中的纯数字部分单位不一致时统一按平方米处理 if isinstance(value, str): match re.search(r\d\.?\d*, value) if match: return float(match.group()) return value def clean_price(value): 万元为单位需要去掉万字并保留数值 if isinstance(value, str): match re.search(r\d\.?\d*, value) if match: return float(match.group()) return value df_clean[面积] df_clean[面积].apply(clean_area) df_clean[总价] df_clean[总价].apply(clean_price)正则表达式\d\.?\d*匹配整数或带小数的数字返回第一个匹配到的数字串。这里用apply对整列逐行执行效率比 for 循环高很多。清洗完成后再用df_clean[面积] pd.to_numeric(df_clean[面积], errorscoerce)做一次强校验确保所有值都是 float之前提到的低效重复就能避免掉。关于这个项目的案例流程用一张表可以概括不同数据问题的处理优先度数据问题处理优先级参考方法与参数编码错误第一优先不解决即全乱encodinggbk或utf-8重复记录第二优先影响所有统计指标drop_duplicates(subset关键列, keepfirst)数值列含单位第三优先影响计算apply 正则提取数字缺失值第四优先影响样本量fillna(statemedian)类别列填未知这种处理顺序不是绝对的但按这个次序做每一步的输入都是可控的排查问题时会省很多时间。4. 用 Matplotlib 做数据可视化让图表回答业务问题4.1 先从直方图看分布形态数据分析项目里可视化不是把数据画出来就完事而是用图表回答一个具体问题。这个项目最典型的分析问题是总价集中在哪个区间单价分布是正态还是偏态这类问题用直方图最直接import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 解决中文标签乱码 plt.rcParams[axes.unicode_minus] False # 解决负号显示异常 fig, ax plt.subplots(figsize(10, 6)) ax.hist(df_clean[总价], bins30, color#6699CC, alpha0.75, edgecolorblack) ax.set_xlabel(总价万元) ax.set_ylabel(房源数量) ax.set_title(二手房总价分布直方图) plt.tight_layout() plt.savefig(price_distribution.png, dpi150) plt.show()bins30决定直方图的分组数量组数太少会掩盖分布细节太多会显得杂乱。对于几千条数据30 是个合理起步值视情况可以调整到 50。alpha0.75控制柱子的透明度多图层叠加时会比较有用。保存图片时用dpi150在答辩 PPT 上不会模糊。4.2 用散点图看单价与面积的关系直方图只能展示单变量的分布如果要解释大面积房源是否单价更低这类问题散点图是更好的选择fig, ax plt.subplots(figsize(10, 6)) ax.scatter( df_clean[面积], df_clean[单价], s12, alpha0.4, c#CC6666 ) ax.set_xlabel(面积平方米) ax.set_ylabel(单价万元/平方米) ax.set_title(面积与单价散点图) plt.tight_layout() plt.savefig(area_vs_price.png, dpi150) plt.show()散点图的s参数控制点的大小这里调成 12 是因为数据量偏大点太大会严重重叠。alpha0.4是为了在半透明效果下显露点密集区域密集程度本身就是信息——它告诉你大多数房源的面积和单价集中在哪个区域。4.3 用柱状图对比各区域均价这一步是多数据表合并的典型场景——原始数据里有latlng.csv文件它提供了经纬度信息用途之一就是把房源按所在区进行分类然后计算每个区的平均单价。这里用到的聚合操作是groupby# 假设 df_clean 里有区域字段计算每个区域的平均单价 region_price df_clean.groupby(区域)[单价].mean().sort_values(ascendingFalse) # 绘制水平柱状图避免区域名称过长遮挡 fig, ax plt.subplots(figsize(10, 8)) region_price.plot(kindbarh, axax, color#6699CC) ax.set_xlabel(平均单价万元/平方米) ax.set_ylabel(区域) ax.set_title(各区域二手房平均单价对比) plt.tight_layout() plt.savefig(region_price_bar.png, dpi150) plt.show()groupby(区域)[单价].mean()是数据分析里出现频率最高的聚合形式按区域分组对单价求均值然后sort_values排序使得图表呈现从上到下递减的视觉效果。水平柱状图之所以用barh是因为区域名称一般是 2 到 4 个字水平放置时不会被旋转的字号压缩页面显示更清晰。在展示或答辩 PPT 里通常建议把这三张图并列呈现——直方图讲分布、散点图讲关系、柱状图讲对比。一张图回答一个问题比把所有内容塞进一张图更容易让听者跟上思路。这里用柱状图排序后的结果也可以顺带用一个表格输出方便直接粘贴到文档里区域平均单价万元/平方米房源数量区域A5.42312区域B4.18458区域C3.76291这个表格如果在清洗完成后打印可以作为答辩 PPT 中展示数据概览的底稿。图案和表格一起呈现内容的可信度会更高这也是项目拿到高分的原因之一——不是图花哨而是图表之间逻辑自洽。4.4 中文字体问题在 Linux 系统下的处理这段要特别提一下因为不少人在 Windows 本地写代码没问题一到 Ubuntu 服务器或 WSL 环境跑plt.show()图表里的中文全部变成方块。原因是 Linux 系统默认没有 SimHei 字体。常见的做法有两种任选其一方式一代码里指定系统中文字体路径import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties # 指定已安装的中文字体文件以 Noto Sans CJK 为例 font_path /usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc font_prop FontProperties(fnamefont_path) plt.rcParams[font.family] font_prop.get_name()方式二在线下载字体文件到本地目录再加载。注意不要全局设置plt.rcParams[font.sans-serif]为不存在的字体名那样设置完之后问题照旧。用fc-list :langzh命令可以在 Linux 终端里查看可用的中文字体列表确认后再在 Python 里配置能节省很多排查时间。5. 进阶技巧用 loc 与 iloc 让数据切片更干净5.1 为什么不用裸的中括号条件筛选很多人在做分析时习惯这样写# 不推荐的写法直接链式赋值会触发警告 df_clean[df_clean[区域] 区域A][单价] df_clean[单价] * 0.95这里的问题在于df_clean[...]返回的是一个视图还是副本取决于 Pandas 内部逻辑链式赋值时修改可能不会生效或者弹出SettingWithCopyWarning。在答辩 PPT 里看到这个警告很影响观感而且如果数据量大视图和副本的内存行为也难以预测。推荐的做法是先用loc精确选择行列# 推荐写法loc 同时指定行条件和目标列 mask df_clean[区域] 区域A df_clean.loc[mask, 单价] df_clean.loc[mask, 单价] * 0.95loc[mask, 单价]中第一个参数mask是布尔索引选择满足条件的行第二个参数单价是要操作的列。行和列同时定位修改的就是原 DataFrame 的真实数据不会产生视图语义。5.2 整表精确定位用 iloc 配合 shapeiloc按整数位置取值在需要浏览数据表的某个局部区域时很实用。比如把清洗后的前 100 条记录导出到一个单独文件作为旧版本ershoufang_old.csv的对照样本# 提取前 100 条记录用作数据核对 sample_df df_clean.iloc[0:100, :] sample_df.to_csv(check_sample.csv, indexFalse, encodingutf-8-sig)iloc[0:100, :]的0:100表示第 0 到第 99 行冒号表示全部列。写入时用encodingutf-8-sig而不是utf-8是为了让生成的 CSV 在 Windows Excel 中直接双击打开时不出现中文乱码这对答辩前检查数据非常实用。这个小细节切实解决了实际教学中高频出现的问题——很多时候不是数据算错了而是 Excel 打开 CSV 时编码不识别。另外在拼接新旧数据时pd.concat的ignore_indexTrue也是一个值得记住的参数。两份清洗过的数据纵向堆叠如果不设ignore_indexTrue合并后 DataFrame 的索引会重复导致后续loc按索引取值时匹配到多行。设了之后行索引会从前到后重新排列成 0、1、2…这一行参数往往能避免很多debug时间。整个项目看下来编码识别、关键字段去重、数值列的强制类型转换、按区域聚合、utf-8-sig的导出保存这些操作不大但都踩在真实业务数据的痛点上。把这套流程跑通一次提取出自己数据集里对应的字段再配合直方图、散点图和柱状图来验证结论一份看得过去的数据分析报告就有雏形了。本文还有配套的精品资源点击获取