news 2026/10/13 4:22:05

短租开放数据全流程分析:数据清洗到房价预测建模

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
短租开放数据全流程分析:数据清洗到房价预测建模

做数据分析最怕什么?不是不会调库,而是拿到一堆真实数据后不知道从哪下手。我建议第一个完整项目就从某短租平台的开放数据开始:这是某国际都市的房源数据,字段覆盖价格、房型、位置、评论时间、可订天数,既有连续数值又有分类文本,还有缺失值和异常值。跑完“数据加载→清洗→探索性分析→建模→可视化→结论”这套全流程,你对数据分析的理解会比刷十遍教程都深。这篇文章就是我做完该项目后整理的完整记录,适合刚入门数据分析的读者,也适合想把自己作品集做成全流程项目的同学参考。

1. 项目整体设计与思路拆解

1.1 为什么要拿“某国际都市短租开放数据”当练手项目

我见过很多初学者,学完 pandas 和 matplotlib 之后依然不会做项目,主要原因就是练习数据太“干净”了。教程里的数据往往已经处理得整整齐齐,而真实开放数据带给你的是最原始的状态:价格字段带着货币符号和逗号,日期列有缺失,经纬度偶尔跑偏,甚至同一个区域的写法都有好几种。这份短租数据完美满足了“真实数据”这个条件。

选择这份数据还有几个现实理由:

  • 免费公开且规模适中,大约有几万条房源记录,单机内存完全能跑,不需要上分布式。
  • 字段类型丰富,包括数值、类别、日期、文本、地理坐标,覆盖主流分析场景。
  • 业务故事好讲,价格、房型、区域、评论量可以直接转化为“什么因素影响房价”这类人人都关心的问题。
  • 数据有明显缺陷,缺失值、异常值、重复值都真实存在,这对练习数据清洗非常有价值。

动手之前我给这个项目定了一个明确目标:不只做描述性统计,还要完成一个价格预测模型,并且把结论讲给非技术同事听。目标清楚了,后面每一步才不会跑偏。

1.2 全流程分几步走,目标怎么定

整个项目我划分成六个阶段,每个阶段有输入、输出和落地的检查点,下面是我实际执行时用的表:

阶段核心动作关键输出检查点
数据获取下载开放CSV文件,确认字段含义原始数据表字段个数、行数是否合理
数据清洗处理缺失值、异常值、类型转换干净的DataFrame缺失率为0或已标记
特征工程构造距离、价格区间、活跃度等特征扩展特征后的数据表新特征是否有业务含义
探索性分析分布、对比、相关性分析图表 + 初步结论能否支撑后续建模方向
建模预测训练线性回归、随机森林模型与评估指标RMSE、R²是否可比
结果呈现可视化 + 一页纸结论可交付报告非技术听众能否看懂

在这个框架里,建模不是终点,能解释结论才是。所以我在探索性分析阶段就会记录一些关键问题,比如:是不是核心区房源更贵、整套房是不是比单间有明显溢价、评论多的房源价格会不会更高。这些问题之后都会成为建模时的验证项。

1.3 分析口径先定好,后面少返工

真实项目里最容易返工的地方就是口径不清。这个项目里我把口径提前定了三件事:

  • 价格口径:只看每晚价格,不处理清洁费和服务费,避免口径混乱。
  • 区域粒度:用“区域组”这一层做对比分析,不细分到社区,因为社区级别太碎,样本量不均。
  • 房源类型:按数据集里原有的三类房型处理,不额外合并。

这些口径看起来是小事,但如果你做了一半才发现“低价房源其实是共享房间”,回头重跑所有分析的代价会非常大。先花十分钟把口径固化下来,是给后面的自己省时间。

2. 数据获取与环境准备:先把地基打牢

2.1 数据从哪来,字段长什么样

这份开放数据通常以 CSV 格式提供,文件名为listings.csv,下载解压后直接读入即可。核心字段虽然在不同版本里略有差异,但以下几个是必有的:

字段名中文含义类型使用场景
price每晚价格字符串,需清洗分析目标
room_type房源类型类别分组对比
neighbourhood_group所属区域组类别区域分析
latitude / longitude经纬度浮点数地理可视化
minimum_nights最少入住晚数整数异常值检测
number_of_reviews评论总数整数活跃度指标
reviews_per_month月均评论数浮点数活跃度指标
last_review最近评论日期字符串/日期时间分析
availability_365全年可订天数整数供应分析
calculated_host_listings_count房东拥有房源数整数房东规模

