完成本实验后,你应能够:
你正在一家房地产数据分析公司实习。公司获取了温哥华市 2021 年的房产税务评估数据,希望从中挖掘有价值的洞察,为投资决策提供数据支撑。
你的分析工作将分为四个阶段:
评分规则: 共 100 分(4 个任务)。
| 任务 | 内容 | 分值 |
|---|---|---|
| 任务 1 | 数据探索与预处理 | 10 分 |
| 任务 2 | 数据可视化分析 | 25 分 |
| 任务 3 | 相关性分析 | 35 分 |
| 任务 4 | 自助法统计推断 | 30 分 |
作答要求:
# YOUR CODE HERE 的代码单元中完成实现,并删除 raise NotImplementedError()。本实验使用温哥华市 2021 年房产税务评估数据集(Vancouver Open Data Portal)。数据集包含 BC Assessment (BCA) 和市政来源的房产信息,涵盖了土地价值、房屋改建价值、建造年份、邮编等字段。
实验提供两份数据文件(位于 Lab-data/ 目录下):
A4-data.zip:包含完整数据集 property-tax-report_2021.csv(下方代码会自动解压);property-tax-report_2021_sample.csv:抽样数据集,用于任务 4 的自助法分析。下面的代码将完成数据解压、加载和环境准备。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
import zipfile
from pathlib import Path
warnings.filterwarnings("ignore")
# 加载完整数据集
df = pd.read_csv("Lab-data/property-tax-report_2021.csv")
print("数据集形状:", df.shape)
print("列名:", list(df.columns))
在进行任何深入分析之前,数据科学家的第一步总是对数据进行探索性分析:了解数据的结构、检查数据质量、处理缺失值,并为后续分析做好准备。
请完成以下操作:
describe() 获取所有数值列的描述性统计信息,并使用 Pandas 的 DataFrame Styler 将数字格式化为不含科学计数法的形式(即不显示浮点指数)。# YOUR CODE HERE
raise NotImplementedError()
我们选择 YEAR_BUILT 列进行分析,该列表示房产的建造年份。
请完成以下操作:
YEAR_BUILT 列的缺失值比例,并将结果存储在变量 year_built_missing_pct 中(取值范围 0~1);提示:缺失值在实际数据集中非常常见。在后续分析中,你需要注意缺失值对分析结果的潜在影响。
# YOUR CODE HERE
raise NotImplementedError()
print(f"YEAR_BUILT 列缺失值比例: {year_built_missing_pct:.4f}")
为后续分析做准备,请添加以下三列并存回 df:
CURRENT_PRICE:当前房产价格,等于 CURRENT_LAND_VALUE + CURRENT_IMPROVEMENT_VALUE;PREVIOUS_PRICE:往年房产价格,等于 PREVIOUS_LAND_VALUE + PREVIOUS_IMPROVEMENT_VALUE;HOUSE_PRICE:以百万为单位的当前房产价格,等于 CURRENT_PRICE / 1,000,000。# YOUR CODE HERE
raise NotImplementedError()
# ===== 任务 1 测试 =====
assert np.isclose(year_built_missing_pct, df["YEAR_BUILT"].isna().mean()), "缺失值比例计算有误"
assert "CURRENT_PRICE" in df.columns, "请添加 CURRENT_PRICE 列"
assert "PREVIOUS_PRICE" in df.columns, "请添加 PREVIOUS_PRICE 列"
assert "HOUSE_PRICE" in df.columns, "请添加 HOUSE_PRICE 列"
assert np.isclose(df["CURRENT_PRICE"].iloc[0],
df["CURRENT_LAND_VALUE"].iloc[0] + df["CURRENT_IMPROVEMENT_VALUE"].iloc[0]), "CURRENT_PRICE 计算有误"
assert np.isclose(df["HOUSE_PRICE"].iloc[0], df["CURRENT_PRICE"].iloc[0] / 1e6), "HOUSE_PRICE 计算有误"
print("✅ 任务 1 测试通过!")
绘制一张折线图,展示 1990 年至 2018 年之间,每年建造的房产数量。
要求:
# YOUR CODE HERE
raise NotImplementedError()
请写下你从上图中观察到的 两个 最有趣的发现。
发现
在 1900 年至 2020 年之间,哪些年份建造的房产最多?请绘制柱状图展示建造房产数量最多的前 20 个年份。
要求:
# YOUR CODE HERE
raise NotImplementedError()
请写下你从上图中观察到的 两个 最有趣的发现。
发现
1990 年至 2020 年间,各年建造的房产数量呈怎样的分布?请绘制直方图回答这一问题。
要求:
# YOUR CODE HERE
raise NotImplementedError()
请写下你从上图中观察到的 两个 最有趣的发现。
发现
对于建造房产数量超过 2000 的年份,年份和建造数量之间是否存在某种关系?请绘制散点图进行探索。
要求:
# YOUR CODE HERE
raise NotImplementedError()
请写下你从上图中观察到的 两个 最有趣的发现。
发现
温哥华的房产价格因地段不同而差异很大。请利用邮编的前三个字符(如 V6A、V5K)对房产进行分区,分析不同区域的房价中位数。
请完成以下操作:
PROPERTY_POSTAL_CODE 列提取邮编前三个字符,存入新列 POSTAL_PREFIX(注意:邮编中可能包含空格,需先去除空格再提取;邮编列可能包含缺失值,需妥善处理);POSTAL_PREFIX 分组,计算每个区域的 HOUSE_PRICE 中位数;# YOUR CODE HERE
raise NotImplementedError()
请写下你从上图中观察到的 两个 最有趣的发现。
发现
由于房价受地段影响很大,我们只分析邮编以 V6A 开头的房产,并排除 1900 年之前建造的房产。
# 筛选 V6A 区域、1900 年之后建造的房产
df_v6a = df[(df["PROPERTY_POSTAL_CODE"].str.startswith("V6A", na=False)) &
(df["YEAR_BUILT"] >= 1900)].copy()
print(f"V6A 区域筛选后的数据量: {len(df_v6a)}")
基于上面筛选得到的 df_v6a,请绘制一行两列的子图:
YEAR_BUILT,Y 轴为 HOUSE_PRICE;YEAR_BUILT,Y 轴为 HOUSE_PRICE。提示:当数据点很多时,散点图可能出现过度绘制(overplotting),六边形图可以更好地展示数据的密度分布。
# YOUR CODE HERE
raise NotImplementedError()
请写下你从上图中观察到的 两个 最有趣的发现。
发现
为了更精细地观察建造年份与房价的关系,请基于 df_v6a,以 YEAR_BUILT 作为分组依据,计算每年 HOUSE_PRICE 的第 25、50、75 百分位数。
请绘制一行三列的子图,每个子图是一个散点图:
YEAR_BUILT,Y = 第 25 百分位数的 HOUSE_PRICE;YEAR_BUILT,Y = 第 50 百分位数的 HOUSE_PRICE;YEAR_BUILT,Y = 第 75 百分位数的 HOUSE_PRICE。请将计算结果存入 DataFrame dfcor,包含列 YEAR_BUILT、25TH_HOUSE_PRICE、50TH_HOUSE_PRICE、75TH_HOUSE_PRICE。
# YOUR CODE HERE
raise NotImplementedError()
# ===== 任务 3.2 测试 =====
assert isinstance(dfcor, pd.DataFrame), "dfcor 应为 DataFrame"
required_cols = {"YEAR_BUILT", "25TH_HOUSE_PRICE", "50TH_HOUSE_PRICE", "75TH_HOUSE_PRICE"}
assert required_cols.issubset(set(dfcor.columns)), f"dfcor 缺少必要的列,当前列: {list(dfcor.columns)}"
assert len(dfcor) > 0, "dfcor 不能为空"
print("✅ 任务 3.2 测试通过!")
请写下你从上图中观察到的 两个 最有趣的发现。
发现
Pearson 相关系数衡量两个变量之间的线性相关程度,取值范围为 [-1, 1]。
请实现函数 calc_pearson(df, x, y),计算 DataFrame df 中列 x 和列 y 之间的 Pearson 相关系数。
注意: 不允许使用
pandas.DataFrame.corr()或scipy.stats.pearsonr()等现成函数,请从公式出发自行实现算法。
def calc_pearson(df, x, y):
# YOUR CODE HERE
raise NotImplementedError()
Spearman 等级相关系数衡量两个变量之间的单调关系(不要求线性),它是对两个变量的排名值计算 Pearson 相关系数。
请实现函数 calc_spearman(df, x, y),计算 DataFrame df 中列 x 和列 y 之间的 Spearman 等级相关系数。
实现步骤:
x 和 y 列的值分别转换为排名(rank);注意: 不允许使用
pandas.DataFrame.corr()或scipy.stats.spearmanr()等现成函数。可以使用pandas.Series.rank()来获取排名。
def calc_spearman(df, x, y):
# YOUR CODE HERE
raise NotImplementedError()
# ===== 任务 3.3-3.4 测试 =====
# 用简单数据验证
_test_df = pd.DataFrame({"a": [1, 2, 3, 4, 5], "b": [2, 4, 6, 8, 10]})
assert np.isclose(calc_pearson(_test_df, "a", "b"), 1.0), "完全线性正相关时 Pearson 应为 1.0"
_test_df2 = pd.DataFrame({"a": [1, 2, 3, 4, 5], "b": [5, 4, 3, 2, 1]})
assert np.isclose(calc_pearson(_test_df2, "a", "b"), -1.0), "完全线性负相关时 Pearson 应为 -1.0"
assert np.isclose(calc_spearman(_test_df2, "a", "b"), -1.0), "完全单调负相关时 Spearman 应为 -1.0"
_test_df3 = pd.DataFrame({"a": [1, 2, 3, 4, 5], "b": [1, 4, 9, 16, 25]})
_pearson_val = calc_pearson(_test_df3, "a", "b")
_spearman_val = calc_spearman(_test_df3, "a", "b")
assert np.isclose(_spearman_val, 1.0), "完全单调正相关时 Spearman 应为 1.0"
assert _pearson_val < 1.0, "非线性关系时 Pearson 应小于 1.0"
print("✅ 任务 3.3-3.4 测试通过!")
使用你实现的两个函数,分别对 dfcor 中的三对变量计算 Pearson 和 Spearman 相关系数:
YEAR_BUILT, 25TH_HOUSE_PRICE)YEAR_BUILT, 50TH_HOUSE_PRICE)YEAR_BUILT, 75TH_HOUSE_PRICE)for th in ["25TH", "50TH", "75TH"]:
col = th + "_HOUSE_PRICE"
p = calc_pearson(dfcor, "YEAR_BUILT", col)
s = calc_spearman(dfcor, "YEAR_BUILT", col)
print(f"{col}\t Pearson={p:.4f}\t Spearman={s:.4f}")
请写下你从上述结果中观察到的 两个 最有趣的发现。
发现
下面的代码加载抽样数据集,并筛选出 LEGAL_TYPE 为 STRATA(分层产权,通常对应公寓)的记录。
df_sample = pd.read_csv("Lab-data/property-tax-report_2021_sample.csv")
df_sample["CURRENT_PRICE"] = df_sample["CURRENT_LAND_VALUE"] + df_sample["CURRENT_IMPROVEMENT_VALUE"]
df_sample["PREVIOUS_PRICE"] = df_sample["PREVIOUS_LAND_VALUE"] + df_sample["PREVIOUS_IMPROVEMENT_VALUE"]
df_sample = df_sample[df_sample["LEGAL_TYPE"] == "STRATA"].copy()
print(f"样本数据量: {len(df_sample)}")
print(f"CURRENT_PRICE 中位数: {df_sample['CURRENT_PRICE'].median():,.0f}")
print(f"PREVIOUS_PRICE 中位数: {df_sample['PREVIOUS_PRICE'].median():,.0f}")
请分别计算 PREVIOUS_PRICE 和 CURRENT_PRICE 的中位数,并通过柱状图进行对比展示。
要求:
median_previous 和 median_current 中(原始数值,无需换算单位);# YOUR CODE HERE
raise NotImplementedError()
# ===== 任务 4.1 测试 =====
assert np.isclose(median_previous, df_sample["PREVIOUS_PRICE"].median()), "median_previous 计算有误"
assert np.isclose(median_current, df_sample["CURRENT_PRICE"].median()), "median_current 计算有误"
print("✅ 任务 4.1 测试通过!")
从上图中我们观察到样本中两年价格的中位数存在差异。但由于这些数字来自样本,"我们能信任这些数字吗?"
接下来,请实现自助法(Bootstrap),计算每个中位数的 95% 置信区间,并将置信区间以误差线(error bar)的形式添加到柱状图上。
参考资料:MIT 18.05 Bootstrap 教程 第 7 节给出了 Bootstrap 算法的详细描述。
Bootstrap 算法概要:
请实现函数 bootstrap_ci(data, num_bootstrap=5000, ci=95):
data:一维数组或 Series;num_bootstrap:重采样次数;ci:置信度(百分比);(lower_bound, upper_bound)。def bootstrap_ci(data, num_bootstrap=5000, ci=95):
# YOUR CODE HERE
raise NotImplementedError()
# ===== 任务 4.2 测试 =====
# 验证函数的基本行为
np.random.seed(0)
_test_data = np.random.normal(100, 10, 200)
_ci = bootstrap_ci(_test_data, num_bootstrap=2000, ci=95)
assert isinstance(_ci, tuple) and len(_ci) == 2, "bootstrap_ci 应返回一个长度为 2 的元组"
assert _ci[0] < _ci[1], "置信区间下界应小于上界"
assert _ci[0] < np.median(_test_data) < _ci[1], "中位数应在置信区间内"
# 验证置信度参数
np.random.seed(0)
_ci_90 = bootstrap_ci(_test_data, num_bootstrap=2000, ci=90)
assert (_ci_90[1] - _ci_90[0]) < (_ci[1] - _ci[0]), "90% 置信区间应比 95% 置信区间窄"
print("✅ 任务 4.2 测试通过!")
使用你实现的 bootstrap_ci 函数,分别计算 PREVIOUS_PRICE 和 CURRENT_PRICE 中位数的 95% 置信区间,并绘制带误差线的柱状图。
# 计算置信区间
np.random.seed(42) # 为了结果可复现
ci_previous = bootstrap_ci(df_sample["PREVIOUS_PRICE"].values)
ci_current = bootstrap_ci(df_sample["CURRENT_PRICE"].values)
print(f"PREVIOUS_PRICE 中位数 95% CI: [{ci_previous[0]:,.0f}, {ci_previous[1]:,.0f}]")
print(f"CURRENT_PRICE 中位数 95% CI: [{ci_current[0]:,.0f}, {ci_current[1]:,.0f}]")
# YOUR CODE HERE: 绘制带误差线的柱状图
raise NotImplementedError()
请写下你从 Bootstrap 结果中观察到的 两个 最有趣的发现。
发现
恭喜你完成了房产数据可视化与统计分析的全部实战任务!你已经从一份真实数据集出发,完整地走过了数据探索、可视化分析、相关性计算和统计推断的全过程。这些技能将在你后续的数据分析和科研工作中反复用到。
提交前最终检查:
Kernel → Restart & Run All,确保代码环境从头到尾运行顺畅无报错。