Python实战:伦敦住房数据清洗、分析与HTML报告生成
简介这份资源是面向数据分析初学者与进阶学习者的伦敦住房市场分析项目基于超过13000条真实记录涵盖平均房价、房屋销售数量与犯罪率等关键指标帮助读者理解房地产市场的动态与影响因素。压缩包共5个文件约566KB包含2个Jupyter Notebook、1个CSV数据集、1个Markdown说明文档和1个HTML页面分别用于代码演示、数据存储、项目说明与结果展示。已有173人学习下载。项目完整覆盖数据加载与预处理、缺失值与异常值处理、基于matplotlib和seaborn的探索性数据分析、相关性热力图与聚类分析以及线性回归、决策树、随机森林等房价预测建模与R²、MSE、MAE等指标评估并涉及Plotly或Bokeh交互式仪表板与报告撰写。读者可借此掌握从数据清洗到建模评估的全流程Python实战技能积累可复用的分析思路与代码模板。1. 拿到一份伦敦住房数据集先别急着画图很多人拿到London-housing-data这类项目第一反应是打开 Jupyter 直接df.head()然后开始画柱状图。我见过太多人这么干结果跑到一半发现字段类型全是 object价格列里混着£和逗号日期列是字符串最后花在清洗上的时间比分析还多。这个项目本质上是一套用 Python 做城市住房数据探索的完整流程——从原始 CSV 到清洗、特征构造、分组聚合、可视化最后输出能支撑判断的结论。它适合两类人一是刚学完 pandas 想找个真实数据集练手的二是需要快速摸清一个城市住房市场结构的从业者。HTML 报告输出是它常见的交付形态之一后面会专门讲怎么把分析结果落成可分享的页面。核心价值不在于代码多复杂而在于它把「脏数据到可用结论」这条链路走通了。2. 数据加载与字段清洗把 £ 和逗号从价格列里剥出来2.1 先看清数据长什么样再动手伦敦住房数据集的典型结构通常包含区域名area、房产类型property_type、卧室数bedrooms、价格price、成交日期date、邮编postcode等字段。不同来源的 CSV 编码和分隔符可能不一样常见做法是先探测编码再读入避免中文或特殊符号乱码。import pandas as pd import numpy as np # 先探测文件编码避免直接读入报 UnicodeDecodeError import chardet with open(london_housing.csv, rb) as f: raw f.read(100000) encoding chardet.detect(raw)[encoding] print(f探测到的编码: {encoding}) # 用探测结果读入low_memoryFalse 防止混合类型警告 df pd.read_csv(london_housing.csv, encodingencoding, low_memoryFalse) print(df.shape) print(df.dtypes) print(df.head(10))这段代码的逻辑是先用chardet对文件前 100KB 做编码探测拿到编码后再传给read_csv。low_memoryFalse的作用是让 pandas 一次性读完再推断类型避免分块读取时同一列被推断成不同类型。参数上encoding不设的话默认 utf-8遇到 latin-1 或 cp1252 就会炸。df.dtypes是必看的一步如果 price 显示为 object说明里面有非数字字符下一步就得处理。2.2 价格列的清洗是重头戏伦敦房价数据里价格字段经常长这样£450,000、£1,200,000、甚至Price on application。直接astype(float)必翻车。我一般会写一个专门的清洗函数把货币符号、千分位逗号、空格全部去掉再把无法转换的标记为 NaN。def clean_price(val): 清洗价格字段 - 去掉 £ 符号和千分位逗号 - 去掉首尾空格 - 无法转换的返回 np.nan if pd.isna(val): return np.nan val str(val).replace(£, ).replace(,, ).strip() try: return float(val) except ValueError: return np.nan df[price_clean] df[price].apply(clean_price) # 查看清洗后的缺失情况 missing_ratio df[price_clean].isna().mean() print(f价格缺失比例: {missing_ratio:.2%}) # 缺失超过 30% 的列要考虑是否保留 threshold 0.3 cols_to_drop [c for c in df.columns if df[c].isna().mean() threshold] print(f建议丢弃的列: {cols_to_drop})clean_price函数的核心是「先替换再转换」replace链式调用把干扰字符去掉try/except兜住那些文本型价格。missing_ratio用来判断这列还值不值得用——如果超过 30% 都是 NaN后续分析结论的可信度就很低。cols_to_drop是一个通用的缺失列筛选逻辑阈值 0.3 是我常用的经验值你可以根据业务容忍度调整到 0.2 或 0.5。2.3 日期和分类字段的类型修正日期列如果是字符串后续按时间聚合会非常别扭。常见做法是统一转成datetime64同时把区域名、房产类型这类低基数字段转成category能省内存也能加速 groupby。# 日期转换errorscoerce 让无法解析的变成 NaT df[date] pd.to_datetime(df[date], errorscoerce, dayfirstTrue) # 分类字段转 category for col in [area, property_type, postcode]: if col in df.columns: df[col] df[col].astype(category) # 检查日期范围是否合理 print(df[date].min(), df[date].max()) print(df[date].isna().sum())dayfirstTrue是针对英式日期格式日/月/年的关键参数不设的话 pandas 会按美式月/日/年解析导致大量 NaT。errorscoerce保证解析失败的不会抛异常而是变成 NaT方便后续统计缺失量。转category的收益在数据量大时才明显但养成习惯没坏处。3. 特征构造与分组聚合从原始字段里挖出可比较的指标3.1 单价指标比总价更有分析价值总价受户型面积影响太大直接比较不同区域的均价容易得出误导性结论。我一般会构造「每卧室价格」或「每平方英尺价格」这类归一化指标。如果数据集里有面积字段就直接除没有的话用卧室数做粗略代理。# 构造每卧室单价避免除以 0 df[price_per_bedroom] df.apply( lambda row: row[price_clean] / row[bedrooms] if pd.notna(row[price_clean]) and row[bedrooms] 0 else np.nan, axis1 ) # 按区域聚合同时算多个统计量 area_stats df.groupby(area, observedTrue).agg( median_price(price_clean, median), mean_price(price_clean, mean), median_ppb(price_per_bedroom, median), count(price_clean, count) ).reset_index() # 过滤掉样本量太少的区域 area_stats area_stats[area_stats[count] 30] area_stats area_stats.sort_values(median_ppb, ascendingFalse) print(area_stats.head(15))apply里用lambda做条件判断确保卧室数为 0 或价格缺失时返回 NaN避免除零错误。groupby的observedTrue在 category 类型下能避免生成大量空组合。聚合时同时算中位数和均值是因为房价分布通常右偏均值容易被豪宅拉高中位数更能反映典型水平。count 30是一个样本量门槛低于这个数的区域统计结论不稳定我一般会直接过滤掉。3.2 时间维度上的价格趋势如果日期字段跨度够大按季度或年度聚合能看出市场走势。这里要注意的是不同时期的成交结构可能不同所以趋势分析最好固定房产类型或卧室数再做。# 提取季度 df[quarter] df[date].dt.to_period(Q) # 只看 2 卧室公寓的趋势 flat_2bed df[(df[property_type] Flat) (df[bedrooms] 2)] trend flat_2bed.groupby(quarter, observedTrue).agg( median_price(price_clean, median), volume(price_clean, count) ).reset_index() trend[quarter] trend[quarter].astype(str) print(trend.tail(12))dt.to_period(Q)把日期转成季度周期对象比手动算季度更省事。筛选条件固定了房产类型和卧室数这样趋势反映的是「同类房产」的价格变化而不是成交结构变化带来的假象。volume列用来判断某个季度的结论是否可信——如果某季度只有 5 笔成交中位数波动大是正常的。3.3 交叉分析区域和房产类型的组合单看区域或单看类型都不够真正有决策价值的是「某区域某类型房产」的表现。用pivot_table可以快速拉出一张交叉表。pivot pd.pivot_table( df, valuesprice_clean, indexarea, columnsproperty_type, aggfuncmedian ) # 只保留样本量足够的区域 valid_areas area_stats[area].tolist() pivot pivot.loc[pivot.index.isin(valid_areas)] # 按公寓价格排序 if Flat in pivot.columns: pivot pivot.sort_values(Flat, ascendingFalse) print(pivot.head(20))pivot_table默认用均值这里显式指定aggfuncmedian更稳健。valid_areas复用了上一节的样本量过滤结果保证交叉表里的区域都是有统计意义的。排序选Flat列是因为公寓通常是伦敦市场的主力交易类型以它为基准比较符合实际。4. 可视化与 HTML 报告输出让结论能直接给人看4.1 用 matplotlib 快速出图分析结果如果只停留在 DataFrame 里很难跟非技术同事沟通。我一般会先用 matplotlib 出几张核心图确认数据形态没问题再考虑交互式方案。import matplotlib.pyplot as plt import matplotlib # 设置中文字体避免乱码 matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(14, 6)) # 左图区域中位单价 Top 15 top15 area_stats.head(15) axes[0].barh(top15[area], top15[median_ppb], colorsteelblue) axes[0].set_xlabel(Median Price per Bedroom (£)) axes[0].set_title(Top 15 Areas by Median Price per Bedroom) axes[0].invert_yaxis() # 右图2 卧室公寓季度趋势 axes[1].plot(trend[quarter], trend[median_price], markero, colorcoral) axes[1].set_xlabel(Quarter) axes[1].set_ylabel(Median Price (£)) axes[1].set_title(2-Bed Flat Price Trend) axes[1].tick_params(axisx, rotation45) plt.tight_layout() plt.savefig(london_housing_overview.png, dpi150) plt.show()barh画横向条形图区域名不会挤在一起。invert_yaxis()让排名最高的区域显示在顶部符合阅读习惯。tick_params(rotation45)解决季度标签重叠问题。dpi150保证保存的图片在报告里够清晰。中文字体设置是必须的否则标题里的中文会变成方框。4.2 生成 HTML 报告HTML 输出是这类项目常见的交付形式好处是能嵌表格、嵌图片、加样式发给人直接浏览器打开就能看。常见做法是用pandas的to_html配合模板字符串拼一个简单页面。# 把关键表格转成 HTML area_table_html area_stats.head(20).to_html( indexFalse, classestable table-striped, float_format%.0f ) trend_table_html trend.tail(8).to_html( indexFalse, classestable, float_format%.0f ) html_template f !DOCTYPE html html head meta charsetutf-8 titleLondon Housing Analysis/title style body {{ font-family: Arial, sans-serif; margin: 40px; }} table {{ border-collapse: collapse; width: 100%; }} th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }} th {{ background-color: #f2f2f2; }} img {{ max-width: 100%; height: auto; }} /style /head body h1London Housing Data Analysis/h1 h2Overview/h2 img srclondon_housing_overview.png altOverview Charts h2Top Areas by Price per Bedroom/h2 {area_table_html} h2Recent Quarterly Trend (2-Bed Flat)/h2 {trend_table_html} /body /html with open(london_housing_report.html, w, encodingutf-8) as f: f.write(html_template) print(报告已生成: london_housing_report.html)to_html的classes参数给表格加上 CSS 类名配合style里的定义就能有基本样式。float_format%.0f让价格不显示小数读起来更干净。图片用相对路径引用保证 HTML 文件和图片在同一目录下能正常显示。整个模板就是一个 f-string没有引入额外依赖适合快速交付。如果要更复杂的报告可以换成jinja2模板但那是另一个话题了。5. 避坑与排查这几个地方我翻过车5.1 价格清洗后没检查量级中位数算出来是错的现象清洗完价格直接 groupby发现某区域中位数只有几百英镑。原因部分价格字段单位是「千英镑」或者清洗时把小数点也去掉了。解决清洗后先看describe()确认 min、max、中位数的量级是否合理异常值单独拉出来看原始值。5.2 日期解析用了默认参数一半数据变成 NaT现象pd.to_datetime之后isna().sum()占了快一半。原因英式日期是日/月/年默认按美式解析13 以上的「月份」全部失败。解决加dayfirstTrue并且解析后检查日期范围是否落在合理区间比如 2000 到当前年份。5.3 groupby 之后忘了 reset_index后续取列报 KeyError现象area_stats[area]报 KeyError。原因groupby 后区域名变成了索引不是列。解决聚合时加.reset_index()或者用.loc按索引取。我一般习惯直接 reset省得后面记不清。5.4 category 类型做 groupby 出现大量空组合现象groupby 结果里出现很多 count 为 0 的行。原因category 类型会保留所有类别即使某些类别在当前数据里没有出现。解决groupby 时加observedTrue只保留实际出现的组合。5.5 HTML 报告里的图片路径用了绝对路径换台机器就裂了现象本地打开报告图片正常发给别人图片全挂。原因img src写的是本地绝对路径。解决统一用相对路径并且把 HTML 和图片放在同一个目录下打包发送。如果图片多可以转成 base64 内嵌但文件会变大。6. 进阶技巧把分析流程封装成可复用的管道走到这里单次分析已经能跑通了。但如果你需要反复跑不同月份的数据或者换一个城市的数据集把清洗和聚合逻辑封装成函数会省很多事。我一般会写一个build_report函数输入原始 CSV 路径输出 HTML 报告路径中间所有步骤串起来。def build_report(input_csv, output_html, min_samples30): 一键生成住房数据 HTML 报告 input_csv: 原始 CSV 路径 output_html: 输出 HTML 路径 min_samples: 区域最小样本量门槛 # 1. 加载 df pd.read_csv(input_csv, low_memoryFalse) # 2. 清洗价格 df[price_clean] df[price].apply(clean_price) # 3. 日期解析 df[date] pd.to_datetime(df[date], errorscoerce, dayfirstTrue) # 4. 构造单价 df[price_per_bedroom] df.apply( lambda r: r[price_clean] / r[bedrooms] if pd.notna(r[price_clean]) and r[bedrooms] 0 else np.nan, axis1 ) # 5. 聚合 stats df.groupby(area, observedTrue).agg( median_price(price_clean, median), median_ppb(price_per_bedroom, median), count(price_clean, count) ).reset_index() stats stats[stats[count] min_samples] # 6. 输出 HTML table_html stats.sort_values(median_ppb, ascendingFalse).head(20).to_html( indexFalse, float_format%.0f ) html fhtmlheadmeta charsetutf-8/headbody html fh1Housing Report/h1{table_html}/body/html with open(output_html, w, encodingutf-8) as f: f.write(html) return output_html # 调用 build_report(london_housing.csv, report_v2.html)这个函数的参数设计有几个考虑min_samples暴露出来是为了不同数据集能调整伦敦数据用 30 合适小城市可能得降到 10。clean_price是外部定义的函数封装时要么一起打包要么改成内部定义。返回输出路径而不是 DataFrame是为了让调用方直接拿到可交付的文件。验证封装是否正确的办法很简单拿同一份数据分别跑手动流程和build_report对比输出的中位数和样本量是否一致。我一般会抽三个区域手工核对确认没问题再拿去跑批量。从那以后我每次封装完管道都会先用一份小样本数据跑一遍全流程确认中间没有类型报错、没有意外的 NaN 扩散再上全量数据。这个习惯帮我省过好几次「跑了两小时才发现日期解析全错」的后悔药。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

