ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用pandas做二手房数据分析:从数据清洗到可视化全流程

用pandas做二手房数据分析:从数据清洗到可视化全流程 最近帮一个做房产中介的朋友处理二手房挂牌数据顺手整理了一套用 pandas 做房地产市场分析的完整流程。几个月前他给我甩过来一个几万行的 Excel让我帮他看看“哪个区域、什么户型最好卖定价多少比较合理”当时我第一反应就是这种活儿用 pandas 做最合适清洗、筛选、分组聚合、透视统计一套组合拳打下来半小时就能拿到能看的结果。这篇文章不是我凭空想出来的就是这次实操的记录。我会从数据准备、清洗、分组聚合到可视化把整个过程完整走一遍其中会穿插很多真实踩过的坑。适合刚学完 pandas 基础语法、想拿真实数据练手的人也适合已经在用 pandas 但总感觉分析思路不够系统的朋友。你不需要懂太多统计学知识只要会 Python 基础跟着步骤走就能跑通整个分析流程。1. 项目思路与数据准备1.1 为什么拿房地产数据分析来练手很多人在 pandas 入门阶段最大的困惑是书上的例子看懂了Series、DataFrame 都会建了但一到真实场景还是不知道从哪里下手。我特别推荐用房地产市场数据作为练手项目原因是这个场景的数据维度非常丰富一套数据里能同时练到 pandas 的大部分核心功能一份房源数据里通常包含总价、单价、面积、户型、朝向、楼层、所在区域、建造年份、挂牌时间等字段。这些字段有的是数值型有的是字符串有的是时间天然适合练习数据类型转换数据里几乎必然有缺失值、重复值、异常值适合练清洗二手房挂牌价格受区域、面积、房龄多重影响适合练多条件筛选和分组聚合。而最重要的一点是房地产价格是所有人都能感知的东西分析结果对不对、合不合理你一眼就能判断出来这比分析一些抽象的工业数据要有反馈感得多。另外房地产数据分析的结论还可以落地。比如我这次分析的结果朋友直接拿去用作门店选品和定价参考虽然不构成严谨的投资建议但至少比凭感觉拍脑袋靠谱。这种“分析完能被人用起来”的成就感也是推动我持续去做数据项目的重要动力。1.2 数据从哪来以及如何装进 DataFrame做房地产数据分析第一步是拿到数据。我整理了几种常见的合法数据来源按推荐程度排序数据来源特点适合做什么链家/贝壳公开挂牌数据字段全包含总价、单价、面积、户型、朝向、楼层、区域二手房价格画像、区域对比各地统计部门公开的房地产月报包含销售面积、销售金额、开发投资额宏观趋势分析Kaggle 上的房价数据集干净、有标准答案但离真实场景稍远练习建模和特征工程自己爬虫抓取需遵守网站协议灵活但需要注意合法合规自定义字段的专项分析如果只是想练技术我建议先从链家或贝壳的公开列表页手动导出部分数据或者直接在网上找一份现成的 CSV/Excel 的房源数据。注意数据来源要合法仅用于个人学习。拿到数据后第一步不是急着分析而是先把它装进 DataFrame然后快速看一眼全貌。假设你拿到一份名为house_data.xlsx的二手房数据读取方式很简单import pandas as pd df pd.read_excel(house_data.xlsx)读进来之后养成先看两个东西的习惯df.info()和df.head()。df.info()能看到每一列的名称、非空值数量、数据类型这是后续做数据清洗的重要依据df.head()则是直接看前几行长什么样。我每次拿到新数据都会先执行这两条命令再去想后面的分析怎么做。提示如果你的数据文件很大比如几百万行读取时可以用usecols参数只读需要的列或者用dtype参数指定某些列的类型这样能大幅减少内存占用和读取时间。比如总价列如果读进来是字符串可以在读取时就指定dtype{总价: str}后面再自己处理。关于读取的格式还有几个细节值得单独说如果文件是 CSV读取时一定要关注编码问题。Windows 环境下导出的 CSV 经常是 GBK 编码直接pd.read_csv(xx.csv)会报UnicodeDecodeError需要指定encodinggbk或者encodinggb18030。如果文件是 Excel后缀为.xlsx时 pandas 底层依赖openpyxl后缀为.xls时依赖xlrd。没有这两个库会报错直接pip install openpyxl xlrd装上就行。如果数据在数据库里用pd.read_sql(sql, conn)直接读取查询结果中间不需要导出 Excel更省事。1.3 先搞清楚字段含义再动手拿到数据之后最忌讳的事情是看了一眼列名就直接开始写聚合代码。房地产数据的字段命名往往比较随意同一个概念在不同平台上有不同叫法。比如“总价”有的叫“总价万元”有的叫“挂牌价”有的干脆叫“Price”“面积”有的带单位写在字符串里比如“89.5平米”有的是纯数字。我第一次拿到这份数据时发现里面有个字段叫“house_info”点开一看内容是“3室2厅 | 89.5平米 | 南北 | 精装”一个字段里塞了户型、面积、朝向、装修四种信息如果不先拆分出来后面完全没法用。后来我用str.extract()配合正则表达式把这一列拆成了四个独立字段才算把数据理顺。所以拿到数据后的正确操作顺序是先df.head()看前几行再df[列名].value_counts()看每一列的分布情况对理解字段含义非常有效。比如户型列你可以通过value_counts()立刻知道这个数据里有哪些户型、各自占比多少也能发现一些奇怪的取值比如“5室3厅6卫”这种极端值。2. 数据清洗分析前最费时间的环节2.1 字段类型与原始数据的“脏乱差”真实场景下的房地产数据几乎不可能拿过来直接就能分析。我拿到这份数据后光是清洗就花了一半时间。最常见的脏乱差情况有三类。第一类是数字和单位混在一起。总价列的内容长这样“850万”、“总价850万”、“750万已降50万”。这种混合文本必须先清洗成纯数字才能计算。我的处理办法是先用正则表达式提取数字部分再转成数值类型import re def extract_price(s): if pd.isna(s): return pd.NA # 提取第一个数字含小数点单位默认为万 match re.search(r(\d\.?\d*), str(s)) if match: return float(match.group(1)) return pd.NA df[总价_万] df[总价].apply(extract_price)第二类是同一字段里混着不同单位。比如面积列有的数据是“89.5平米”有的直接是“89.5”还有的是“89.5平”。如果直接astype(float)会直接报错。更稳妥的做法是先统一格式再转换或者自定义一个清洗函数把“平米”“平”“㎡”这些单位词全部去掉只保留数字。第三类是数据中夹带备注信息。比如朝向列里出现“南北通透采光好”这种描述性文字比如楼层列里出现“低楼层共32层”。这些信息本身有价值但如果想用 pandas 做结构化分析最好把备注信息拆到单独列或者直接丢弃只保留结构化字段。拆楼层可以用str.extract把“共多少层”和“第几层”分别提取出来df[楼层] df[楼层信息].str.extract(r^(.层)) df[总楼层] df[楼层信息].str.extract(r共(\d)层).astype(float)2.2 缺失值、重复值与异常值处理清洗数据时我习惯按照“缺失值 - 重复值 - 异常值”的顺序来处理每一步都要先看统计量再做决定而不是直接dropna()一把梭。先看缺失值。使用df.isna().sum()能快速知道每一列缺了多少数据。关键不在于有没有缺失而在于缺失比例和缺失字段的重要性。比如“装修情况”这个字段缺失了 5%影响不大可以选择直接丢掉这几行但“总价”是核心字段如果缺失了基本只能删除因为后面所有分析都要用到它。如果缺失比例不高比如低于 20%对核心字段直接dropna(subset[总价_万])是合理的如果某个非关键字段缺失很多可以考虑用“未知”填充而不是硬删行df[装修情况] df[装修情况].fillna(未知)再看重复值。房地产挂牌数据很容易出现重复因为中介平台会重复录房源或者同一房源在不同时间被多次挂牌。用df.duplicated()可以快速找出重复行但这里有一个坑直接判断重复行会把所有字段完全相同的记录视为重复但实际业务中同一个房子可能因为楼层描述略有差异而字段不完全一样。所以去重时我一般会用subset参数指定关键字段比如小区名称、面积、总价、户型这几个字段都一致基本可以认定是同一房源df df.drop_duplicates(subset[小区, 面积_平米, 总价_万, 户型], keepfirst)最后处理异常值。异常值的判断需要一些业务常识。比如面积小于 5 平米的房子大概率是车位或者数据录入错误单价超过同区域平均单价 3 倍以上的大概率是别墅或者录入错误总价为 0 的记录直接删除即可。我处理异常值的方法是先看描述统计再用条件筛选把明显离谱的数据揪出来。# 看看描述统计找出异常区间 print(df[[总价_万, 面积_平米, 单价]].describe()) # 删除明显异常的数据 df df[(df[面积_平米] 5) (df[面积_平米] 500)] df df[df[总价_万] 0]删除之后我会再跑一次describe()对比一下清洗前后各个字段的均值、最大最小值确认清洗没有误删正常数据。这一步虽然朴素但非常关键因为如果清洗阶段处理不当后面所有分析结果都会失真。3. 核心分析价格分布、区域差异与关联因素3.1 数据概览与描述统计清洗完数据正式分析就开始了。我习惯先做一轮全局性的描述统计搞清楚总体情况再逐步下钻到细分维度。用df.describe()可以一次性看到总价、面积、单价等数值型字段的样本量、均值、标准差、最小值、四分位数和最大值。这里要说一个容易忽略的知识点describe()默认只对数值型列生效如果数据里还有分类字段可以用includeobject参数单独查看分类字段的统计信息。描述统计不只是看一眼均值就完了我通常会重点关注三个值均值与中位数的差异、标准差大小、最大值的合理性。如果总价的均值明显高于中位数比如均值 650 万、中位数 480 万说明数据里有不少高价房源把整体均值拉高了这时候分析“典型房价”应该优先看中位数而不是均值。如果标准差特别大说明不同房源之间的价格差异很大这时候做全局分析意义不大应该按区域、户型分组去看。如果最大值是那种“一栋写字楼挂 8 个亿”的数据前面清洗时漏网了这时候需要回溯到清洗步骤确认过滤条件是否有问题。描述统计还会帮助我快速了解户型和朝向的分布。比如用df[户型].value_counts()能看到哪种户型的挂牌量最大用df[朝向].value_counts()能发现“南北”朝向永远是二手房市场的主流。3.2 分组聚合各区域/户型的价格画像描述统计回答的是“整体什么水平”的问题但房地产分析的核心价值在于“不同区域、不同户型之间差别有多大”。这一步要用到 pandas 的分组聚合功能。我这次分析时先按“区域”分组看各区域的挂牌量、平均总价、中位数总价和平均单价region_stats df.groupby(区域).agg( 挂牌量(总价_万, count), 平均总价(总价_万, mean), 中位总价(总价_万, median), 平均单价(单价, mean) ).sort_values(中位总价, ascendingFalse)这里为什么要同时看均值和中间值因为不同区域的价格分布形态差异很大。有些区域大量房源集中在 400-600 万区间但区域里有几个顶豪楼盘总价直接两千万起步均值就会被拉得很高看起来“这个区域好贵”但实际上普通买家在这个区域根本感受不到均值那么高的价格。中位数则能给出更接近体感的价格水平。所以我做区域画像时中位数是必看指标。再看户型维度用groupby配合pivot_table做交叉分析这样能同时看到区域和户型两个维度对价格的影响pivot pd.pivot_table( df, values总价_万, index区域, columns户型, aggfuncmedian, fill_value0 )透视表的结果非常直观一眼就能看出哪个区域的哪个户型最贵、哪个户型挂牌量最少。我朋友拿到这份透视表后立刻就看出来他们门店所在的区域“三室”户型的中位总价比隔壁区贵了将近 20%这在做门店选品和房源开发策略时是很重要的参考信息。分组聚合适用的场景很多但有几个细节值得注意groupby之后如果直接mean()只会对数值列计算均值如果分组字段里混进了字符串列会直接忽略建议用.agg()明确指定要汇总哪些字段、用什么聚合函数。如果聚合函数需要同时看多个字段的不同统计量用.agg()配合元组列表最灵活比如agg([(平均总价, mean), (中位总价, median)])。分组之后如果还要排序可以在groupby后直接接sort_values也可以先reset_index()再排序两种方式都能得到同样的结果看个人习惯。3.3 字段间关系面积与价格、房龄与价格分组聚合回答的是“什么区域、什么户型什么价”但房地产分析里还有一个重要的问题值得探索面积和总价是什么关系房龄对价格影响大吗判断两个连续变量之间的关系最直接的方法是算相关系数。用 pandas 的.corr()方法可以快速得到字段之间的皮尔逊相关系数矩阵corr df[[总价_万, 面积_平米, 单价, 房龄]].corr() print(corr)以我这次分析的结果举例总价和面积的相关系数大约是 0.87说明两者存在强正相关——面积越大总价越高这个因果关系很直接总价和房龄的相关系数大约是 -0.35说明房龄越大价格越低但相关性不强因为房龄对价格的影响还受到了区域位置、周边配套、小区品质等很多因素的干扰。相关系数不等于因果这一点要格外提醒看到相关性强不要直接下结论说“一定是这个原因导致的”只能说“两者存在较强的线性关联”至于因果关系的验证那是更高阶的统计模型要解决的事。除了相关系数我还会用条件筛选来找极端样本。比如筛出“面积在 80-100 平米之间但总价差异特别大”的房源看看到底是什么因素导致同样面积价格差了一倍多sample df[(df[面积_平米] 80) (df[面积_平米] 100)] extreme_high sample.sort_values(总价_万, ascendingFalse).head(10) extreme_low sample.sort_values(总价_万, ascendingTrue).head(10)这种下钻分析能发现很多有意思的细节。比如我这次发现同样 90 平米左右的房子核心地段的挂牌总价可能比远郊贵出一倍以上而且高价房源里“次新房”占比远高于老破小。这说明地段和房龄是除面积之外影响价格的两个重要变量下一步分析就可以按“区域 房龄段”继续下钻。对房龄维度我习惯先做离散化处理把连续房龄变成几个区间再分组比较bins [0, 5, 10, 15, 20, 100] labels [5年以内, 6-10年, 11-15年, 16-20年, 20年以上] df[房龄段] pd.cut(df[房龄], binsbins, labelslabels) age_analysis df.groupby(房龄段, observedFalse)[单价].agg([mean, median, count])pd.cut是处理连续变量分段的标准工具注意边界值的设计要与业务语义一致。这个分析做完之后就能非常直观地看到房龄对单价的衰减影响——通常 5 年以内的次新房挂牌单价会明显高于同区域的老房源。4. 可视化辅助判断与报告输出4.1 画几张三分钟能看懂的图分析做出来一堆数字如果不配图拿给别人看的时候说服力会大打折扣。pandas 的绘图功能是基于 Matplotlib 封装的简单场景下直接df.plot()就能出图。我这次主要画了三类图都是能在三分钟内快速上手的第一类是总价分布直方图用来观察整体价格分布形态import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 解决中文乱码 plt.rcParams[axes.unicode_minus] False # 解决负号显示 df[总价_万].plot.hist(bins50, figsize(10, 6)) plt.xlabel(总价万元) plt.title(房源总价分布直方图) plt.show()如果直方图呈现明显的右偏长尾往右拖说明大部分房源集中在相对较低的价格区间少数高价房源拉长了尾巴。这时候前面说的“均值被拉高”就得到了直观验证应该优先看中位数。第二类是各区域房价箱线图用来对比不同区域的分布离散程度df.boxplot(column总价_万, by区域, figsize(12, 6)) plt.xticks(rotation45) plt.show()箱线图能同时展示最小值、下四分位数、中位数、上四分位数和异常值区域之间的对比一目了然。我尤其喜欢箱线图上拉出的异常值点这些点在业务上往往有明确含义可能是豪宅、别墅或者误录入数据值得单独下一钻看看。第三类是面积-总价散点图用来观察两个连续变量之间的关系df.plot.scatter(x面积_平米, y总价_万, alpha0.3, figsize(10, 6)) plt.xlabel(面积平方米) plt.ylabel(总价万元) plt.title(面积与总价关系散点图) plt.show()散点图能直观呈现相关性但更重要的是能发现“孤立的点群”——比如在散点图右上角有一小撮面积大、单价也明显偏高的点那大概率是别墅或特殊房源可以把它们单独筛出来分析。注意如果是在 Jupyter Notebook 里跑需要加上%matplotlib inline才能正常显示图片。如果是在 PyCharm 里跑plt.show()会弹窗显示图表。写代码之前设置好中文字体否则图上的中文会变成方块。4.2 把分析结果整理成 Excel 报告分析做完之后最终交付物往往不是代码而是一份能让别人直接打开看、甚至直接拿去用的报表。我一般会把所有关键结果整合进一个 Excel 文件每个分析维度放在不同的 Sheet 里用 pandas 的ExcelWriter一步搞定with pd.ExcelWriter(房产数据分析报告.xlsx, engineopenpyxl) as writer: df.to_excel(writer, sheet_name清洗后数据, indexFalse) region_stats.to_excel(writer, sheet_name区域价格统计) pivot.to_excel(writer, sheet_name区域-户型透视) age_analysis.to_excel(writer, sheet_name房龄价格分析)生成文件之后我通常还会再加几行代码调整一下显示效果——设置列宽、冻结首行、给表头加粗。这一步用 pandas 配合 openpyxl 的底层 API 可以做但稍微有点繁琐。更省力的替代方案是把结果导出到 Excel 之后手动调整反正是给同事看的关键是数据准确、结构清晰。写报告时我还有一个习惯每一个 Sheet 里加一列“分析结论”摘要用一两句话说明这张表告诉了我们什么。比如在区域价格统计表下面加一行“中心城区中位总价最高远郊挂牌量最大但中位价最低”。这样做的好处是看报告的人可以先读结论再按需下钻到数据明细。5. 常见问题与排查技巧实录5.1 让人崩溃的报错与解决方案每次做 pandas 项目总会遇到各种报错。这里整理几个我在房地产数据分析过程中真实遇到、且高频出现的报错按严重程度排出优先级报错一AttributeError: module pandas has no attribute core这是我见过最多人踩的坑。出现这个报错通常不是因为代码写错了而是环境里存在多个 pandas 版本或者某个第三方库安装时覆盖了 pandas 的旧版本导致模块解析异常。排查思路是先确认当前 Python 环境里 pandas 是哪个版本再看是不是需要重装。pip show pandas pip uninstall pandas pip install pandas --upgrade如果是 conda 环境也可以用conda install pandas --force-reinstall。我建议所有数据处理项目都单独建一个虚拟环境或 conda 环境避免不同项目之间的包互相干扰这是从根源上规避版本混乱最有效的办法。报错二ModuleNotFoundError: No module named pandas这个报错最简单直接当前环境压根没装 pandas。用pip install pandas或者conda install pandas装上即可。另外提醒一下如果用的是 Jupyter Notebook安装之后可能需要重启 Kernel 才能生效。报错三UnicodeDecodeError或中文乱码读 CSV 文件时最常见的坑。Windows 环境下导出的 CSV 通常是 GBK 编码直接用默认 UTF-8 读取就会报错。解决方案是读取时指定编码df pd.read_csv(house_data.csv, encodinggbk) # 或者用 gb18030兼容性更好 df pd.read_csv(house_data.csv, encodinggb18030)而写 Excel 文件时如果是.xlsx格式默认 UTF-8 没问题但用to_csv输出时要注意用encodingutf-8-sig这样生成的 CSV 用 Excel 打开才不会乱码。报错四SettingWithCopyWarning这个警告非常磨人但它的出现是在提醒你你的代码可能在对一个 DataFrame 的切片进行赋值而 pandas 不确定你改的是原数据还是副本。最规范的解决方法是尽量不要链式赋值如果需要修改某个子集优先用.loc或者先.copy()明确复制一份再改。# 不建议的写法 sub_df df[df[区域] 中心城区] sub_df[备注] 重点区域 # 可能触发警告 # 推荐写法 sub_df df[df[区域] 中心城区].copy() sub_df.loc[:, 备注] 重点区域报错五内存不足或运行极慢如果数据量特别大df.info()会发现内存占用高得吓人。优化思路有几个只读需要的列usecols、把重复度高的字符串列转成category类型、及时删除不需要的中间变量并调用gc.collect()。我处理几百万行数据时仅把区域、朝向、装修这些低基数列转成category内存就能下降一大半for col in [区域, 朝向, 装修情况]: df[col] df[col].astype(category)5.2 版本变化带来的“老代码失效”pandas 的版本迭代比较频繁网上很多教程和代码是旧版本写的直接复制可能会运行报错。我身边常遇到的一个是df.append()方法在 pandas 2.0 版本中已经被移除了如果代码里用df.append()拼接 DataFrame会直接报AttributeError。现在的标准做法是用pd.concat()# 旧写法pandas 2.0 # df df.append(new_row, ignore_indexTrue) # 新写法 df pd.concat([df, new_row], ignore_indexTrue)还有df.applymap()在 pandas 2.1 版本中也被标记为弃用新的替代方案是df.map()。遇到这种问题解决思路不是强行找旧版本绕开而是去读官方文档了解新接口的用法。我的习惯是直接在命令行里查帮助文档help(pd.concat)或者用pd.show_versions()确认当前环境版本。遇到报错先看版本、再看参数名这是排查大多数函数层面报错的关键路径。5.3 我的几个独家避坑技巧除了前面提到的报错排查还有几个我在实际项目中摸索出来的小技巧平时教程里不太容易看到技巧一用pd.set_option让输出更友好。当 DataFrame 列数很多时默认显示会省略中间的列不利于快速排查。建议在 Notebook 里设置pd.set_option(display.max_columns, None) pd.set_option(display.max_rows, 100) pd.set_option(display.width, 200)技巧二分组聚合结果要记得reset_index()。groupby之后如果直接导出 Excel分组键会变成索引放到 Excel 里就会出现一个没有列名的空表头。加上reset_index()才能把分组键恢复成普通列。技巧三做条件筛选时别忘了括号。这可能是 pandas 新手最容易踩的坑# 直接写会语法错误 df df[df[面积_平米] 50 df[总价_万] 500] # 会报 ValueError # 正确写法每个条件都要加括号 df df[(df[面积_平米] 50) (df[总价_万] 500)]6. 更进一步哪些方向还能继续深入如果前面这些内容你已经全部跟下来了那 pandas 做房地产数据分析的基础框架已经建立起来了。接下来想继续深入可以从三个方向入手。方向一结合地理信息做空间分析。把房源数据中的经纬度字段拿出来在地图上用热力图展示不同区域的价格密度分布比单纯看分组聚合表格要直观得多。Python 生态里有 folium、pyecharts 等库几行代码就能生成交互式地图配上总价字段按颜色映射可以看到非常清晰的房价梯度。方向二引入时间维度做趋势分析。把挂牌时间用pd.to_datetime()转成标准时间格式再用set_index()设为索引配合resample()按月或按季度统计挂牌量、平均单价的走势就能看到市场热度的周期变化。房地产数据的季节性很强金三银四、金九银十在数据里都能看得出来。方向三从“描述现状”走向“预测未来”。pandas 擅长的是数据清洗、特征工程和统计分析但如果你有足够的特征字段面积、户型、房龄、区域、周边配套、交通距离等可以在这个基础上用 scikit-learn 构建一个价格预测模型。这个方向的前提是前面的数据清洗要做得扎实因为决策树和回归模型对数据质量要求很高脏数据会直接影响预测精度。pandas 做特征工程、sklearn 做建模这套组合是很多数据分析师的日常工作流程。不过说实话对于刚开始用 pandas 的人我不建议一上来就奔着机器学习去。先把描述统计、分组聚合、透视表、可视化这些基本功做扎实能独立思考“一个业务问题应该用哪些数据分析方法来回答”后面的模型算法都是锦上添花。回到我帮朋友做的这次分析。数据清洗花了大半天分析代码其实只有几十行最终输出的 Excel 报告他看了十分钟就抓住了重点主力成交户型是哪几个、哪个区域的价格还有上行空间、什么样的房子定价最容易被快速消化。他给我反馈说“你们做数据的人真好有问题直接看表就行不用来回问”。我心里很清楚工具本身不神奇神奇的是懂业务、能拆解问题、然后用合适工具落地的那个过程。pandas 在这套流程里解决的是“拿到一堆又脏又乱的数据之后怎样快速变成能用的表格”这个核心问题。当你真正用 pandas 完成一次从原始数据到决策依据的完整闭环之后你会对这个工具有完全不一样的理解。
返回列表