这些字段足够支撑一套完整分析。我会重点利用price、room_type、neighbourhood_group做业务分组,再用latitude、longitude做空间分布展示。

2.2 环境安装:一个命令装齐所需库

我的建议是直接用 Anaconda 创建一个隔离环境。这里演示我用的命令:

conda create -n data_analysis python=3.10 -y conda activate data_analysis pip install pandas numpy matplotlib seaborn scikit-learn openpyxl

注意版本问题:scikit-learn新版本里mean_squared_error增加了squared=False参数可以直接返回 RMSE,旧版本没有。如果你用的还是 0.x 版本的老环境,建议升级到 1.x 或直接用np.sqrt(mean_squared_error(...))手动开方。

2.3 第一眼体检:加载数据后的5分钟内做什么

拿到 CSV 之后,不要急着清洗,先做三件事:看维度、看字段类型、看数值分布。

import pandas as pd df = pd.read_csv("listings.csv") print("数据规模:", df.shape) df.head() print(df.dtypes) print(df.describe())

这一步的产出不是多么漂亮的图表,而是对数据质量形成一个整体判断。我当时跑完describe()后立刻发现两个问题:price列的 min 居然是 0,且有些价格高到离谱;reviews_per_month存在大量缺失。这些问题会直接影响后续分析和建模,所以记录到自己的问题清单里,接下来逐一处理。

3. 数据清洗与特征工程:把脏数据变成能用的表

3.1 缺失值处理:先看业务含义再决定填还是删

缺失值处理不能机械套用“均值填充”或“直接删除”。处理之前,先弄清楚缺失为什么发生、缺失代表什么业务含义。当时列了缺失检查代码:

missing = df.isna().sum() missing = missing[missing > 0] missing_percent = (missing / len(df) * 100).round(2) print(pd.DataFrame({"缺失数量": missing, "缺失占比%": missing_percent}))

这份数据里比较容易出现缺失的字段集中在评论相关列:last_review和reviews_per_month缺失的房源,通常是上线后从来没有收到过评论。这类房源要么是太新,要么入住率低、没人评价。缺失本身就是一个信号:这个房源没有获得过用户反馈。

我的处理方法是:把reviews_per_month缺失值填 0,表示“当前没有评论活跃度”;把last_review缺失单独创建一个布尔特征has_review,避免用一个假日期去填充干扰时间分析。这样既保留了缺失背后的业务信息,又不影响后续建模。

注意:last_review不要随便填一个很早的日期,否则后面做“距最近评论天数”时会造出大量异常值。

3.2 异常值与类型修正:价格里的“$”和超低价房源

这个数据集的price列是字符串格式,比如$150或者$1,200,直接astype(float)会报错。洗价格是第一步,也是后面所有分析的前提:

df["price"] = df["price"].replace({r"\$": "", ",": ""}, regex=True).astype(float)

清洗之后立刻检查异常值。我当时用两种方式并行判断:先看分布,再看业务合理性。价格最小值出现了 0,这明显是异常值;价格上限用 99% 分位作为截断参考,比写死一个“1000美元”更合理,因为不同城市价格带差异很大,固定阈值很容易误杀正常房源。

valid_price = df["price"].between(10, df["price"].quantile(0.99)) df = df[valid_price]

同时,我也检查了minimum_nights,把超过 365 天的极端值视为登记错误或非正常房源,直接剔除。坐标异常值则通过过滤经纬度明显越界的记录处理。

3.3 特征工程:新增4个对价格分析有用的特征

做完清洗,我只做少量特征,保证每一步都能讲清楚业务含义。新增了四个特征:

  • distance_to_center:房源到全市房源几何中心点的距离,用来量化位置对价格的影响。距离计算用球面距离公式,避免直接用经纬度平面距离在大城市尺度下失真。
  • has_review:是否有评论,0/1 布尔特征。
  • days_since_last_review:最近评论距离数据发布日期的天数,衡量房源近期活跃度。
  • host_listing_count:直接复用原有字段,但把大于 10 的截断为 10,防止极端值影响模型。

距离计算的核心函数如下:

