简介:这是一份西南财经大学学士学位毕业论文,主题为基于Python的南京二手房数据可视化分析,适合计算机或数据科学方向的本专科毕业生用于课程设计、毕业设计参考。资源包仅含1个docx文件,约29KB,即论文全文。已有498人学习,可参考完整论文结构与分析思路。论文按正式学位论文框架展开,涵盖研究背景、相关技术、数据采集与处理、可视化分析与结果讨论,涉及Python爬虫获取房源、Pandas数据清洗与统计、Matplotlib和Seaborn绘制散点图、箱线图、热力图等方法,并探讨房价分布、时间趋势与区域关联。读者既能了解二手房数据可视化项目流程,也能借鉴学术论文撰写规范,提升数据分析和写作能力。
1. 这个南京二手房可视化项目:论文级的完整案例,能抄能扩展
你没看错,这是一份本科毕业论文的完整项目——基于 Python 的南京二手房数据可视化分析设计与实现。但千万别因为“毕业论文”四个字就低估它,我拆完这份资源后最直观的感受是:数据采集、清洗、可视化、回归预测四个环节全都有实际代码,不是那种只放两张截图的“纸面论文”。对于正在做毕设、想练手 Python 数据分析、或者打算转行数据分析岗位的人来说,这份资源相当于一条完整项目链路:Requests 爬虫拉数据、Pandas 清洗、Matplotlib/Seaborn 出图,最后还带了一个回归模型做房价走势预测。你要做的不是从零搭框架,而是把里面的代码路径跑通、吃透、改成你自己的数据集。一句话——这是能直接上手的“论文级”项目,唯一的问题是很多人在复现时会卡在环境依赖和中文编码这两个老坑上,这篇笔记就是帮你排雷的。
2. 数据采集模块:Requests + BeautifulSoup 还是 Selenium,先把思路理顺
2.1 静态页面优先用 Requests,别一上来就上浏览器自动化
采集二手房数据,最常见的方式是拿 Requests 请求目标网站的列表页,再用 BeautifulSoup 解析 HTML。论文里提到的是“网络爬虫技术从二手房交易平台获取房源价格、面积、地理位置”,对应到实际案例里,链家、贝壳、安居客这类网站是首选。但这里面有一个关键选择:Requests 只能拿到服务端渲染的 HTML,如果目标页面是 JavaScript 动态加载的(现在很多平台列表页都是),直接请求返回的 HTML 里根本找不到房源数据。所以第一步是先右键查看网页源代码,确认数据是直接写在 HTML 里的,还是通过接口异步返回的。
import requests from bs4 import BeautifulSoup import csv import time headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://nj.lianjia.com/" } # 抓取列表页第一页,后续循环页码即可 url = "https://nj.lianjia.com/ershoufang/pg1/" resp = requests.get(url, headers=headers, timeout=10) resp.encoding = "utf-8" # 链家的页面是 UTF-8,有些老网站是 gbk,需按实际改 soup = BeautifulSoup(resp.text, "html.parser") # 每条房源信息在 li 标签里,class 包含清晰房源信息 house_items = soup.select("li.clear") print(f"当前页面抓到 {len(house_items)} 条房源,开始解析...")这段代码的关键点有两个。第一是resp.encoding = "utf-8",如果不手动指定编码,Requests 有时会按响应头里的 charset 去解码,而部分平台返回的页面实际是 GBK 编码,解码出来就是乱码,你会看到房源标题和价格全是“����”,这种时候把编码改成gbk或gb2312就能解决。第二是soup.select("li.clear")这个选择器,不同网站的列表项结构和 class 名完全不一样,贝壳可能是li[class*="house-lst"],58 同城又是另一套,所以拿到页面后先别急着写循环,先去浏览器开发者工具里确认自己要提取的信息挂在哪个标签下,这一步花十分钟能省两个小时。
2.2 动态加载页面:Selenium 兜底,但别滥用
遇到页面数据是 AJAX 加载的情况,Requests 拿不到完整 HTML,这时候才轮到 Selenium。很多做毕设的人在这里翻车——笔记本上装了 Anaconda 就直接pip install selenium,然后跑脚本时发现浏览器根本没弹出来,一看报错是WebDriverException: Message: 'chromedriver' executable needs to be in PATH。原因是 Selenium 只是个客户端库,它需要对应的 WebDriver(比如 chromedriver)才能驱动浏览器,而 WebDriver 需要手动下载且版本要和浏览器版本匹配。
from selenium import webdriver from selenium.webdriver.common.by import By import time options = webdriver.ChromeOptions() options.add_argument("--headless") # 无头模式,不弹浏览器窗口,服务器上也能跑 options.add_argument("--no-sandbox") options.add_argument("--disable-gpu") # chromedriver 路径按本机实际修改 driver = webdriver.Chrome(options=options) driver.get("https://nj.lianjia.com/ershoufang/pg1/") # 显式等待页面加载完成,比 time.sleep 更靠谱 from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "li.clear"))) # 此时页面已经渲染完成,可以提取数据 items = driver.find_elements(By.CSS_SELECTOR, "li.clear") for item in items: title = item.find_element(By.CSS_SELECTOR, ".title a").text price = item.find_element(By.CSS_SELECTOR, ".totalPrice").text print(title, price) driver.quit()Selenium 的核心优势是能拿到浏览器渲染后的完整 DOM,代价是速度慢、资源占用高。如果只是做毕设或小规模数据采集,无头模式 + 单线程跑几百页完全够了,但如果你要抓几万条数据,优先考虑找目标网站的开放 API,或者直接搜索“城市名+二手房+API”,很多第三方平台提供免费额度,能省掉大量时间和封 IP 的风险。抓取频率上,我一般会在两次请求之间加time.sleep(random.uniform(1, 3)),不要用固定间隔。
另外要注意一个实际操作中的问题:Requests 和 Selenium 抓下来的列表页通常只有 30 条/页,南京二手房在售房源大概几万套,如果想全量抓要循环几千次请求,很容易触发网站的反爬机制。论文里的做法是采集“大量数据”后做分析,所以建议抓前 10-20 页或按区域分开抓,比如鼓楼区、玄武区、建邺区各抓几十页,保证数据量在 2000 条以上,对可视化分析来说已经足够撑起图表。
3. Pandas 数据清洗与预处理:把脏数据变干净,这几步别跳
3.1 读入数据与字段类型修正:先看清楚 dtype 再动手
爬下来的数据存成 CSV 后,第一件事不是去重也不是画图,而是用df.info()和df.head()确认每一列的类型是否正确。二手房数据里最容易出现的问题是:价格字段带着“万”字、面积带着“平米”、户型字段混着“3室2厅1卫”这种组合字符串。这些字段如果不做清洗,后期用df["price"].mean()算平均值时直接报错——因为它们是 object 类型不是数值类型。
import pandas as pd import numpy as np df = pd.read_csv("nj_ershoufang.csv", encoding="utf-8") print(df.info()) print(df.head(3)) # 价格字段清洗:去掉"万"字并转成 float # 有的平台价格写的是"398万",有的是"单价 39800元/平",先统一口径 df["price"] = df["price"].str.replace("万", "").astype(float) # 面积字段:"89.5平米" -> 89.5 df["area"] = df["area"].str.replace("平米", "").str.strip().astype(float) # 户型字段提取室、厅数 df["bedrooms"] = df["house_type"].str.extract(r"(\d+)室").astype(float) df["living_rooms"] = df["house_type"].str.extract(r"(\d+)厅").astype(float) print(df.dtypes)这里用到的最关键方法就是str.extract,它配合正则表达式能把“3室2厅1卫”这种字符串里的数字精确抠出来。实际踩坑体验是:str.replace后面一定要加.astype(float),很多新手只做了字符串替换没做类型转换,后面画图时横坐标全是字符串,横轴标签挤在一起根本没法看——这也就是热搜里“python画图横坐标太密集”的典型来源之一。另外价格字段要注意单位统一,有的房源价格可能写的是“3.98万/平”(单价)而其他房源写的是“398万”(总价),两种口径混在一起做分析会得出完全错误的结论。处理办法是看字段名:totalPrice是总价,unitPrice是单价,做分析前先明确自己要用的是哪个维度。
3.2 去重、缺失值与异常值:按“三步法”执行
数据清洗的核心逻辑其实不复杂,就三步:去重、补缺失、剔异常。但每一步都有细节。去重时drop_duplicates()默认是对所有列完全相同的行去重,实际二手房数据里可能同一套房源被不同经纪人发了两次,标题、价格一样但链接不同,这时候要指定subset参数按关键列去重。
# 1. 去重:按标题+区域+价格组合去重,同一套房源重复发布只留一条 df = df.drop_duplicates(subset=["title", "region", "price", "area"], keep="first") print(f"去重后剩余 {len(df)} 条") # 2. 缺失值处理:面积缺失的用区域中位数填充 # 注意:房价这个字段缺失了宁可删除,也不要用均值填,否则预测模型会被污染 df["area"] = df["area"].fillna(df.groupby("region")["area"].transform("median")) df = df.dropna(subset=["price", "region"]) # 3. 异常值:按 IQR 法则剔除,3σ法则也行 Q1 = df["price"].quantile(0.25) Q3 = df["price"].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR df = df[(df["price"] >= lower_bound) & (df["price"] <= upper_bound)] print(f"异常值剔除后剩余 {len(df)} 条")逻辑说明:先用组内中位数填充缺失面积,是因为同一个区域的面积分布相对集中,用全区均值填充会把偏远区域的房子面积拉高;而价格缺失直接删行,因为后续的散点图和回归模型都依赖价格这个因变量,用填充值做出来的结论不可信。异常值剔除用的 IQR 方法对偏态分布更鲁棒,3σ法则假设数据服从正态分布,二手房价格明显是右偏的——高端房源的超高总价会被误判成异常值直接删掉。如果你发现剔除后剩下的数据量太少(比如少于 1500 条),可以把1.5 * IQR放宽到3 * IQR。这一步没有任何玄学,完全取决于你后续分析的容错空间。
3.3 经纬度与区域字段的归一:地图可视化的前提
如果你后续要做热力图或地图分布,光有“鼓楼区”“玄武区”这种文本是不够的。需要把区域字段映射成经纬度坐标,最常见的做法是用高德地图或百度地图的 Web API 做地理编码。
import requests import time def get_lat_lon(address, key="YOUR_AMAP_KEY"): url = "https://restapi.amap.com/v3/geocode/geo" params = {"address": f"南京市{address}", "key": key} resp = requests.get(url, params=params, timeout=5) data = resp.json() if data["geocodes"]: location = data["geocodes"][0]["location"] # 格式是 "经度,纬度" lon, lat = location.split(",") return float(lon), float(lat) return None, None # 拿到区域列表后循环请求,注意控制频率 regions = df["region"].unique() geocode_dict = {} for region in regions: lon, lat = get_lat_lon(region) geocode_dict[region] = {"lon": lon, "lat": lat} time.sleep(0.3) # 高德免费版 QPS 限制,太快会直接拒绝服务 df["lon"] = df["region"].map(lambda x: geocode_dict[x]["lon"]) df["lat"] = df["region"].map(lambda x: geocode_dict[x]["lat"])一定要在循环里加time.sleep,高德地图免费版对 QPS 的限制很严格,不是按天算总额的,是每秒最多并发几次,跑太快会在某一次循环里收到{"status":"0","info":"USER_DAILY_QUERY_OVER_LIMIT"},然后整个循环中断。另一个替代方案是用极简的离线库——在 1:400 万的 GeoJSON 地图上做区块着色,不需要精确到街道,只需把“区域名”和 GeoJSON 里的name字段对齐即可,这种方式不消耗 API 配额,适合做论文里的静态区域分布图。论文里强调的“地图进行空间分析,展示各地区的房价差异和热度分布”,用 GeoJSON + Python 的json模块就能实现。
4. 可视化分析实战:从直方图到热力图,图表输出全流程拆解
4.1 单一变量分析:价格直方图与箱线图的组合解读
单一变量分析是可视化分析的地基。论文里明确了要分析价格分布、面积分布、区域分布这三个维度。实际操练下来,最值得优先画的图是价格直方图和箱线图——直方图看整体分布形态,箱线图看离群点和分位数。两图配合,数据的大体情况就能吃透。
import matplotlib.pyplot as plt import seaborn as sns # 全局设置中文字体,这一步不设置就是方块字 plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = False fig, axes = plt.subplots(1, 2, figsize=(14, 5)) # 左图:总价直方图,bin 数按数据量调整 sns.histplot(df["price"], bins=50, kde=True, ax=axes[0], color="#2E86AB") axes[0].set_title("南京二手房总价分布直方图") axes[0].set_xlabel("总价(万元)") axes[0].set_ylabel("房源数量") # 右图:总价箱线图 sns.boxplot(x=df["price"], ax=axes[1], color="#A63A50", orient="h") axes[1].set_title("南京二手房总价箱线图") axes[1].set_xlabel("总价(万元)") plt.tight_layout() plt.savefig("price_distribution.png", dpi=150, bbox_inches="tight") plt.show()这里两条最容易被忽视的细节:第一,plt.rcParams["font.sans-serif"] = ["SimHei"]是画带中文标题的图的命门,不设置的话所有中文全部显示为方框,尤其是 Windows 上装了 Anaconda 跑 Matplotlib 的经典翻车现场;第二,bbox_inches="tight"会把图周围多余白边裁掉,不然保存出来的图片四周大片留白,放进论文里很不协调。直方图的bins=50不是固定的,如果数据量只有几百条,50 个柱子会稀稀拉拉很难看,建议数据量 1000 条以下用 20-30 个 bins,5000 条以上用 80-100 个 bins。KDE 曲线叠加在直方图上是看分布形态的利器——南京二手房总价大概率是右偏的(低价房源多、高价房源少),KDE 曲线会非常直观地展示这个“长尾”特征。
4.2 多变量分析:散点图看面积-价格关系,热力图看特征相关性
单一变量分析只能看到分布形态,多变量分析才真正回答“购房者最关心的——面积和价格什么关系、什么因素最影响房价”。这里核心手段就是sns.scatterplot()画散点图,同时用颜色区分区域,再叠加sns.heatmap()看数值型特征之间的相关系数矩阵。
import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 构造相关性矩阵:选取数值型特征 numeric_cols = ["price", "area", "bedrooms", "living_rooms", "floor_level"] corr_matrix = df[numeric_cols].corr() fig, axes = plt.subplots(1, 2, figsize=(15, 5.5)) # 左图:面积 vs 总价散点图(按区域着色) sns.scatterplot(data=df, x="area", y="price", hue="region", alpha=0.6, s=20, ax=axes[0], palette="Set2") axes[0].set_title("面积与总价关系散点图(按区域着色)") axes[0].set_xlabel("面积(平方米)") axes[0].set_ylabel("总价(万元)") # 右图:特征相关性热力图,annot 显示数值 sns.heatmap(corr_matrix, annot=True, cmap="coolwarm", fmt=".2f", linewidths=0.5, ax=axes[1]) axes[1].set_title("二手房特征相关性热力图") plt.tight_layout() plt.savefig("multi_variable_analysis.png", dpi=150, bbox_inches="tight") plt.show() # 输出面积和价格的实际相关系数 print(f"面积-总价 Pearson 相关系数: {df['area'].corr(df['price']):.3f}")这段代码跑出来的散点图,正常会看到左侧集中分布在一条约 45 度的斜带里,这说明面积和价格存在明显的正相关关系。但如果你发现散点图呈现“上密下疏”的喇叭形分布——面积小的房子价格低且集中,面积大的房子价格分散——那是正常的,说明单价(每平米价格)其实是随面积递减的,这就是所谓的“大面积单价低”现象。热力图里,annot=True会在每个格子上显示相关系数数值,fmt=".2f"控制显示两位小数,cmap="coolwarm"让正相关显示为红色、负相关显示为蓝色。从这段代码得到的corr()数值,可以直接写进论文的结果讨论章节。
4.3 区域对比:柱状图 + 地图叠加展示房价梯度
论文里专门提到了“结合地图进行空间分析,展示各地区的房价差异和热度分布”。实际操作中,pyecharts 是最省事的方案——它封装了百度 ECharts 的能力,而且地图和柱状图的绘制逻辑一致,不用像原生 ECharts 那样写一堆 JavaScript。先按区域分组算出均价,再生成地图。
import pandas as pd from pyecharts.charts import Bar, Map from pyecharts import options as opts # 按区域计算均价和房源套数 region_stats = df.groupby("region").agg( avg_price=("price", "mean"), avg_area=("area", "mean"), count=("price", "size") ).reset_index().round(2) # 柱状图:各区域均价 bar = ( Bar(init_opts=opts.InitOpts(width="900px", height="500px")) .add_xaxis(region_stats["region"].tolist()) .add_yaxis("平均总价(万元)", region_stats["avg_price"].tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="南京各区域二手房均价对比"), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=45)), ) ) bar.render("region_price_bar.html") # 区域地图:需要南京市的 GeoJSON,网上直接下现成的即可 map_chart = ( Map(init_opts=opts.InitOpts(width="900px", height="600px")) .add("均价", [list(z) for z in zip(region_stats["region"], region_stats["avg_price"])], maptype="南京") .set_global_opts( title_opts=opts.TitleOpts(title="南京二手房区域房价分布地图"), visualmap_opts=opts.VisualMapOpts(min_=100, max_=500, is_piecewise=False) ) ) map_chart.render("region_price_map.html")这里最关键的坑是maptype="南京",pyecharts 默认只带中国地图的 GeoJSON,是区分省份的,没有细分到市级地图。直接写“南京”会报Cannot find map: 南京。解决办法有两个:一是去 pyecharts 的扩展包pyecharts.datasets官方仓库下载南京市的 GeoJSON 文件放到项目里;二是直接把 map 的注册名改成"江苏",展示江苏省地图然后把南京区域重点高亮。大多人第一次跑都会卡在这里,算是 pyecharts 最常见的翻车点之一。切换到bar.render()之后,如果没报错但浏览器打开是白屏,基本是文件路径问题——render 默认输出到当前工作目录下的 HTML 文件,用绝对路径或用os.chdir()切到输出目录可以规避。
4.4 交互式可视化:Plotly 做动态图表才是论文加分项
论文的研究目的里专门提到了“实现南京二手房数据的动态交互可视化,借助 Matplotlib、Seaborn 和 Plotly 等工具”。Matplotlib 出静态图,Plotly 出交互图,这一节可以说是区分“及格论文”和“优秀论文”的分水岭。Plotly 的好处是鼠标悬停就能显示具体数值,适合给答辩老师现场演示。
import plotly.express as px # 用 Plotly Express 一行出交互散点图 fig = px.scatter( df, x="area", y="price", color="region", size="area", hover_data=["house_type", "region"], title="南京二手房面积-价格交互散点图", labels={"area": "面积(平米)", "price": "总价(万元)"} ) # 保存成独立 HTML 文件,离线也能打开 fig.write_html("interactive_scatter.html") print("交互图已保存为 interactive_scatter.html,浏览器打开即可拖拽、缩放、悬停")px.scatter的优势是不需要像 Matplotlib 那样做一堆 axes 设置,一个color参数直接按区域分色,一个hover_data参数就能在悬停时显示户型信息。生成的文件是独立的 HTML,自带 JavaScript 渲染,不需要 Python 环境也能在答辩现场的电脑上打开演示。如果你想更进一步,把图表嵌入 Web 页面展示,就需要 Flask + Plotly 配合:Flask 提供一个路由,把fig.to_html()的字符串嵌入模板,用户就能在网页里拖拽查看所有图表。这种“论文+可运行网页”的组合对毕业答辩的加分效果极其明显。
5. 避坑与常见问题排查:环境、编码、数据三个战场
5.1 Matplotlib 中文全部显示为方框,标题和坐标轴全是乱码
现象:plt.title("南京二手房价格分布")运行不报错,但保存出来的图片中文全是实心方块,英文正常。
原因:Matplotlib 默认的字体族 DejaVu Sans 不包含中文字形。Windows 和 macOS 各自的中文字体名不一样,Windows 是 SimHei 或 Microsoft YaHei,macOS 是 PingFang SC、STHeiti 或 Arial Unicode MS。系统里没有对应字体就会失败。
解决:第一优先,代码里写plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"],注意是中括号列表形式,可以一次给多个备选字体。第二优先,直接在代码开头执行plt.rcParams["axes.unicode_minus"] = False,否则负号也会变成一个奇怪的方框,价格坐标轴有负号时就会触发。如果这两步做完还是方块,检查系统是否安装了中文字体——Windows 自带的没问题,但精简版 Linux 服务器经常没有,执行fc-list :lang=zh查看可用中文字体,没有就安装fonts-wqy-microhei。
5.2 Pandas 读 CSV 报错UnicodeDecodeError: 'utf-8' codec can't decode byte
现象:pd.read_csv("nj_ershoufang.csv")直接报编码错误,或者读出后发现价格字段变成一堆乱码字符(比如“398涓囷拷”)。
原因:爬虫写 CSV 时用了encoding="utf-8"但 Excel 另存过一次变成了 GBK;或者爬虫本身以gbk写出去了,读的时候按utf-8解析。Windows 区域设置下中文软件默认是 GBK 编码,跨平台文件共享最容易遇到这个坑。
解决:读文件时不要默认 utf-8,先尝试用pd.read_csv(file, encoding="gbk"),如果报错再换encoding="gb18030"(GBK 的超集,兼容性更好)。更稳妥的做法是用with open(file, "rb") as f: raw = f.read()先读二进制,然后用raw.decode("utf-8", errors="ignore")或尝试解码,我一般是写一个小判断函数自动探测。另外强烈建议所有 CSV 文件统一存成 utf-8-sig 格式,to_csv("xxx.csv", encoding="utf-8-sig"),这个格式带 BOM 头,Excel 打开不会乱码,Pandas 读入也不会有问题,是跨平台工具链里最省心的方案。
5.3 爬虫在第 30 页之后开始出现空数据或验证码页面
现象:循环抓取列表页时,前 20 页一切正常,从某一页开始返回的列表项数量突然变成 0,或者页面内容变成了滑块验证。
原因:目标网站的 IP 访问频率限制。即便每次请求后sleep(1),连续几十分钟内同一个 IP 的高频访问仍会触发反爬虫机制。平台不是按 QPS 单看,而是按时间窗口(比如 1 小时 60 次)统计。
解决:三个方案按优先级排。第一,放慢速度,把sleep间隔调到 3-5 秒随机值,甚至单线程循环里每 50 页停 60 秒,亲测可以有效延长被封时间。第二,用代理 IP 轮换,但免费的代理池质量参差不齐,做毕设没必要在这上面烧钱,除非你被抓了被封了再考虑。第三,直接缩减采集规模——毕设论文不要求百万级数据量,抓 1500-3000 条足够撑起图表和回归模型,少抓一点反而更安全。另一个辅助手段是启用了 Selenium 的话加上options.add_argument("--disable-blink-features=AutomationControlled")隐藏自动化特征,能降低被检测到 WebDriver 的概率。
5.4 回归模型 R² 只有 0.2,预测出来的房价走势完全偏了
现象:按论文的方法做完线性回归,在测试集上评估发现 R² 得分只有 0.2 左右,预测的房价和实际值差距很大,可视化出来的预测曲线几乎是一条平线。
原因:特征工程没做好。只拿面积和卧室数量去拟合价格,忽略了区位、楼层、装修程度、房屋朝向、建筑年代等强特征变量。二手房价格里最大权重其实是位置——同样面积的房子,鼓楼区和六合区的总价可以差 3 倍以上,但如果模型里没有“区域”这个特征,或用字符串表示区域没做 one-hot 编码,模型根本学不到区域信息。
解决:至少做到两步。第一,把region做 one-hot 编码(pd.get_dummies(df["region"], prefix="region")),或用目标编码,把每个区域的均价作为一个新特征列。第二,增加特征列:楼层高低、梯户比、建筑年份、装修情况,这些字段在爬虫阶段就要想办法采下来,否则清洗阶段没数据可用就是巧妇难为无米之炊。做完这两步再去跑回归,R² 通常能从 0.2 提升到 0.6 以上。
# 目标编码示例:用区域均价作为新特征,比 one-hot 更省维度 region_price_mean = df.groupby("region")["price"].transform("mean") df["region_price_level"] = region_price_mean # one-hot 编码区域字段 region_dummies = pd.get_dummies(df["region"], prefix="region") df = pd.concat([df, region_dummies], axis=1)5.5 生成的 HTML 地图文件打开是白屏或报错Cannot find map
现象:pyecharts 渲染Map类型的图表后,浏览器打开 HTML 文件,页面空白,控制台报错Error: Cannot find map: 南京或者map is not defined。
原因:pyecharts 的Map图表依赖注册到 ECharts 里的地图数据,默认的pyecharts.datasets只内置了中国省级地图的 GeoJSON,市级地图需要额外从 echart 的 map 扩展包下载注册。直接写maptype="南京"时找不到对应地图文件。
解决:优先改用省份作为筛选维度,用maptype="江苏"注册,再用视觉映射突出南京的区域。或者去 pyecharts 官方示例仓库下载南京市 GeoJSON,使用echarts.register_map("南京", geojson_data)手动注册后使用。具体操作是把下载的nanjing.json放到项目目录下,用 Python 读入 JSON 后调用注册接口。多数情况下用maptype="江苏"就够用了——论文只要求展示“各地区的房价差异和热度分布”,江苏省级地图加南京高亮完全可以满足这个需求。
6. 进阶技巧:给静态图加交互,给预测模型加可信区间
先说一个能直接让你的毕设或作品集比别人高一个维度的技巧:把 Matplotlib 画出来的静态图全部替换成 Plotly 的交互版本。静态图适合论文打印,但答辩演示或作品集展示时,交互图的体验完全不在一个层次——鼠标悬停看到具体房源信息、滚动缩放查看局部细节、点击图例隐藏某个区域的数据,这些交互操作对“可视化分析”四个字的诠释力远胜于静态图片。做完基础图表后,我会统一用一个自动化脚本把所有静态图批量转成交互图,这个操作可以在答辩前一夜完成,改动量很小但呈现效果提升巨大。
第二个技巧是给回归预测模型加“可信区间”。论文里提到要建立回归模型预测未来二手房价格走势,但绝大多数人会直接甩出一个model.predict()的结果,然后画一条干巴巴的预测线。更专业的做法是用statsmodels库里的 OLS 回归输出带置信区间的预测结果,或者用scikit-learn的GradientBoostingRegressor输出特征重要性排序——哪个特征对房价影响最大、哪个几乎没影响,这种分析才是答辩时能经得住老师追问的深度内容。
from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split # 准备特征矩阵 features = ["area", "bedrooms", "living_rooms", "region_price_level"] X = df[features].copy() y = df["price"].copy() X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) # 随机森林回归,n_estimators 不用太大,300 颗树足够 model = RandomForestRegressor(n_estimators=300, max_depth=10, random_state=42) model.fit(X_train, y_train) # 特征重要性:排序后绘图,直观回答“什么最影响房价” importance = pd.Series(model.feature_importances_, index=features).sort_values(ascending=False) print(importance) # 测试集评估 from sklearn.metrics import r2_score, mean_absolute_error y_pred = model.predict(X_test) print(f"R²: {r2_score(y_test, y_pred):.3f}") print(f"MAE: {mean_absolute_error(y_test, y_pred):.2f} 万元")在论文的讨论章节里,你可以用这组输出来支撑结论,比如“从特征重要性排序来看,区域价格水平和面积是影响总价的两个核心变量,卧室数量的影响比预想中要弱”。我过去带毕设时见过太多人栽在同一个地方:数据量只有几百条,却硬要上一个复杂的深度学习模型,预测结果一塌糊涂不说,答辩时被老师问到“为什么用这个模型”也答不上来。用随机森林或线性回归这种可解释性强的模型,配合特征重要性输出,才是职场上和答辩场上都认的做法。
另一个容易出错的地方是时间序列预测。论文里写的是“基于历史数据预测未来房价走势”,如果你手头的数据只有横截面数据(当前在售房源),没有过去几年的历史价格记录,那你实际能做的并不是时间序列预测,而是一个“当前价格影响因素回归模型”。这两个概念在论文里不能混在一起写,否则评审老师一眼就能看出数据配不上结论。我一般会这样处理:在论文里明确区分“基于当前特征的价格预测”和“基于时间序列的价格走势预测”,自己手头有什么数据就做什么分析,不硬凑。
最后一个我自己的习惯:每次拿到一份类似这样的项目资源,我会先花二十分钟跑一遍它的数据和代码,确认能出图、能出预测结果,再开始改数据源做自己的项目。因为毕业论文级别的资源最大的价值在于完整链路,不在于算法多先进,一旦链路打通了,后面的修改只是在换数据和调参数。从那以后我每次接数据分析项目都强制走一遍“先复现、再改造、后扩展”的流程,不贪快。希望这个南京二手房可视化项目的拆解思路帮到你,把你的数据喂进去,跑出第一张图,后面的事情自然就顺了。
本文还有配套的精品资源,点击获取