学习笔记(34):matplotlib绘制图表-房价数据分析与可视化
分析房价分布情况,通过直方图、核密度估计和正态分布拟合来直观展示房价的分布特征,并进行统计检验。
一、房价数据分析与可视化,代码分析
1.1、导入必要的库
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy import stats
import os
- 导入数据处理 (pandas)、绘图 (matplotlib, seaborn)库
- 导入数学计算 (numpy, scipy) 和文件操作 (os) 库
1.2、设置中文字体和负号显示
# 设置 Windows 系统的中文字体
plt.rcParams["font.family"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
- 设置了适用于 Windows 系统的中文字体,确保图表中的中文能正常显示
- 解决了负号显示为方块的问题
1.3、数据加载函数 load_data()
def load_data(file_path):
"""加载房价数据"""
try:
# 尝试读取CSV文件
data = pd.read_csv(file_path)
print(f"数据加载成功,共{data.shape[0]}条记录,{data.shape[1]}个特征")
print(f"数据特征: {', '.join(data.columns.tolist())}")
return data
except FileNotFoundError:
print(f"错误: 文件 '{file_path}' 不存在")
# 创建示例数据用于演示
print("创建示例数据用于演示...")
np.random.seed(42)
size = 500
data = pd.DataFrame({
'price': np.random.normal(15000, 3000, size), # 房价,单位:万元
'area': np.random.normal(100, 20, size), # 面积,单位:平方米
'age': np.random.randint(1, 30, size), # 房龄,单位:年
})
# 确保房价与面积正相关,与房龄负相关
data['price'] = data['price'] + 50 * data['area'] - 100 * data['age']
data['price'] = data['price'].clip(lower=5000) # 设置价格下限
return data
- 尝试从指定路径加载 CSV 文件
- 如果文件不存在,会生成模拟数据:
- 使用正态分布生成房价、面积数据
- 使用均匀分布生成房龄数据
- 通过公式
price = base_price + 50*area - 100*age
确保房价与面积正相关,与房龄负相关 - 设置房价下限为 5000 万元
数据house_prices.csv
area,price,age,bedrooms
120,15000,10,3
140,18000,5,4
90,12000,15,2
160,20000,8,3
100,13000,12,2
92,12000,15,2
162,20000,8,3
102,13000,12,2
91,12000,15,2
161,20000,8,3
101,12000,12,2
121,13000,10,3
142,16000,5,4
122,13000,10,3
142,15000,5,4
123,17000,10,3
144,17000,5,4
124,17000,10,3
144,17000,5,4
125,18000,10,3
145,18000,5,4
1.4、房价分布可视化函数 plot_price_distribution()
def plot_price_distribution(data, price_col='price'):
"""绘制房价分布直方图"""
plt.figure(figsize=(10, 6))
# 绘制直方图和核密度估计
sns.histplot(data[price_col], kde=True, bins=30, color='skyblue')
# 添加均值和中位数线
mean_val = data[price_col].mean()
median_val = data[price_col].median()
plt.axvline(mean_val, color='red', linestyle='dashed', linewidth=2, label=f'均值: {mean_val:.2f}')
plt.axvline(median_val, color='green', linestyle='dashed', linewidth=2, label=f'中位数: {median_val:.2f}')
# 添加正态分布拟合曲线
mu, sigma = stats.norm.fit(data[price_col])
x = np.linspace(data[price_col].min(), data[price_col].max(), 100)
plt.plot(x, stats.norm.pdf(x, mu, sigma) * len(data) * (x.max() - x.min()) / 100,
'r--', linewidth=2, label=f'正态分布拟合: μ={mu:.2f}, σ={sigma:.2f}')
plt.title('房价分布直方图')
plt.xlabel('房价 (万元)')
plt.ylabel('频数')
plt.legend()
plt.grid(axis='y', alpha=0.5)
plt.tight_layout()
# 保存图像
if not os.path.exists('plots'):
os.makedirs('plots')
plt.savefig('plots/price_distribution.png', dpi=300)
plt.show()
# 打印统计信息
print("\n房价统计信息:")
print(data[price_col].describe())
# 检验正态性
stat, p = stats.normaltest(data[price_col])
print(f"\n正态性检验 (p值): {p:.4f}")
if p < 0.05:
print("房价分布显著偏离正态分布")
else:
print("房价分布近似正态分布")
- 创建 10x6 英寸的图表
- 使用 seaborn 绘制直方图和核密度估计曲线
- 添加均值 (红色虚线) 和中位数 (绿色虚线) 参考线
- 拟合正态分布曲线并绘制 (红色虚线)
- 设置图表标题、轴标签,添加图例和网格线
- 将图表保存到 plots 文件夹,并显示图表
- 打印房价的描述性统计信息 (计数、均值、标准差等)
- 使用
stats.normaltest
进行正态性检验并输出结果
1.5、主函数 main()
def main():
"""主函数:执行数据加载和价格分布分析"""
file_path = '../../data/house_prices.csv' # 替换为实际文件路径
# 1. 加载数据
data = load_data(file_path)
# 2. 绘制房价分布直方图
plot_price_distribution(data)
print("\n数据分析完成!图表已保存到 'plots' 文件夹")
- 设置数据文件路径
- 调用
load_data()
加载数据 - 调用
plot_price_distribution()
分析并可视化房价分布 - 打印分析完成信息
1.6、程序入口
if __name__ == "__main__":
main()
- 确保程序作为脚本直接运行时才执行
main()
函数 - 如果作为模块导入,则不会执行
代码优化建议
- 添加更多错误处理,如处理空数据的情况
- 可以将图表保存路径作为参数传入
- 正态分布曲线的高度计算可以更精确
- 可以添加更多的房价分析维度,如不同房龄、面积段的价格分布
二、代码和执行结果
2.1、代码
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy import stats
import os
# 设置 Windows 系统的中文字体
plt.rcParams["font.family"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
def load_data(file_path):
"""加载房价数据"""
try:
# 尝试读取CSV文件
data = pd.read_csv(file_path)
print(f"数据加载成功,共{data.shape[0]}条记录,{data.shape[1]}个特征")
print(f"数据特征: {', '.join(data.columns.tolist())}")
return data
except FileNotFoundError:
print(f"错误: 文件 '{file_path}' 不存在")
# 创建示例数据用于演示
print("创建示例数据用于演示...")
np.random.seed(42)
size = 500
data = pd.DataFrame({
'price': np.random.normal(15000, 3000, size), # 房价,单位:万元
'area': np.random.normal(100, 20, size), # 面积,单位:平方米
'age': np.random.randint(1, 30, size), # 房龄,单位:年
})
# 确保房价与面积正相关,与房龄负相关
data['price'] = data['price'] + 50 * data['area'] - 100 * data['age']
data['price'] = data['price'].clip(lower=5000) # 设置价格下限
return data
def plot_price_distribution(data, price_col='price'):
"""绘制房价分布直方图"""
plt.figure(figsize=(10, 6))
# 绘制直方图和核密度估计
sns.histplot(data[price_col], kde=True, bins=30, color='skyblue')
# 添加均值和中位数线
mean_val = data[price_col].mean()
median_val = data[price_col].median()
plt.axvline(mean_val, color='red', linestyle='dashed', linewidth=2, label=f'均值: {mean_val:.2f}')
plt.axvline(median_val, color='green', linestyle='dashed', linewidth=2, label=f'中位数: {median_val:.2f}')
# 添加正态分布拟合曲线
mu, sigma = stats.norm.fit(data[price_col])
x = np.linspace(data[price_col].min(), data[price_col].max(), 100)
plt.plot(x, stats.norm.pdf(x, mu, sigma) * len(data) * (x.max() - x.min()) / 100,
'r--', linewidth=2, label=f'正态分布拟合: μ={mu:.2f}, σ={sigma:.2f}')
plt.title('房价分布直方图')
plt.xlabel('房价 (万元)')
plt.ylabel('频数')
plt.legend()
plt.grid(axis='y', alpha=0.5)
plt.tight_layout()
# 保存图像
if not os.path.exists('plots'):
os.makedirs('plots')
plt.savefig('plots/price_distribution.png', dpi=300)
plt.show()
# 打印统计信息
print("\n房价统计信息:")
print(data[price_col].describe())
# 检验正态性
stat, p = stats.normaltest(data[price_col])
print(f"\n正态性检验 (p值): {p:.4f}")
if p < 0.05:
print("房价分布显著偏离正态分布")
else:
print("房价分布近似正态分布")
def main():
"""主函数:执行数据加载和价格分布分析"""
file_path = '../../data/house_prices.csv' # 替换为实际文件路径
# 1. 加载数据
data = load_data(file_path)
# 2. 绘制房价分布直方图
plot_price_distribution(data)
print("\n数据分析完成!图表已保存到 'plots' 文件夹")
if __name__ == "__main__":
main()
2.2、执行结果
数据加载成功,共21条记录,4个特征
数据特征: area, price, age, bedrooms房价统计信息:
count 21.000000
mean 15619.047619
std 2854.403449
min 12000.000000
25% 13000.000000
50% 16000.000000
75% 18000.000000
max 20000.000000
Name: price, dtype: float64正态性检验 (p值): 0.0725
房价分布近似正态分布数据分析完成!图表已保存到 'plots' 文件夹
三、1.4中的部分详解
1.4.1、正态分布拟合曲线绘制代码详解
mu, sigma = stats.norm.fit(data[price_col])
x = np.linspace(data[price_col].min(), data[price_col].max(), 100)
plt.plot(x, stats.norm.pdf(x, mu, sigma) * len(data) * (x.max() - x.min()) / 100,
'r--', linewidth=2, label=f'正态分布拟合: μ={mu:.2f}, σ={sigma:.2f}')
1. 计算正态分布参数
mu, sigma = stats.norm.fit(data[price_col])
stats.norm.fit()
是 SciPy 库中用于拟合正态分布的函数- 它使用最大似然估计方法,根据输入数据计算最匹配的正态分布参数
- 返回两个值:
mu
:正态分布的均值(位置参数)sigma
:正态分布的标准差(尺度参数)
2. 生成曲线绘制的 x 坐标
x = np.linspace(data[price_col].min(), data[price_col].max(), 100)
np.linspace()
在房价数据的最小值和最大值之间生成 100 个均匀分布的点- 这 100 个点将作为曲线的 x 坐标,确保曲线覆盖整个数据范围
- 例如,如果房价最小值是 5000,最大值是 25000,则会生成从 5000 到 25000 的 100 个点
3. 计算正态分布曲线的 y 坐标(核心难点)
stats.norm.pdf(x, mu, sigma) * len(data) * (x.max() - x.min()) / 100
这部分代码可以分解为三个关键部分:
3.1 计算理论概率密度值
stats.norm.pdf(x, mu, sigma)
stats.norm.pdf()
计算正态分布的概率密度函数 (Probability Density Function, PDF)- 输入参数:
x
:前面生成的 100 个房价坐标点mu
和sigma
:前面拟合得到的正态分布参数
- 输出:每个 x 点对应的正态分布概率密度值
3.2 缩放因子 - 样本量调整
* len(data)
- 乘以样本数量(数据行数)
- 这一步将概率密度转换为理论频数
- 例如,如果某个房价区间的理论概率是 0.05,样本量是 500,则理论频数是 0.05 * 500 = 25
3.3 缩放因子 - 区间宽度调整
* (x.max() - x.min()) / 100
(x.max() - x.min()) / 100
计算每个区间的宽度- 这一步调整曲线高度以匹配直方图的区间宽度
- 例如,如果房价范围是 20000(25000-5000),分成 100 个区间,则每个区间宽度是 200
4. 绘制正态分布曲线
plt.plot(x, y, 'r--', linewidth=2, label=f'正态分布拟合: μ={mu:.2f}, σ={sigma:.2f}')
- 使用 matplotlib 的 plot 函数绘制曲线
- 参数说明:
x
:横坐标(房价值)y
:纵坐标(调整后的理论频数)'r--'
:红色虚线linewidth=2
:线宽 2label
:图例标签,显示拟合的正态分布参数(保留两位小数)
为什么需要这些缩放因子?
正态分布的概率密度函数 (PDF) 返回的是概率密度值,范围通常很小(例如 0-0.0001),直接绘制会与直方图的高度不匹配。通过乘以样本量和区间宽度,可以将理论概率密度转换为与直方图可比的理论频数,使曲线与直方图在同一尺度上显示,便于直观比较数据分布与正态分布的拟合程度。
示例说明
假设:
- 房价数据范围:5000-25000 万元
- 样本量:500 条
- 拟合的正态分布参数:μ=15000, σ=3000
- 某点 x=15000 处的概率密度值:stats.norm.pdf (15000, 15000, 3000) ≈ 0.000133
经过缩放计算:
0.000133 * 500 * (25000-5000)/100 ≈ 0.000133 * 500 * 200 ≈ 13.3
这意味着在 x=15000 处,理论上该区间的频数约为 13.3,这个值与直方图在该区间的高度可比。
常见问题与优化
- 如果直方图的 bins 数量不是 100,需要相应调整缩放因子中的分母
- 对于偏态分布,正态拟合可能不佳,可以考虑使用其他分布(如对数正态、伽马分布等)
- 可以添加判断逻辑,根据 bins 数量自动计算缩放因子,提高代码通用性