光学仿真必会:VirtualLab Fusion通用探测器配置与实战指南

光学仿真必会:VirtualLab Fusion通用探测器配置与实战指南

搞光学仿真的人,几乎都会在项目里遇到同一个问题:光路搭好了,模拟也跑了,屏幕上各种光栅、透镜、波导都在发光,可你到底怎么知道“光斑长什么样”“相位抖没抖”“偏振转没转”?在 VirtualLab Fusion 里&am…

2026/10/13 5:13:27 阅读更多 →
Persome屏幕捕获管线深度解析:Accessibility事件、本地OCR与截图加密存储

Persome屏幕捕获管线深度解析:Accessibility事件、本地OCR与截图加密存储

【免费下载链接】personal-model Build your HUMAN.md. 项目地址: https://gitcode.com/gh_mirrors/pe/personal-model 点击查看 免费下载 Persome(开源项目 personal-model)的屏幕捕获管线是整座个人模型的"眼睛":它通…

2026/10/13 5:13:27 阅读更多 →
Agent技能编排实战:让大模型真正“动手办事”的工程指南

Agent技能编排实战:让大模型真正“动手办事”的工程指南

接手这个“agent-skills”项目的时候,我脑子里最先冒出来的问题是:大模型明明已经能写诗、能解题、能聊得头头是道,为什么真正想让它替我去处理一点“具体的工作”,比如整理一下邮箱、批量改个文件名、跨系统同步一条数据&#xf…