import numpy as np def haversine_np(lat1, lon1, lat2, lon2): R = 6371.0 dlat = np.radians(lat2 - lat1) dlon = np.radians(lon2 - lon1) a = np.sin(dlat / 2) ** 2 + np.cos(np.radians(lat1)) * np.cos(np.radians(lat2)) * np.sin(dlon / 2) ** 2 return R * 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a)) df["distance_to_center"] = haversine_np( df["latitude"], df["longitude"], df["latitude"].median(), df["longitude"].median() )

选取中位数坐标作为中心参照,避免被个别极端坐标带偏。

4. 探索性数据分析(EDA):先看图再建模

4.1 价格分布:右偏数据别硬建模

价格分布是这个项目里最重要的第一张图。直接画直方图会发现一个典型特征:长尾向右拖得很远,少量高端房源把均值拉得很高,中位数却远低于均值。这时如果用原始价格做线性回归,模型会被长尾样本带偏。我把价格做了自然对数变换,让分布更接近正态,后续模型的误差评估也更平稳。

df["log_price"] = np.log1p(df["price"])

处理之后你会看到分布明显对称许多。这个变换不是硬凑数学,而是给模型一个更合理的优化空间。后面预测结果要还原成原始价格时,做一次np.expm1(pred)就能转回来。

4.2 区域与房型:哪个区最“金贵”

按区域组和房型做分组统计,能立刻看出市场结构。我用的分析思路是:先看每个区域的房源数量占比,再看该区域的均价中位数。

group_stats = df.groupby(["neighbourhood_group", "room_type"]).agg( 房源数=("price", "count"), 价格中位数=("price", "median"), 价格均值=("price", "mean") ).round(2)

结果呈现的很明显:核心区房源数量最多,价格中位数也最高;外围区域房源数量少,价格中位数明显下降。房型上,整套房源的中位数价格最高,共享房间最低,这和直觉一致。不过要再往深看一层:核心区里整套房源和独立房间的价差,明显大于外围区域的同类价差。这种“位置溢价”在后续建楼时会体现为区域特征和距离特征的显著贡献。

4.3 时间相关特征:从评论看供需活跃度

last_review、reviews_per_month、number_of_reviews是理解房源活跃度的三件套。我画了按月份统计的“最近评论数量”折线图,发现整体评论活跃度有明显的季节波动:旅游旺季评论量集中,淡季明显下滑。这说明短租市场供需受季节影响大,价格模型里加入“月份”或“是否旺季”特征会更贴近真实规律。

这部分分析不复杂,但能让你的结论更有业务洞察。很多人只盯着价格分布和区域箱线图,忽略了时间维度,结果模型少了季节性这个解释变量。

4.4 相关性矩阵:哪些特征和价格最相关

建模型之前,先算一组相关性,避免把高度共线的特征直接塞进模型。我挑选了价格、坐标、评论数、可订天数、房源数、距离这些数值特征做相关性矩阵,并用 seaborn 热力图展示。

import seaborn as sns import matplotlib.pyplot as plt corr = df[numeric_cols].corr() sns.heatmap(corr, annot=True, cmap="coolwarm", fmt=".2f") plt.show()

结论通常很稳定:距离与价格显著负相关,越靠近中心越贵;房源评分等字段若存在也与价格正相关;可订天数与价格的相关性较弱。这一步给建模提供了特征筛选方向。

5. 建模预测价格:从线性回归到随机森林

5.1 建模前处理:怎么编码、选哪些特征

建模前把数据集划分成特征和标签。标签选log_price而不是原始价格,理由已经讲过。特征方面我保留五类:

  • 数值特征:latitude、longitude、distance_to_center、minimum_nights、availability_365、number_of_reviews、reviews_per_month、days_since_last_review、host_listing_count
  • 类别特征:room_type、neighbourhood_group

类别特征用 One-Hot 编码。实际操作时我直接用pd.get_dummies(df, columns=[...]),并在训练测试划分后调用,避免数据泄露。

5.2 基线模型:线性回归和两个核心指标

建模先跑一个线性回归作为基线,目的是给后续复杂模型一个对比基准。划分训练集和测试集时,随机种子固定为 42,保证结果可复现:

from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) lr = LinearRegression() lr.fit(X_train, y_train) y_pred = lr.predict(X_test) rmse = mean_squared_error(y_test, y_pred, squared=False) r2 = r2_score(y_test, y_pred)

指标怎么解读?RMSE代表预测值和实际值的平均偏差,单位与标签一致。因为标签是log_price,RMSE 越小说明在对数尺度下预测越准确。R²是模型解释的方差比例,0.4 左右在真实房价数据里已经算可用,别期待像教程数据那样动不动 0.9。我当时线性回归的 R² 只有 0.4 出头,RMSE 在 0.65 左右,说明还有提升空间。

注意:把预测结果的 RMSE 读回业务层面时,先做np.expm1还原价格,再计算百分比误差,这样更容易跟业务方解释。

5.3 进阶模型:随机森林的参数设置

线性回归无法捕捉房型和位置之间的非线性关系,所以我第二个模型选了随机森林。这个模型对缺失值不敏感、不需要特征标准化,而且能输出特征重要性,解释性也不错。

参数设置上我用的是一组经过尝试后的稳健组合:

from sklearn.ensemble import RandomForestRegressor rf = RandomForestRegressor( n_estimators=500, max_depth=15, min_samples_split=5, min_samples_leaf=2, random_state=42, n_jobs=-1, oob_score=True ) rf.fit(X_train, y_train)

参数为什么这样设:n_estimators=500保证预测稳定,继续增大到 1000 的边际收益很小;max_depth=15限制树深,防止训练集样本量不大时过拟合;min_samples_split=5和min_samples_leaf=2相当于正则化,让每棵树不那么“钻牛角尖”。oob_score=True可以免费看到一个袋外评分,作为快速验证。

最终随机森林的 R² 提升到 0.55 左右,RMSE 降到 0.55 附近。这个提升说明确实存在非线性关系。特征数量不多,训练耗时可接受。

5.4 结果解读:真正影响价格的因素

随机森林有一个特性,就是训练完成后可以直接看feature_importances_。我当时按重要性排序后发现,排名靠前的特征基本稳定为:distance_to_center、room_type、latitude、longitude、minimum_nights、number_of_reviews。

这个结果的意义不在于“模型能用”,而在于它能回答业务问题:位置仍然是短租定价最核心的因素,距离核心圈越近,价格越高;房型的影响紧随其后,整套房源的溢价明显;评论数反映房源口碑和人气,也会正向影响价格。

线性回归的系数还能告诉你方向,随机森林只能给重要程度,所以我更推荐线性回归和树模型一起跑:一个用来说明方向和强度,一个用来做预测精度。两种视角互补。

6. 可视化与交付:让分析结果能被听懂

6.1 三张必画图:地图、箱线图、特征重要性

交付给非技术听众时,图的数量不是越多越好,而是每张图都要回答一个问题。我最终选了这三张:

第一张是价格空间分布图。用经纬度做散点图,颜色映射到价格,立刻可以看到核心区颜色偏亮、外围偏暗,比任何文字都直观:

plt.scatter(df["longitude"], df["latitude"], c=df["price"], cmap="plasma", s=4, alpha=0.5) plt.colorbar(label="price") plt.show()

第二张是区域与房型的箱线图,用来展示“不同区域不同房型”的价格分布,既能看中位数,也能看波动范围。

第三张是特征重要性条形图,直接展示模型认为哪些变量最重要。

importance = pd.Series(rf.feature_importances_, index=X_train.columns).sort_values(ascending=True) importance.plot.barh(figsize=(8, 8)) plt.show()

6.2 输出结构化结论:一行结论配一张图

我在项目收尾时按照“结论 → 证据 → 建议”的结构整理了一页纸报告。比如:

  • 结论:位置是短租定价的最强影响因素。
  • 证据:价格散点图呈现明显的中心向外围递减;距离变量在模型重要性中排第一。
  • 建议:如果想提高收益,核心区小户型比外围大户型更有竞争力。

这个方法很适合工作场景。别把 20 页图表全丢给老板,一个核心结论配一张核心图,是最有效的表达方式。

6.3 如何把项目代码改造成可复用模板

做完一版分析后,我把清洗、特征工程、建模、画图分别封装成函数,文件名按流程命名。这样下次拿到另一个城市的开放数据,只需要替换数据源路径,微调字段映射就能复用同一套流程。

我建议你也这么做:把load_data()、clean_data()、add_features()、train_model()独立出来,固定输入输出格式。第一次写可能多花半小时,但后续迭代和换数据集时省下的时间远不止半小时。

