學習筆記(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 萬元
數據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 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 數量自動計算縮放因子,提高代碼通用性