2026/10/13 5:13:27 阅读更多 →

最新新闻

AnyPS5实战:用开源面板统一管理PS5游戏库与远程串流

AnyPS5实战:用开源面板统一管理PS5游戏库与远程串流

AnyPS5 这个名字听起来像是一台主机,其实它是我折腾的一个本地工具项目。起因很简单:我手里的游戏、账号、主机状态越来越散,数字版躺在不同账号里,光盘版挤在书架上,想远程玩一把还得分清哪台机器、哪个账号、哪次打折…

2026/10/13 5:57:39 阅读更多 →
多AI Provider架构设计:从统一接口到动态路由的接入层重构

多AI Provider架构设计:从统一接口到动态路由的接入层重构

一个多月前,我把 HagiCode 平台的核心接入层从“单 Provider 写死”重构成了“多 AI Provider 架构”。今天把整个过程中的设计取舍、代码落地方案以及踩过的坑整理出来,算是给后端团队和做 AI 应用集成的朋友一份参考。这里说的 AI Provider&#xff0c…

2026/10/13 5:57:39 阅读更多 →
小程序加密实战:CE固定动态密钥与全流程自动化方案

小程序加密实战:CE固定动态密钥与全流程自动化方案

小程序加密这件事,很多团队一开始都低估了。刚接手一个电商小程序项目时,我以为把请求参数签个名、在本地存个AppSecret就够了,结果上线没几天,接口就被别人用脚本刷了一遍。原因不复杂:小程序包里的东西基本是透明的&…

