学习笔记(34):matplotlib绘制图表-房价数据分析与可视化
学习笔记(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 dataexcept FileNotFoundError:print(f"错误: 文件 '{file_path}' 不存在")# 创建示例数据用于演示print("创建示例数据用于演示...")np.random.seed(42)size = 500data = 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 万元
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 dataexcept FileNotFoundError:print(f"错误: 文件 '{file_path}' 不存在")# 创建示例数据用于演示print("创建示例数据用于演示...")np.random.seed(42)size = 500data = 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 datadef 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 数量自动计算缩放因子,提高代码通用性