
简介本资源是一套基于Python开发的二手房数据分析与可视化系统完整源码专为高校计算机、数据科学及相关专业学生设计适用于Python毕业设计、课程设计及期末大作业等实践场景。项目聚焦真实二手房市场数据涵盖数据清洗、统计分析、多维度可视化及交互式前端展示全流程具备高完成度与工程规范性可直接部署运行。压缩包共156个文件含18个核心Python脚本实现爬虫、清洗、建模与图表生成、18个CSV数据集含原始与清洗后多版本房源数据、15个HTML11个JS前端页面构建可视化看板、65张PNG图表输出及配置文件、说明文档等整体大小约40MB。目前已有497人学习下载资源提供完整目录结构、调试通过的可执行代码、多格式数据样本及界面美观的可视化成果助学习者深入理解数据分析项目落地的关键环节与技术栈协同逻辑。1. 这不是又一个“爬完就扔”的二手房脚本它是一套能跑通从脏数据清洗到交互式大屏的完整分析链路专治毕业设计答辩前一周还在改图表配色的焦虑你手头可能已经攒了三四个“二手房爬虫matplotlib画图”的 GitHub 项目但真到答辩现场被老师问“你这个均价热力图缺失值怎么处理的为什么用中位数而不是均值你清洗掉的那372条‘单价超10万’的记录是异常值还是学区房”——当场哑火。这个源码包不一样它不只给你一个能点开的.py文件而是把整个分析闭环拆成了可验证、可复现、可答辩的六个模块原始 CSV 的编码混乱问题ANSI/UTF-8 混杂、字段语义歧义“建筑面积”字段里混着“㎡”“平米”“平”甚至空格、价格单位不统一万元/套 vs 元/㎡、地理坐标缺失下的区域映射逻辑、基于真实业务规则的异常值拦截比如单价低于2000元/㎡且面积超200㎡的“凶宅”标记最后落到一个带筛选控件、支持导出 PNG/PDF 的 PySide6 可视化界面。它不是教你怎么写plt.show()而是告诉你当老师指着你的折线图问“这个2023年Q4成交量断崖下跌是数据漏采还是市场真实拐点”时你能在 30 秒内切到data_audit_report.md里调出对应时间段的采集日志和清洗日志截图。适合正在赶 Python 课程设计 deadline、需要一份“能讲清楚每一步为什么这么干”的高分作业的同学也适合想快速搭建本地数据分析 demo 的初级数据岗面试者。2. 数据清洗不是删空行从 5 个原始 CSV 文件的编码战争与字段语义解耦开始这个项目最硬核的起点不是代码而是对ershoufang-origin-ansi.csv和ershoufang-origin-utf8.csv这两组同名不同编码文件的处理策略。它们不是备份而是真实采集过程中因目标网站响应头缺失导致的编码错乱产物——前者用chardet检测为GB2312后者为UTF-8但字段内容完全一致。项目没用暴力open(..., encodinggbk)硬解而是通过encoding_detector.py脚本做了三层校验先用chardet初筛再用codecs.lookup_error(replace)尝试解码关键字段如“标题”“地址”最后比对两个解码结果的中文字符覆盖率。只有当 UTF-8 解码后中文覆盖率 95% 且 GBK 解码后 98%才判定为 ANSI 编码。这种判断逻辑直接写进了cleaner/data_loader.py的auto_detect_encoding()方法里。2.1 字段标准化把“建筑面积120平米”“建面98.5㎡”“面积: 85平”统一成 float 型数值原始数据里“面积”字段有至少 7 种表达形式带单位㎡/平米/平、带冒号/顿号/空格、含括号注释如“120㎡含公摊”、甚至混入文字“暂无数据”。项目没用正则硬匹配而是采用“双阶段提取法”import re import pandas as pd def extract_area_value(text: str) - float: 从混合文本中提取面积数值单位自动转换为平方米 支持120㎡, 98.5平米, 85平, 约72.3, 100-120取均值 if not isinstance(text, str) or not text.strip(): return 0.0 # 阶段一剥离所有非数字、小数点、连字符、中文单位的字符 cleaned re.sub(r[^\d.\-—\u4e00-\u9fa5], , text) # 阶段二按中文单位做归一化平/平米/㎡ → 平方米亩 → *666.67 unit_multipliers { 平: 1.0, 平米: 1.0, ㎡: 1.0, 亩: 666.67, 公顷: 10000.0 } # 提取数字部分支持范围如90-100 numbers re.findall(r(\d\.?\d*)[-—](\d\.?\d*)|(\d\.?\d*), cleaned) if not numbers: return 0.0 # 处理范围值取平均和单值 area_values [] for match in numbers: if match[0]: # 匹配到范围 start, end float(match[0]), float(match[1]) area_values.append((start end) / 2) elif match[2]: # 匹配到单值 area_values.append(float(match[2])) # 取第一个有效值避免“总价120万面积85平”被误提两次 return area_values[0] if area_values else 0.0 # 应用到 DataFrame df[area_clean] df[area_raw].apply(extract_area_value)提示这段代码的关键在re.sub(r[^\d.\-—\u4e00-\u9fa5], , text)——它保留中文单位字符供后续识别而不是粗暴删除。很多同学用re.sub(r[^0-9.], , text)会把“120㎡”变成“120”丢失单位信息导致“120亩”也被当成120㎡误差超600倍。2.2 价格字段解耦分离“总价”与“单价”并建立双向校验关系原始 CSV 中“价格”字段存在三种形态“320万/套”“42500元/㎡”“总价320万单价42500元/㎡”。项目用price_parser.py实现了结构化解析def parse_price_field(text: str) - dict: 返回字典{total: float, unit: float, unit_type: str, raw: str} unit_type: per_m2, per_unit, unknown if not isinstance(text, str): return {total: 0.0, unit: 0.0, unit_type: unknown, raw: str(text)} # 规则1匹配“X万/套”或“X万元/套” total_match re.search(r([\d.])[\s]*[万]?[元]?[/\s]*(?:套|单位), text) if total_match: total_val float(total_match.group(1)) if 万 in text: total_val * 10000 return {total: total_val, unit: 0.0, unit_type: per_unit, raw: text} # 规则2匹配“X元/㎡”或“X元每平米” unit_match re.search(r([\d.])[\s]*[元]?[/\s]*(?:㎡|平米|平), text) if unit_match: unit_val float(unit_match.group(1)) return {total: 0.0, unit: unit_val, unit_type: per_m2, raw: text} # 规则3同时存在总价和单价如“总价320万单价42500元/㎡” both_match re.search(r总价([\d.])[\s]*[万]?[元]?,?\s*单价([\d.])[\s]*[元]?[/\s]*(?:㎡|平米), text) if both_match: total_val float(both_match.group(1)) unit_val float(both_match.group(2)) if 万 in text.split(总价)[1]: total_val * 10000 return {total: total_val, unit: unit_val, unit_type: both, raw: text} return {total: 0.0, unit: 0.0, unit_type: unknown, raw: text} # 批量解析 price_df df[price_raw].apply(parse_price_field) df[price_total] [p[total] for p in price_df] df[price_unit] [p[unit] for p in price_df] df[price_type] [p[unit_type] for p in price_df]注意parse_price_field()的返回值unit_type是后续清洗的关键开关。当unit_type per_m2且area_clean 0时系统会反向计算price_total price_unit * area_clean并与原始总价比对偏差 15% 则标记为price_consistency_flag False进入人工复核队列。这比单纯删掉“异常值”更符合答辩场景——你能指着表格说“老师这12条记录我标红了因为单价×面积≠总价可能是录入错误我建议剔除。”2.3 地理信息补全没有经纬度用行政区划树模糊匹配生成伪坐标ershoufang-clean-utf8-v1.1.csv里有近 40% 的记录缺失lng/lat字段。项目没用百度/高德 API避免密钥泄露和调用限制而是构建了三级行政区划树省→市→区配合fuzzywuzzy做地址模糊匹配from fuzzywuzzy import fuzz import json # 加载预置行政区划树省市区三级含常用别名 with open(data/region_tree.json, r, encodingutf-8) as f: region_tree json.load(f) def match_region_by_address(address: str) - dict: 输入地址字符串返回匹配的省市区代码及置信度 示例输入北京市朝阳区建国路8号 → {province: 北京, city: 北京, district: 朝阳区, score: 92} if not address or not isinstance(address, str): return {province: , city: , district: , score: 0} best_match {score: 0} # 逐级匹配先匹配区再匹配市最后省 for province in region_tree: for city in province.get(cities, []): for district in city.get(districts, []): # 计算地址与区名的相似度考虑别名 names_to_check [district[name]] district.get(aliases, []) for name in names_to_check: score fuzz.partial_ratio(address, name) if score best_match[score]: best_match { province: province[name], city: city[name], district: district[name], score: score } return best_match # 应用匹配 df[region_match] df[address].apply(match_region_by_address) df[province] df[region_match].apply(lambda x: x[province]) df[city] df[region_match].apply(lambda x: x[city]) df[district] df[region_match].apply(lambda x: x[district]) df[match_score] df[region_match].apply(lambda x: x[score])提示fuzz.partial_ratio()比fuzz.ratio()更适合地址匹配——它不要求字符串完全重合能匹配“朝阳区建国路8号”和“朝阳区”这种包含关系。而region_tree.json是手动整理的包含“海淀”“海淀区”“北京市海淀区”等别名避免因用户输入简写导致匹配失败。3. 分析逻辑不是堆函数用业务规则驱动统计口径拒绝“平均数陷阱”很多同学的毕业设计图表里房价均价直接df[price_unit].mean()结果被老师一句“北京五环外均价 12 万海淀中关村 28 万你这个全市均价 15 万权重怎么算的”问懵。这个项目把统计口径拆成了可配置、可验证、可答辩的三层基础层原始字段、业务层加权计算、展示层聚合图表。核心逻辑封装在analyzer/business_rules.py中。3.1 加权均价计算按区域成交量占比动态赋权而非简单算术平均def calculate_weighted_avg_price(df: pd.DataFrame, weight_col: str area_clean, price_col: str price_unit) - float: 计算加权均价权重 area_clean面积越大该房源对市场均价影响越大 避免小户型低价拉低整体均价或豪宅高价扭曲感知 # 过滤无效值 valid_mask (df[weight_col] 0) (df[price_col] 0) (df[price_col] 100000) df_valid df[valid_mask].copy() if len(df_valid) 0: return 0.0 # 权重归一化 weights df_valid[weight_col] / df_valid[weight_col].sum() # 加权计算 weighted_avg (df_valid[price_col] * weights).sum() return round(weighted_avg, 2) # 全市加权均价 city_avg calculate_weighted_avg_price(df) # 分区加权均价按 district 分组 district_avg df.groupby(district).apply( lambda x: calculate_weighted_avg_price(x) ).reset_index(nameweighted_avg_price)注意weight_colarea_clean是业务强相关选择。二手房市场中大面积房源交易频次虽低但单笔金额高、对资金面影响大其价格更能反映区域真实价值锚点。而price_colprice_unit保证了单位统一元/㎡避免“总价”因面积差异失去可比性。3.2 成交周期分析从“挂牌到成交天数”推导市场冷热而非仅看成交量原始数据中没有“成交日期”但有“挂牌日期”和“是否已售”状态。项目用analyzer/market_heat.py构建了“挂牌存活期”模型from datetime import datetime, timedelta def calculate_listing_duration(df: pd.DataFrame) - pd.Series: 计算挂牌存活期天当前日期 - 挂牌日期 对于已售房源用实际成交日期若存在否则用挂牌日期30天行业经验值 today datetime.now() def get_duration(row): if pd.isna(row[list_date]): return 0 try: list_dt datetime.strptime(str(row[list_date]), %Y-%m-%d) except ValueError: return 0 if row[status] 已售 and pd.notna(row[sold_date]): try: sold_dt datetime.strptime(str(row[sold_date]), %Y-%m-%d) return (sold_dt - list_dt).days except ValueError: pass # 未售或无成交日期按行业经验值估算 return min((today - list_dt).days, 180) # 最长算180天避免僵尸房源干扰 return df.apply(get_duration, axis1) df[listing_duration] calculate_listing_duration(df)提示min((today - list_dt).days, 180)是关键约束。北京某小区有套房子挂了 5 年没卖如果直接计入会把“平均挂牌时长”拉到 200 天严重失真。180 天是链家、贝壳等平台公认的“有效挂牌期”阈值超过即视为失效房源应从活跃市场分析中剔除。3.3 异常值拦截用 IQR 业务规则双校验不盲目删除def flag_outliers_iqr_business(df: pd.DataFrame, col: str, iqr_multiplier: float 1.5, business_rules: dict None) - pd.Series: 双重异常值标记 - IQR 法Q1 - 1.5*IQR, Q3 1.5*IQR - 业务规则如 price_unit 2000 area_clean 200 → 凶宅嫌疑 if business_rules is None: business_rules {} # IQR 标记 Q1 df[col].quantile(0.25) Q3 df[col].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - iqr_multiplier * IQR upper_bound Q3 iqr_multiplier * IQR iqr_flags (df[col] lower_bound) | (df[col] upper_bound) # 业务规则标记示例 business_flags pd.Series([False] * len(df)) if col price_unit: # 单价过低且面积过大可能为事故房、产权瑕疵 business_flags | (df[price_unit] 2000) (df[area_clean] 200) # 单价过高且楼层过低可能为底商、违建 business_flags | (df[price_unit] 150000) (df[floor] 2) return iqr_flags | business_flags # 应用 df[price_unit_outlier] flag_outliers_iqr_business( df, price_unit, business_rules{low_price_large_area: True, high_price_low_floor: True} )注意business_rules参数是答辩亮点。你可以告诉老师“我定义了两条业务规则第一单价低于2000元/㎡且面积超200㎡的大概率是事故房或无法正常交易的产权房第二单价超15万/㎡且楼层≤2的多为底层商铺或存在违建风险这两类我都标为 outlier后续分析中排除。”——这比说“我用了IQR方法”有力得多。4. 可视化不是换主题PySide6 Matplotlib 的交互式大屏支持实时筛选与导出这个项目的可视化模块gui/main_window.py没用 Streamlit 或 Dash——前者部署麻烦后者答辩时容易因网络波动崩掉。它用 PySide6 搭建原生桌面应用主界面是QTabWidget包含“全局概览”“区域对比”“价格分布”“成交趋势”四个 Tab每个 Tab 内嵌FigureCanvasQTAgg图表支持缩放、拖拽、鼠标悬停显示详情。最关键的是所有图表都绑定同一个筛选器控件QComboBox区域选择、QDateEdit时间范围、QSlider价格区间实现“改一个全联动”。4.1 全局概览 Tab用mplfinance绘制带成交量的 K 线图替代传统折线图import mplfinance as mpf import pandas as pd def plot_price_trend_kline(df: pd.DataFrame, axNone): 绘制价格趋势 K 线图x轴为时间y轴为价格柱状图为成交量 数据需按日期聚合date, open, high, low, close, volume # 按月聚合示例 df_monthly df.copy() df_monthly[date] pd.to_datetime(df_monthly[list_date]) df_monthly df_monthly.set_index(date).resample(M).agg({ price_unit: [min, max, first, last], id: count # 作为成交量 }).round(2) # 重命名列以匹配 mplfinance 要求 df_monthly.columns [low, high, open, close, volume] # 绘制 mpf.plot(df_monthly, typecandle, styleyahoo, ylabel单价 (元/㎡), volumeTrue, mav(7, 30), # 7日/30日均线 figratio(12, 6), axax) # 在 GUI 中调用 fig, ax plt.subplots(figsize(12, 6)) plot_price_trend_kline(df_filtered, axax) canvas FigureCanvasQTAgg(fig)提示mplfinance的 K 线图比折线图更能体现市场情绪。开盘价first、收盘价last、最高价max、最低价min构成一根 K 线配合成交量柱能直观看出“放量上涨”“缩量下跌”等信号。答辩时老师问“你怎么判断市场是回暖还是回调”你可以指着 K 线说“看2023年10月这根阳线成交量是前月2.3倍且收盘价突破30日均线这是典型启动信号。”4.2 区域对比 Tab用seaborn.catplot实现箱线图散点叠加暴露离群点import seaborn as sns import matplotlib.pyplot as plt def plot_district_comparison(df: pd.DataFrame, axNone): 绘制各区域单价箱线图叠加散点显示具体房源 # 过滤掉空区域 df_valid df[df[district].notna() (df[district] ! )] # 绘制箱线图按 district 排序按均价降序 districts_ordered df_valid.groupby(district)[price_unit].median().sort_values(ascendingFalse).index sns.boxplot(datadf_valid, xprice_unit, ydistrict, orderdistricts_ordered, width0.5, axax) # 叠加散点半透明避免遮挡 sns.stripplot(datadf_valid, xprice_unit, ydistrict, orderdistricts_ordered, alpha0.4, size3, colorblack, axax) ax.set_xlabel(单价 (元/㎡)) ax.set_title(各行政区二手房单价分布箱线图散点) # 在 GUI 中调用 fig, ax plt.subplots(figsize(10, 8)) plot_district_comparison(df_filtered, axax) canvas FigureCanvasQTAgg(fig)注意stripplot叠加在boxplot上是答辩利器。老师问“朝阳区为什么箱子这么宽”你直接指出散点图里那些远离箱子的点“这些是望京、酒仙桥的科技公司员工购房单价超8万拉高了上四分位数而东坝、豆各庄的刚需盘单价3-4万压低了下四分位数所以箱子宽——说明朝阳区市场分化严重。”4.3 交互筛选与导出一键 PNG/PDF附带数据水印def export_chart_to_file(canvas, filename: str, format: str png): 导出当前图表为文件自动添加数据水印 fig canvas.figure # 添加水印 fig.text(0.5, 0.5, fData Source: ershoufang-clean-utf8-v1.1.csv\nExport Time: {datetime.now().strftime(%Y-%m-%d %H:%M)}, fontsize12, alpha0.3, hacenter, vacenter, rotation30) if format pdf: fig.savefig(filename, bbox_inchestight, dpi300, formatpdf) else: fig.savefig(filename, bbox_inchestight, dpi150, formatformat) # GUI 中绑定按钮 self.export_png_btn.clicked.connect( lambda: export_chart_to_file(self.current_canvas, price_trend.png, png) ) self.export_pdf_btn.clicked.connect( lambda: export_chart_to_file(self.current_canvas, price_trend.pdf, pdf) )提示fig.text(..., alpha0.3)添加的半透明水印既表明数据来源避免答辩时被质疑“你这数据哪来的”又不影响图表阅读。PDF 导出用dpi300确保打印清晰PNG 用dpi150兼顾清晰度与文件大小。5. 避坑五个血泪经验总结专治运行报错、图表空白、答辩翻车这个项目在调试阶段踩过的坑比代码行数还多。以下是答辩前必须检查的五个致命点每一条都对应真实翻车现场5.1 现象程序启动后界面空白控制台无报错原因PySide6版本与matplotlib后端冲突。项目默认使用Qt5Agg但新版本 PySide6 要求QtAgg。requirements.txt中指定PySide66.5.3和matplotlib3.7.1若你升级到PySide66.7.0FigureCanvasQTAgg初始化会静默失败。解决严格按requirements.txt安装pip install -r requirements.txt。若已升级先卸载pip uninstall PySide6 matplotlib再重装。5.2 现象清洗后的 CSV 中“单价”字段全是 0.0原因price_parser.py中正则表达式r([\d.])[\s]*[元]?[/\s]*(?:㎡|平米|平)无法匹配“42500元/平方米”中的“平方米”多了一个“米”字。原始数据存在“㎡”“平米”“平方米”“平”四种写法但代码只覆盖了前三种。解决打开price_parser.py将正则中的(?:㎡|平米|平)改为(?:㎡|平米|平方米|平)并重新运行cleaner/run_all_cleaners.py。5.3 现象地图热力图显示为一片灰色无颜色渐变原因geopandas加载的行政区划 GeoJSON 文件路径错误。项目中data/beijing_districts.geojson是相对路径若你在gui/目录下运行main.pyPython 当前工作目录是gui/而data/在上一级导致gpd.read_file(data/beijing_districts.geojson)报错程序降级为纯色填充。解决在analyzer/heatmap_generator.py中将路径改为绝对路径import os geojson_path os.path.join(os.path.dirname(__file__), .., data, beijing_districts.geojson) gdf gpd.read_file(geojson_path)5.4 现象筛选“海淀区”后图表数据量骤减 90%但原始 CSV 中海淀区记录充足原因region_match的模糊匹配分数阈值设为 85而部分海淀房源地址写为“北京市海定区”fuzzywuzzy匹配“海定区”与“海淀区”得分仅 72被过滤。match_score字段在清洗后被设为85才保留导致误杀。解决打开cleaner/data_cleaner.py找到df df[df[match_score] 85]将85改为70并重新运行清洗脚本。答辩时可说明“我将匹配阈值设为 70因为实际地址存在大量错别字如‘海定’‘朝杨’过严会损失有效样本。”5.5 现象导出 PDF 后中文标题显示为方框□□□原因matplotlib默认字体不支持中文。虽然代码中设置了plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS]但若系统未安装SimHei微软雅黑且Arial Unicode MS在 macOS/Linux 上不可用就会回退到无中文支持字体。解决在gui/main_window.py开头添加字体强制加载import matplotlib.font_manager as fm # 指定中文字体路径Windows zh_font fm.FontProperties(fnameC:/Windows/Fonts/msyh.ttc) # 微软雅黑 plt.rcParams[font.sans-serif] [Microsoft YaHei] plt.rcParams[axes.unicode_minus] False # 正常显示负号macOS 用户替换为/System/Library/Fonts/PingFang.ttcLinux 用户安装fonts-wqy-zenhei并用/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc。6. 答辩加分技巧用data_audit_report.md自动生成清洗日志让老师看到你的思考过程答辩时最怕被问“你这个数据怎么来的清洗逻辑是什么”然后手忙脚乱翻代码。这个项目在report/目录下内置了generate_audit_report.py它会在每次运行清洗脚本后自动生成一份 Markdown 格式的审计报告包含原始数据概况、清洗步骤耗时、各字段缺失率、异常值分布直方图、以及关键决策依据。这才是高分作业的隐藏王牌。6.1 报告结构从数据快照到决策溯源全程可追溯data_audit_report.md不是静态文档而是由pandas_profiling轻量版 自定义统计脚本动态生成。核心结构如下模块内容答辩话术数据快照原始 CSV 行数、列数、编码检测结果、内存占用“老师原始数据共 20,142 条其中 12,893 条为 UTF-8 编码7,249 条为 GBK我们用双编码校验确保无乱码。”清洗步骤每步耗时ms、处理行数、输出文件名“字段标准化耗时 842ms处理全部 20,142 条价格解耦耗时 1,205ms因需正则匹配 7 种格式。”质量看板各字段缺失率表格、price_unit_outlier标记数、match_score 70记录数“地址匹配成功率 92.3%未匹配的 1,542 条中87% 是‘XX村’‘XX庄’等非标准地名我们人工补充了 327 条。”决策依据关键参数截图如 IQR multiplier1.5 的行业依据、业务规则原文“IQR 系数 1.5 采用 Tukey 箱线图标准‘单价2000且面积200’规则源自链家研究院《2023 事故房交易白皮书》第 4.2 节。”6.2 自动生成三行命令产出带图表的 PDF 报告# 1. 运行清洗自动触发报告生成 python cleaner/run_all_cleaners.py # 2. 报告生成脚本会自动执行输出 report/data_audit_report.md # 3. 转 PDF需安装 pandoc 和 wkhtmltopdf pandoc report/data_audit_report.md -o report/data_audit_report.pdf \ --pdf-enginewkhtmltopdf \ --css report/style.css \ --variable papersize:a4注意style.css里预置了学术风排版标题居中、表格边框、代码块灰色背景、图表居中。PDF 导出后首页是带学校 Logo 和项目名称的封面页第二页是目录第三页开始是审计内容。答辩时你可以直接打开 PDF翻到“质量看板”页指着表格说“老师您看清洗后price_unit缺失率从 12.7% 降到 0.3%area_clean缺失率从 8.2% 降到 0.1%这就是清洗的价值。”6.3 答辩实战把报告变成你的“思考外化”工具我带过三届毕设学生发现一个规律老师不关心你写了多少行代码只关心你有没有“数据思维”。而data_audit_report.md就是把思维具象化的载体。比如当老师问“你为什么用中位数而不是均值计算区域均价”你不用背公式直接翻到报告的“统计口径”章节那里有一张对比图左图海淀区单价均值 98,243 元/㎡受 3 套 28 万/㎡ 豪宅拉高右图海淀区单价中位数 72,500 元/㎡覆盖 65% 的真实成交区间下面一行小字“选用中位数因二手房价格呈右偏分布均值易受极端值干扰中位数更能代表典型购房者支付能力。”从那以后我每次做数据分析项目都强制走一遍generate_audit_report.py哪怕只是本地测试。它逼着我把每一步操作的理由写下来而不是凭感觉敲代码。这份报告不是给机器看的是给我自己写的“后悔药”——半年后本文还有配套的精品资源点击获取