7. 常见问题与排查技巧实录

7.1 数据加载失败与乱码怎么处理

我在加载 CSV 时遇到过两种典型问题:一是文件路径含中文或空格导致读取失败,二是文件编码不是默认的 UTF-8。读入时最好显式指定编码:

df = pd.read_csv("listings.csv", encoding="utf-8")

如果报错,换成encoding="latin-1"或encoding="gbk"试试。另外,尽量避免在路径中使用中文目录,这是最简单的避坑方式。

7.2 缺失值比例超过一半的列怎么处理

有些字段缺失比例可能超过 60%,比如评论相关列。这时候要区分两种情形:如果缺失集中在某个区域或某个房型,那可能是数据登记问题;如果是随机缺失,需要判断该列的业务价值。

我一般原则是:重要特征缺失率超过 60%,先不要直接删列,而是构造“是否缺失”特征再做模型对比。这样能判断缺失本身是否包含信息。随意删列会丢失这部分信号。

7.3 模型和测试集分不好导致过拟合怎么办

我当时踩过的坑是:先对整个数据集做了 One-Hot 编码,再切分训练测试集,结果特征里包含了测试集的信息,属于“数据泄露”。虽然随机森林的 R² 高达 0.7,但换到新数据立刻掉下来。

正确做法是先切分,再在训练集上做编码和特征计算。更严谨一点,连续特征如果要做标准化,也应该先用训练集的均值方差去转换测试集,而不是各自独立处理。

7.4 关于这个项目的几点个人实操心得

这个项目我前后完整跑了三轮。第一轮只想快点出结果,忽略了不少业务细节;第二轮补了距离特征和日期处理,模型解释性变好很多;第三轮才开始整理函数和报告,也算找到了比较舒适的节奏。

最后再分享一个小技巧:做这类全流程项目时,从一开始就给列名用英文、给代码文件按数字编号,你会发现在最后写报告、复现结果时会顺畅很多。真实项目里没人看你写了多花哨的代码,只看你能不能稳定复现结果、把结论讲清楚。数据分析项目(1)到这里收尾,下一期我会把同样的流程换一份数据再做一遍,对比两次分析里哪些步骤可以完全复用,哪些环节必须重新思考。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 4:21:40

Scrum框架底层逻辑拆解:从经验主义到角色制衡的敏捷实践

很多团队在引入Scrum框架的时候,都没有认真问过一句:这套框架的底层逻辑到底是什么?日程表上排满了Sprint Planning、Daily Scrum、Review、Retrospective,看板上贴满便签,用户故事也按要求写了“作为XX,我…

作者头像 李华
网站建设 2026/10/13 4:21:31

Agent Skills实战:从通用大模型到垂直领域技能系统

1. 从“什么都能聊”到“什么都能干”:Agent Skills 到底在解决什么问题通用大模型刚出来那阵子,大家的兴奋点都集中在“它居然能跟我对话”这件事上。你问它天气、让它写首诗、帮你想个公司名字,它都能接得住。但真把它丢进一个具体业务场景…

作者头像 李华
网站建设 2026/10/13 4:21:08

莫烦DQN代码精讲:287行读懂强化学习核心原理

1. 项目概述:为什么“莫烦DQN代码”至今仍是强化学习入门绕不开的锚点如果你刚接触强化学习,大概率会在某个深夜的搜索框里敲下“DQN 入门”“Q网络怎么写”“PyTorch DQN 示例”,然后被一条标题为《莫烦Python:DQN》的教程反复推…

作者头像 李华
网站建设 2026/10/13 4:21:04

轻量级本地任务调度系统ZTMS实战指南

1. 项目概述:这不是一个“咸鱼”项目,而是一套轻量级任务调度系统的实操落地“咸鱼ZTMS实例—程序”这个标题乍看有点让人摸不着头脑——“咸鱼”是平台名?代号?还是某种自嘲式命名习惯?其实它背后指向的,是…

作者头像 李华
网站建设 2026/10/13 4:20:24

手机打造电影感台词拼图:质感、光影与排版全攻略

朋友圈里的那种台词拼图,看着是真有电影味儿。黑底白字,配上一句恰到好处的台词,再加一点光影和噪点,整个画面的情绪感一下就上来了。不少人以为这种图得用电脑软件精修,或者需要专业的设计基础,其实真不是…

作者头像 李华