简介:这是一套面向Python数据分析初学者、毕业设计及期末大作业需求的南京二手房数据可视化完整项目资料,围绕链家网房源数据展开从采集到建模的全流程实践。资源包共157个文件,约40.04MB,包含18个py源码脚本、18个csv原始与清洗数据、65张png可视化图表、15个html页面及11个js文件,另附pptx答辩文档与说明文档,覆盖爬虫、分析、展示各环节。项目综合运用Requests与BeautifulSoup抓取房源信息,借助Numpy、Pandas完成数据清洗,通过Matplotlib绘制分布图表,并采用k-means聚类对房源进行分类归纳,同时结合高德地图JS API实现地理可视化。已有999人学习下载,读者可据此掌握二手房数据采集、清洗、可视化与聚类分析的完整思路,理解房源基本特征与区域分布规律,为购房决策或同类课题提供可复用的代码框架与分析方法。
1. 南京二手房数据可视化分析:从一份 zip 到能讲清楚的图表
南京二手房市场有个很典型的特点:挂牌量高、区域价差大、同一板块内不同小区的单价能差出一倍。想靠人工翻房源列表得出结论,基本不现实。这个标题指向的是一套完整的落地链路——用 Python 把南京二手房数据抓下来或读进来,清洗成规整的表,再用可视化把「哪个区在涨、哪种户型最扛价、总价和面积到底什么关系」讲明白,最后配一份能直接讲的 PPT。它适合三类人:做课程设计的学生、想练手数据分析的 Python 入门者、以及需要快速出一份区域楼市简报的从业者。核心不是画几张图,而是让图表能支撑一个结论。下面按「数据怎么来 → 怎么洗 → 怎么画 → 坑在哪 → 怎么讲」的顺序拆开讲。
2. 数据从哪来:南京二手房数据集的获取与字段设计
2.1 采集还是用现成数据:两条路怎么选
做南京二手房分析,第一步永远是数据。常见做法有两种:一是用 Python 爬虫从公开房源平台抓取,二是直接用整理好的 CSV/Excel 数据集。如果你手上是「带源码」的项目包,大概率已经附了一份静态数据文件,这时候别急着写爬虫,先把数据读进来看字段。
爬虫路线的现实问题是:房源平台普遍有反爬,列表页和详情页字段不一致,翻页参数经常变。对新手来说,把大量时间耗在对抗反爬上,最后分析部分反而草草收尾,这是最常见的翻车方式。我的建议是:课程设计或练手场景,优先用现成数据集跑通全流程;确实要练爬虫,就限定在少量页面,把重点放在字段解析和存储上。
一份能支撑可视化的南京二手房数据,至少要有这些字段:
| 字段名 | 含义 | 类型 | 用途 |
|---|---|---|---|
| district | 所在区(鼓楼、江宁等) | 字符串 | 区域对比 |
| community | 小区名 | 字符串 | 明细查询 |
| area | 建筑面积(㎡) | 浮点 | 面积分布 |
| total_price | 总价(万元) | 浮点 | 总价分布 |
| unit_price | 单价(元/㎡) | 浮点 | 核心指标 |
| layout | 户型(如 3室2厅) | 字符串 | 户型分析 |
| floor | 楼层描述 | 字符串 | 辅助维度 |
| build_year | 建成年份 | 整数 | 房龄分析 |
字段设计的关键是:单价一定要单独存一列,不要每次现算。因为总价和面积里只要有一个是脏数据,现算的单价就会污染整张图。
2.2 用 pandas 把数据读进来并做第一轮体检
拿到数据文件后,先别画图,先体检。下面这段代码是每次开工我都会跑一遍的模板:
import pandas as pd import numpy as np # 读取数据,注意编码,中文数据常见 gbk / utf-8-sig df = pd.read_csv("nanjing_ershoufang.csv", encoding="utf-8-sig") # 第一轮体检:形状、字段、缺失、重复 print("数据形状:", df.shape) print("字段列表:", df.columns.tolist()) print("缺失值统计:\n", df.isnull().sum()) print("重复行数:", df.duplicated().sum()) # 看数值字段的分布,快速发现异常值 print(df[["area", "total_price", "unit_price"]].describe()) # 看区域取值是否规范 print("区域取值:", df["district"].unique())这段代码的逻辑是:shape告诉你数据量够不够支撑分析;isnull().sum()定位哪些列需要清洗;duplicated()查重复房源;describe()用最小值和最大值快速抓异常,比如面积出现 0 或 9999,单价出现个位数,基本都是脏数据。encoding参数是中文数据的第一个坑,用utf-8读出来乱码时换成utf-8-sig或gbk通常能解决。
参数上,describe()默认只统计数值列,如果你把单价存成了带「元/㎡」单位的字符串,这里会直接不显示,说明类型没转对,得先用str.replace去掉单位再astype(float)。
3. 清洗与特征工程:让单价和区域字段能直接进图
3.1 处理缺失值、异常值和单位不统一
真实数据里,面积缺失、总价写成「暂无报价」、单价带单位,都是家常便饭。清洗的目标只有一个:让每一列都是干净的可计算类型。下面是我常用的清洗流程:
# 1. 统一去除字符串字段首尾空格 df["district"] = df["district"].str.strip() df["layout"] = df["layout"].str.strip() # 2. 把带单位的字段转成数值 def to_float(x): if pd.isna(x): return np.nan return float(str(x).replace("元/㎡", "").replace("㎡", "").replace("万", "").strip()) df["unit_price"] = df["unit_price"].apply(to_float) df["area"] = df["area"].apply(to_float) df["total_price"] = df["total_price"].apply(to_float) # 3. 删除关键字段缺失的行 df = df.dropna(subset=["district", "area", "total_price", "unit_price"]) # 4. 用分位数剔除极端异常值 q_low = df["unit_price"].quantile(0.01) q_high = df["unit_price"].quantile(0.99) df = df[(df["unit_price"] >= q_low) & (df["unit_price"] <= q_high)] # 5. 面积和总价也要设合理区间 df = df[(df["area"] >= 10) & (df["area"] <= 500)] df = df[(df["total_price"] >= 20) & (df["total_price"] <= 3000)] print("清洗后数据量:", df.shape)逻辑说明:第 2 步的to_float是核心,把「450万」「32000元/㎡」这类字符串统一成数字,否则后面所有计算都会报错或算错。第 4 步用 1% 和 99% 分位数卡边界,比直接拍脑袋定阈值更稳,因为不同区域单价差异大,固定阈值容易误删。第 5 步的面积和总价区间是业务常识兜底,10㎡ 以下的住宅和 3000 万以上的普通二手房,在分析里都属于噪声。
参数上,分位数可以根据数据量调整:数据少于 1000 条时用 0.05/0.95 更稳,数据上万条时 0.01/0.99 足够。清洗完一定要重新describe()一遍,确认数值范围合理。
3.2 从户型、房龄里挖出能进图的特征
原始字段直接画图往往不够看,需要派生几个特征。最实用的三个:户型拆成室数、从建成年份算房龄、按单价分档。
# 1. 从户型字符串里提取室数,如 "3室2厅" -> 3 df["rooms"] = df["layout"].str.extract(r"(\d+)室").astype(float) # 2. 计算房龄 current_year = 2024 df["house_age"] = current_year - df["build_year"] # 3. 按单价分档,方便做分层对比 bins = [0, 20000, 30000, 40000, 60000, 100000] labels = ["2万以下", "2-3万", "3-4万", "4-6万", "6万以上"] df["price_level"] = pd.cut(df["unit_price"], bins=bins, labels=labels) # 4. 按区域统计均价,作为后续图表的排序依据 district_avg = df.groupby("district")["unit_price"].mean().sort_values(ascending=False) print(district_avg)str.extract用正则从户型里抠出室数,比字符串切片稳,因为户型写法不统一。pd.cut做分档是可视化前的常用预处理,分档后画堆叠柱状图或饼图都方便。最后按区域算均价并排序,这一步的产出直接决定了后面柱状图的顺序,先排好序,图才好看。
这里有个容易忽略的点:build_year如果是字符串或者有空值,house_age会算出一堆 NaN,画图时这些点会消失。所以算完房龄要再dropna一次,或者用中位数填充。
4. 可视化落地:用 matplotlib 和 pyecharts 出四张核心图
4.1 区域均价对比和总价分布怎么画
可视化的第一原则是:一张图只回答一个问题。南京二手房分析里,最该先出的两张图是「各区域均价对比」和「总价分布」。
import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei"] # 中文显示 plt.rcParams["axes.unicode_minus"] = False # 图1:各区域均价柱状图 fig, ax = plt.subplots(figsize=(10, 6)) district_avg.plot(kind="bar", ax=ax, color="#4C72B0") ax.set_title("南京各区域二手房均价对比") ax.set_xlabel("区域") ax.set_ylabel("均价(元/㎡)") for i, v in enumerate(district_avg.values): ax.text(i, v, f"{int(v)}", ha="center", va="bottom", fontsize=9) plt.tight_layout() plt.savefig("district_avg.png", dpi=150) # 图2:总价分布直方图 fig, ax = plt.subplots(figsize=(10, 6)) ax.hist(df["total_price"], bins=40, color="#55A868", edgecolor="white") ax.set_title("南京二手房总价分布") ax.set_xlabel("总价(万元)") ax.set_ylabel("房源数量") plt.tight_layout() plt.savefig("total_price_dist.png", dpi=150)逻辑说明:district_avg是上一节算好的按均价排序的 Series,直接plot(kind="bar")就能出图,排序后的柱子从高到低,一眼能看出价格梯队。柱顶标数值用ax.text循环加,比默认图例直观。直方图的bins参数控制柱子数量,40 个在几千条数据下比较合适,太少看不出分布形状,太多会碎。
中文显示是 matplotlib 的经典坑,SimHei在 Windows 上一般可用,Mac 上要换成Arial Unicode MS,Linux 服务器上如果没有中文字体,图里会全是方框,这时候要么装字体,要么改用 pyecharts 出 HTML。
4.2 用 pyecharts 做可交互的区域-户型热力图
静态图适合放 PPT,但如果想让别人自己点着看,pyecharts 更合适。下面用热力图展示「区域 × 户型」的均价分布:
from pyecharts.charts import HeatMap from pyecharts import options as opts # 构造 区域 x 户型 的均价矩阵 pivot = df.pivot_table(values="unit_price", index="district", columns="rooms", aggfunc="mean").round(0) districts = pivot.index.tolist() rooms = [str(int(c)) + "室" for c in pivot.columns.tolist()] data = [] for i, d in enumerate(districts): for j, r in enumerate(rooms): val = pivot.iloc[i, j] if pd.notna(val): data.append([j, i, int(val)]) heatmap = ( HeatMap() .add_xaxis(rooms) .add_yaxis("区域", districts, data, label_opts=opts.LabelOpts(is_show=True, position="inside")) .set_global_opts( title_opts=opts.TitleOpts(title="南京各区域不同户型均价热力图"), visualmap_opts=opts.VisualMapOpts(min_=pivot.min().min(), max_=pivot.max().max(), is_piecewise=False), ) ) heatmap.render("district_room_heatmap.html")逻辑说明:pivot_table把数据透视成区域为行、室数为列的矩阵,aggfunc="mean"算均价。热力图的data格式是[x索引, y索引, 值],所以循环时 x 对应户型、y 对应区域,别写反,写反了图会转置。visualmap_opts控制颜色映射范围,用矩阵的最小最大值,颜色对比最充分。
参数上,is_piecewise=False是连续渐变,改成True会变成分段色块,看个人偏好。如果某些区域某个户型没有房源,pivot里是 NaN,代码里用pd.notna跳过,避免热力图出现空洞报错。
5. 避坑与排查:南京二手房分析里最容易翻车的五件事
5.1 中文乱码和字体缺失
现象:读 CSV 报UnicodeDecodeError,或者图里中文全是方框。 原因:编码不匹配,或系统没有中文字体。 解决:读取时依次尝试utf-8-sig、gbk;matplotlib 里显式设置font.sans-serif,Linux 环境先fc-list :lang=zh确认有没有中文字体,没有就装一个或用 pyecharts。
5.2 单价算错导致整张图失真
现象:区域均价柱状图里某个区高得离谱。 原因:总价单位是「万」,面积单位是「㎡」,直接相除得到的是「万元/㎡」,和「元/㎡」差了 10000 倍。 解决:统一单位,unit_price = total_price * 10000 / area,算完抽查几条和原始数据对一下。
5.3 区域名称不统一
现象:groupby 之后出现「鼓楼」「鼓楼区」「南京市鼓楼区」三个分组。 原因:数据来源不同,区域写法不一致。 解决:清洗时用映射表统一,df["district"] = df["district"].str.replace("区", ""),或者建一个{"鼓楼区": "鼓楼"}的字典做map。
5.4 异常值没处理,均值被拉偏
现象:均价明显高于市场认知。 原因:个别天价房源或面积填错的数据没剔除。 解决:用分位数卡边界,别用固定阈值;剔除后重新算均值,并记录剔除了多少条,PPT 里要能解释。
5.5 图表太多但没有结论
现象:PPT 里十几张图,讲的时候说不清重点。 原因:为画图而画图,没有围绕问题组织。 解决:每张图配一句话结论,比如「鼓楼均价最高,是江宁的 1.6 倍」,图是证据,结论才是内容。
6. 从数据到 PPT:让图表能讲出结论的三个技巧
做完整套分析,最后卡在 PPT 上的人不少。图表能画出来,不代表能讲清楚。我自己的习惯是:先定三个结论,再倒推需要哪几张图。
第一个技巧是给每张图配一句「人话结论」。比如区域均价图,标题不要写「各区域均价对比」,直接写「鼓楼均价领跑,江宁性价比最高」。观众看标题就懂,图只是佐证。第二个技巧是控制图表数量,一份二手房分析 PPT,四到六张图足够:区域均价、总价分布、面积-总价散点、区域户型热力图,再加一张结论页。图多了反而稀释重点。第三个技巧是散点图加趋势线,面积和总价的关系用散点图最直观:
import numpy as np fig, ax = plt.subplots(figsize=(10, 6)) ax.scatter(df["area"], df["total_price"], alpha=0.3, s=10, color="#C44E52") # 拟合一条趋势线 z = np.polyfit(df["area"], df["total_price"], 1) p = np.poly1d(z) x_line = np.linspace(df["area"].min(), df["area"].max(), 100) ax.plot(x_line, p(x_line), color="black", linewidth=2, label="趋势线") ax.set_title("面积与总价关系:面积每增 10㎡,总价约增多少") ax.set_xlabel("面积(㎡)") ax.set_ylabel("总价(万元)") ax.legend() plt.tight_layout() plt.savefig("area_price_scatter.png", dpi=150)alpha=0.3让点半透明,重叠区域能看出密度;np.polyfit一次拟合出斜率,斜率乘以 10 就是「面积每增 10㎡ 总价增加多少万」,这句话直接可以放进 PPT。注意散点图在数据量大时渲染慢,超过两万条可以先抽样再画。
最后说个我自己的教训:早期做这类分析,我总想把所有字段都塞进图里,结果 PPT 做了三十页,讲的时候自己都绕晕。后来改成「三个结论、六张图、每图一句话」,反而被问得最多的是结论怎么来的,而不是图怎么画的。数据可视化分析的价值不在图多漂亮,在于能不能让人看完就记住一个判断。希望帮到你。
本文还有配套的精品资源,点击获取