2026/10/13 5:57:39 阅读更多 →
风机叶片开裂智能预警:机器学习方案的数据清洗、特征工程与模型落地

风机叶片开裂智能预警:机器学习方案的数据清洗、特征工程与模型落地

简介:一套面向风电运维与机器学习实践者的风机叶片开裂故障智能预警系统实现方案,系统阐述从多传感器网络部署、数据标准化滤波与质量校验,到共振频移、应变分布等敏感特征提取,再到集成学习与深度神经网络构建异常识别模型&#…

2026/10/13 5:57:39 阅读更多 →
小程序加解密动态密钥管理:从受控调试到自动化验证

小程序加解密动态密钥管理:从受控调试到自动化验证

做小程序加解密这一块,最难受的从来不是AES算法本身怎么写,而是密钥怎么发、怎么存、怎么换,以及出了问题之后怎么把密文层层剥开去定位。之前我处理过一个线上问题:同一套动态密钥在当前会话里加解密完全正常,换一个会…

2026/10/13 5:57:39 阅读更多 →
西门子PLC实战:锂电浆料线程序开发与通讯调试全流程

西门子PLC实战:锂电浆料线程序开发与通讯调试全流程

很多人找我聊西门子PLC程序开发怎么入门,我一般不会直接甩教程,而是先反问一句:你见过新能源浆料线吗?去年我接手了一条锂电正极浆料产线的整套PLC程序开发,从需求梳理到带料调试,前后折腾了小半年。说句实…

2026/10/13 5:56:39 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →