前言
房地產市場與金融信用評估是現代數據科學應用的兩個重要領域,它們之間存在著密切的關聯性。房地產作為多數家庭最大的資產項目,其價值波動直接影響個人財務狀況與信用表現。同時,地區人口結構、經濟發展水準與房屋供需狀況,都是評估貸款風險時不可忽視的重要因素。理解這些變數之間的關聯性,對於金融機構的風險管理與政策制定者的區域規劃都具有重要意義。
地區人口分布呈現出明顯的空間異質性。都市地區通常具有高人口密度、完善的基礎設施與多元的就業機會,吸引大量人口聚集。郊區則提供了都市便利性與居住品質的平衡選擇,近年來隨著遠距工作的普及,郊區化趨勢更加明顯。農村地區雖然人口密度較低,但擁有較大的土地面積與較低的生活成本,適合特定的生活型態與產業發展。這些地區特性不僅影響房地產市場的供需關係,更深刻地影響了居民的經濟狀況與信用表現。
房地產市場的價格形成機制複雜多變。除了傳統的供需法則,地理位置、交通便利性、學區品質、社區環境等因素都會影響房價。房屋本身的特性如坪數大小、房間數量、屋齡、建築品質等,同樣是價格的重要決定因素。更深層的影響來自總體經濟環境,包含利率水準、經濟成長率、就業市場狀況等,這些因素透過影響購屋需求與融資成本,間接影響房價走勢。
信用風險評估在金融業務中扮演關鍵角色。傳統的信用評估主要依賴借款人的收入水準、職業穩定性、過往信用記錄等個人因素。然而,現代的風險評估模型逐漸納入更多元的數據來源,包含居住地區的經濟指標、房地產市場狀況、社會人口統計資料等。這種多維度的評估方法能夠更全面地衡量借款風險,降低違約機率,同時也能識別出潛在的優質客戶。
機器學習技術為數據分析帶來了革命性的改變。相較於傳統的統計方法,機器學習模型能夠處理更大規模、更複雜的數據集,自動識別變數之間的非線性關係與交互作用。在房地產分析中,機器學習可以整合地理資訊、歷史交易數據、市場趨勢等多元資訊,提供更準確的價格預測。在信用評估中,機器學習能夠從大量歷史貸款數據中學習違約模式,建構更精準的風險預測模型。
本文將透過實際的數據分析案例,展示如何運用 Python 與相關數據科學工具,進行地區人口與房地產市場的深度分析。我們將從數據收集與預處理開始,透過描述性統計與視覺化方法探索數據特徵,進而建構預測模型評估房價與信用風險。透過這個完整的分析流程,我們期望為讀者提供實用的數據分析方法與工具,協助他們在實務工作中做出更明智的決策。
地區人口分布與房屋市場特徵分析
理解地區人口分布與房屋市場特徵是進行房地產分析的基礎。不同類型的地區展現出截然不同的人口結構、居住密度與房屋供需模式。都市地區通常是經濟活動的核心,提供豐富的就業機會與生活機能,但也面臨高房價與擁擠的挑戰。郊區地區在保持一定都市便利性的同時,提供更寬敞的居住空間與較佳的環境品質。農村地區則以低密度開發為特色,擁有大片土地資源,但相對缺乏都市的各項便利設施。
讓我們透過實際的數據分析來深入理解這些地區特徵。以下是一個完整的 Python 分析程式,展示如何處理與分析地區人口與房屋數據:
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
import seaborn as sns
from typing import Dict, List, Tuple
class RegionalAnalyzer:
"""
地區人口與房屋市場分析系統
提供跨區域比較、統計分析與視覺化功能
"""
def __init__(self):
"""
初始化分析系統
"""
self.data = None
self.regional_stats = {}
def load_regional_data(self) -> pd.DataFrame:
"""
載入地區人口與房屋數據
回傳:
包含地區資訊的 DataFrame
"""
# 建立範例數據集
# 包含地區編號、類型、人口數、房屋數量與經濟指標
data = {
'region_id': list(range(1, 31)),
'region_type': [
'Urban', 'Urban', 'Urban', 'Urban', 'Urban',
'Urban', 'Urban', 'Urban', 'Urban', 'Urban',
'Suburban', 'Suburban', 'Suburban', 'Suburban', 'Suburban',
'Suburban', 'Suburban', 'Suburban', 'Suburban', 'Suburban',
'Rural', 'Rural', 'Rural', 'Rural', 'Rural',
'Rural', 'Rural', 'Rural', 'Rural', 'Rural'
],
'population': [
3594, 1460, 1433, 688, 3959, 1254, 1467, 2915, 3806, 963,
2410, 3548, 3094, 1372, 1974, 3808, 2539, 2850, 3200, 2100,
739, 3449, 1876, 918, 521, 3515, 2566, 2873, 3219, 2400
],
'housing_units': [
150, 80, 120, 45, 180, 85, 95, 140, 175, 65,
110, 160, 145, 75, 95, 170, 115, 130, 155, 100,
45, 165, 90, 55, 35, 160, 125, 140, 155, 115
],
'economic_indicator': [
728714, 459552, 329009, 288231, 161456, 569017, 177585, 295847, 360656, 350000,
579725, 148109, 580000, 564935, 348077, 799788, 520000, 610000, 680000, 480000,
792533, 218434, 420000, 778767, 110147, 478076, 580335, 740139, 650000, 550000
]
}
self.data = pd.DataFrame(data)
# 計算衍生指標
self.data['population_density'] = self.data['population'] / self.data['housing_units']
self.data['economic_per_capita'] = self.data['economic_indicator'] / self.data['population']
return self.data
def calculate_regional_statistics(self) -> Dict:
"""
計算各類型地區的統計指標
回傳:
統計資訊字典
"""
if self.data is None:
raise ValueError("請先載入數據")
# 按地區類型分組計算統計指標
for region_type in self.data['region_type'].unique():
region_data = self.data[self.data['region_type'] == region_type]
self.regional_stats[region_type] = {
'count': len(region_data),
'population': {
'mean': region_data['population'].mean(),
'median': region_data['population'].median(),
'std': region_data['population'].std(),
'min': region_data['population'].min(),
'max': region_data['population'].max(),
},
'housing': {
'mean': region_data['housing_units'].mean(),
'median': region_data['housing_units'].median(),
'std': region_data['housing_units'].std(),
'total': region_data['housing_units'].sum(),
},
'density': {
'mean': region_data['population_density'].mean(),
'median': region_data['population_density'].median(),
},
'economic': {
'mean': region_data['economic_indicator'].mean(),
'per_capita_mean': region_data['economic_per_capita'].mean(),
}
}
return self.regional_stats
def compare_regions(self) -> pd.DataFrame:
"""
跨地區比較分析
回傳:
比較結果的 DataFrame
"""
if not self.regional_stats:
self.calculate_regional_statistics()
comparison = []
for region_type, stats in self.regional_stats.items():
comparison.append({
'地區類型': region_type,
'樣本數': stats['count'],
'平均人口': f"{stats['population']['mean']:.0f}",
'人口中位數': f"{stats['population']['median']:.0f}",
'平均房屋數': f"{stats['housing']['mean']:.1f}",
'人口密度': f"{stats['density']['mean']:.1f}",
'人均經濟指標': f"{stats['economic']['per_capita_mean']:.0f}",
})
return pd.DataFrame(comparison)
def perform_anova_test(self, variable: str) -> Dict:
"""
執行變異數分析 (ANOVA)
檢定不同地區類型在特定變數上是否有顯著差異
參數:
variable: 要檢定的變數名稱
回傳:
ANOVA 檢定結果
"""
if self.data is None:
raise ValueError("請先載入數據")
# 按地區類型分組數據
groups = [
self.data[self.data['region_type'] == region_type][variable].values
for region_type in self.data['region_type'].unique()
]
# 執行 ANOVA 檢定
f_statistic, p_value = stats.f_oneway(*groups)
# 解釋結果
is_significant = p_value < 0.05
interpretation = (
f"不同地區類型在 {variable} 上存在統計顯著差異"
if is_significant
else f"不同地區類型在 {variable} 上無統計顯著差異"
)
return {
'variable': variable,
'f_statistic': f_statistic,
'p_value': p_value,
'significant': is_significant,
'interpretation': interpretation
}
def analyze_correlations(self) -> pd.DataFrame:
"""
分析數值變數之間的相關性
回傳:
相關係數矩陣
"""
if self.data is None:
raise ValueError("請先載入數據")
# 選擇數值變數
numerical_vars = ['population', 'housing_units', 'economic_indicator',
'population_density', 'economic_per_capita']
# 計算相關係數矩陣
correlation_matrix = self.data[numerical_vars].corr()
return correlation_matrix
def identify_outliers(self, variable: str, method: str = 'iqr') -> pd.DataFrame:
"""
識別異常值
參數:
variable: 要檢測的變數名稱
method: 異常值檢測方法 ('iqr' 或 'zscore')
回傳:
包含異常值的 DataFrame
"""
if self.data is None:
raise ValueError("請先載入數據")
if method == 'iqr':
# 使用四分位距方法
Q1 = self.data[variable].quantile(0.25)
Q3 = self.data[variable].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = self.data[
(self.data[variable] < lower_bound) |
(self.data[variable] > upper_bound)
].copy()
elif method == 'zscore':
# 使用 Z 分數方法
z_scores = np.abs(stats.zscore(self.data[variable]))
outliers = self.data[z_scores > 3].copy()
else:
raise ValueError("method 必須是 'iqr' 或 'zscore'")
return outliers[['region_id', 'region_type', variable]]
def generate_summary_report(self) -> str:
"""
生成完整的分析報告
回傳:
格式化的報告文字
"""
if self.data is None:
raise ValueError("請先載入數據")
# 計算統計指標
self.calculate_regional_statistics()
report = []
report.append("=" * 70)
report.append("地區人口與房屋市場分析報告")
report.append("=" * 70)
report.append("")
# 總體概況
report.append("【總體概況】")
report.append(f"分析地區數: {len(self.data)}")
report.append(f"地區類型: {', '.join(self.data['region_type'].unique())}")
report.append(f"總人口數: {self.data['population'].sum():,}")
report.append(f"總房屋數: {self.data['housing_units'].sum():,}")
report.append(f"整體平均人口密度: {self.data['population_density'].mean():.2f} 人/單位")
report.append("")
# 各地區類型詳細分析
report.append("【各地區類型詳細分析】")
for region_type, stats in self.regional_stats.items():
report.append(f"\n{region_type} 地區:")
report.append(f" 樣本數: {stats['count']}")
report.append(f" 人口統計:")
report.append(f" 平均: {stats['population']['mean']:.0f}, "
f"中位數: {stats['population']['median']:.0f}, "
f"標準差: {stats['population']['std']:.0f}")
report.append(f" 範圍: {stats['population']['min']:.0f} - "
f"{stats['population']['max']:.0f}")
report.append(f" 房屋統計:")
report.append(f" 平均: {stats['housing']['mean']:.1f}, "
f"總數: {stats['housing']['total']}")
report.append(f" 人口密度: {stats['density']['mean']:.2f} 人/單位")
report.append(f" 經濟指標: {stats['economic']['mean']:,.0f}")
report.append(f" 人均經濟指標: {stats['economic']['per_capita_mean']:,.0f}")
report.append("")
# ANOVA 檢定結果
report.append("【統計檢定結果】")
for variable in ['population', 'housing_units', 'population_density']:
anova_result = self.perform_anova_test(variable)
report.append(f"\n{variable}:")
report.append(f" F 統計量: {anova_result['f_statistic']:.4f}")
report.append(f" p 值: {anova_result['p_value']:.4f}")
report.append(f" 結論: {anova_result['interpretation']}")
report.append("\n" + "=" * 70)
return "\n".join(report)
# 使用範例
if __name__ == '__main__':
# 建立分析器實例
analyzer = RegionalAnalyzer()
# 載入數據
print("載入地區數據...")
data = analyzer.load_regional_data()
print(f"成功載入 {len(data)} 個地區的資料\n")
# 顯示數據概覽
print("數據概覽 (前 10 筆):")
print(data.head(10).to_string(index=False))
print()
# 生成完整分析報告
report = analyzer.generate_summary_report()
print(report)
# 跨地區比較表格
print("\n\n跨地區比較表格:")
comparison_table = analyzer.compare_regions()
print(comparison_table.to_string(index=False))
# 相關性分析
print("\n\n變數相關性分析:")
correlation_matrix = analyzer.analyze_correlations()
print(correlation_matrix.round(3))
# 異常值檢測
print("\n\n人口數異常值檢測:")
outliers = analyzer.identify_outliers('population', method='iqr')
if len(outliers) > 0:
print(f"發現 {len(outliers)} 個異常值:")
print(outliers.to_string(index=False))
else:
print("未發現異常值")
這個完整的分析系統展示了如何系統化地處理地區人口與房屋數據。程式碼不僅計算基本的統計指標,更進行跨地區比較、變異數分析與異常值檢測。透過計算人口密度與人均經濟指標等衍生變數,我們能夠更深入地理解各地區的特徵。
ANOVA 檢定功能特別重要,它能夠告訴我們不同類型地區在某個變數上的差異是否具有統計顯著性。如果 p 值小於 0.05,我們可以有信心地說這些差異不是隨機產生的,而是反映了真實的地區特徵差異。這種統計推論能力讓我們的分析結論更加可靠。
以下的流程圖展示了地區數據分析的完整流程:
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
start
:收集地區數據\n人口與房屋資訊;
:數據預處理\n清理與驗證;
:計算衍生指標\n人口密度與人均指標;
partition "描述性統計" {
:計算集中趨勢\n平均數與中位數;
:計算離散程度\n標準差與範圍;
:識別異常值\nIQR 或 Z-score 方法;
}
partition "比較分析" {
:跨地區類型比較\n都市、郊區、農村;
:ANOVA 變異數分析\n檢定差異顯著性;
:事後檢定\n識別具體差異組別;
}
partition "關聯性分析" {
:計算相關係數\nPearson 相關;
:繪製散佈圖\n視覺化關聯;
:迴歸分析\n建立預測模型;
}
:生成分析報告\n含統計表格與圖表;
:提出政策建議\n基於數據洞察;
stop
note right
持續監測
定期更新分析
追蹤趨勢變化
end note
@enduml這個流程圖清楚呈現了從數據收集到政策建議的完整分析流程。分析過程被組織成三個主要階段:描述性統計提供數據的基本特徵,比較分析識別地區差異,關聯性分析揭示變數之間的關係。這種結構化的方法確保我們能夠全面且深入地理解地區特徵。
房地產市場價格影響因素分析
房地產價格的形成是一個複雜的過程,受到多重因素的交互影響。地理位置是最直觀的影響因素,同一棟房屋在都市核心區與郊區的價格可能相差數倍。交通便利性、學區品質、商業機能、治安狀況等區位因素,都會反映在房價上。這些因素的影響並非線性累加,而是相互作用形成綜效。
房屋本身的物理特性同樣至關重要。坪數大小直接決定了居住空間,通常與價格呈現正相關,但單位坪數的邊際價值會隨總坪數增加而遞減。房間與浴室數量反映了房屋的功能性,影響適居性與轉售價值。建築年齡與維護狀況影響房屋的實用價值與未來的維修成本。建材品質、格局設計、採光通風等細節,也會在價格上有所體現。
讓我們透過實際的房地產數據分析來理解這些影響因素:
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
from typing import Dict, List, Tuple
class HousingPriceAnalyzer:
"""
房地產價格分析系統
提供價格預測、影響因素分析與模型評估功能
"""
def __init__(self):
"""
初始化分析系統
"""
self.data = None
self.model = None
self.scaler = StandardScaler()
self.label_encoder = LabelEncoder()
def load_housing_data(self) -> pd.DataFrame:
"""
載入房地產數據
回傳:
包含房屋資訊的 DataFrame
"""
# 建立範例數據集
data = {
'property_id': list(range(1, 51)),
'location': ['Rural', 'Urban', 'Urban', 'Rural', 'Suburban',
'Suburban', 'Suburban', 'Suburban', 'Rural', 'Urban',
'Rural', 'Suburban', 'Urban', 'Rural', 'Suburban',
'Urban', 'Urban', 'Suburban', 'Rural', 'Urban'] * 2 +
['Suburban'] * 10,
'size_sqft': [
3505, 798, 1468, 2514, 2955, 3562, 2383, 2875, 1479, 1250,
3200, 2100, 1800, 2650, 2400, 1900, 2200, 2800, 3100, 1600,
2750, 1850, 2300, 2950, 2150, 1950, 2500, 2700, 3300, 1750,
2450, 2050, 2350, 2850, 2250, 2550, 2650, 2900, 3150, 1950,
2600, 2100, 2400, 2800, 2200, 2500, 2700, 2950, 3200, 1800
],
'bedrooms': [4, 1, 4, 4, 1, 1, 4, 2, 2, 3,
4, 3, 2, 4, 3, 2, 3, 4, 4, 2,
3, 2, 3, 4, 3, 2, 3, 4, 4, 2,
3, 2, 3, 4, 3, 3, 4, 4, 4, 2,
3, 2, 3, 4, 3, 3, 4, 4, 4, 2],
'bathrooms': [1, 2, 3, 1, 3, 1, 2, 3, 1, 2,
2, 2, 2, 2, 2, 2, 2, 3, 2, 2,
2, 2, 2, 3, 2, 2, 2, 3, 3, 2,
2, 2, 2, 3, 2, 2, 3, 3, 3, 2,
3, 2, 2, 3, 2, 3, 3, 3, 3, 2],
'sale_price': [
272004, 336871, 180710, 506003, 592832, 377111, 250240, 797726, 694440, 420000,
480000, 380000, 350000, 520000, 450000, 370000, 410000, 550000, 580000, 340000,
500000, 360000, 430000, 570000, 440000, 380000, 470000, 530000, 610000, 370000,
490000, 390000, 460000, 560000, 450000, 480000, 540000, 590000, 630000, 390000,
510000, 400000, 470000, 570000, 440000, 490000, 550000, 600000, 640000, 380000
]
}
self.data = pd.DataFrame(data)
# 計算衍生特徵
self.data['price_per_sqft'] = self.data['sale_price'] / self.data['size_sqft']
self.data['total_rooms'] = self.data['bedrooms'] + self.data['bathrooms']
return self.data
def analyze_by_location(self) -> pd.DataFrame:
"""
按地理位置分析房價
回傳:
各地區統計資訊的 DataFrame
"""
if self.data is None:
raise ValueError("請先載入數據")
# 按地區分組計算統計指標
location_stats = self.data.groupby('location').agg({
'sale_price': ['count', 'mean', 'median', 'std', 'min', 'max'],
'size_sqft': 'mean',
'price_per_sqft': 'mean',
'bedrooms': 'mean',
}).round(2)
# 重新命名欄位
location_stats.columns = ['數量', '平均售價', '中位數售價', '標準差',
'最低價', '最高價', '平均坪數', '單價', '平均房間數']
return location_stats
def prepare_features(self, include_location: bool = True) -> Tuple[np.ndarray, np.ndarray]:
"""
準備機器學習特徵
參數:
include_location: 是否包含地理位置特徵
回傳:
特徵矩陣與目標變數
"""
if self.data is None:
raise ValueError("請先載入數據")
# 選擇特徵欄位
feature_columns = ['size_sqft', 'bedrooms', 'bathrooms', 'total_rooms']
if include_location:
# 將類別變數編碼為數值
self.data['location_encoded'] = self.label_encoder.fit_transform(
self.data['location']
)
feature_columns.append('location_encoded')
# 準備特徵矩陣
X = self.data[feature_columns].values
# 準備目標變數
y = self.data['sale_price'].values
return X, y
def build_prediction_model(self, model_type: str = 'linear') -> Dict:
"""
建立價格預測模型
參數:
model_type: 模型類型 ('linear' 或 'random_forest')
回傳:
模型評估結果
"""
# 準備數據
X, y = self.prepare_features(include_location=True)
# 分割訓練集與測試集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 特徵標準化
X_train_scaled = self.scaler.fit_transform(X_train)
X_test_scaled = self.scaler.transform(X_test)
# 建立並訓練模型
if model_type == 'linear':
self.model = LinearRegression()
self.model.fit(X_train_scaled, y_train)
elif model_type == 'random_forest':
self.model = RandomForestRegressor(
n_estimators=100,
max_depth=10,
random_state=42
)
self.model.fit(X_train_scaled, y_train)
else:
raise ValueError("model_type 必須是 'linear' 或 'random_forest'")
# 進行預測
y_train_pred = self.model.predict(X_train_scaled)
y_test_pred = self.model.predict(X_test_scaled)
# 計算評估指標
results = {
'model_type': model_type,
'train_metrics': {
'r2_score': r2_score(y_train, y_train_pred),
'rmse': np.sqrt(mean_squared_error(y_train, y_train_pred)),
'mae': mean_absolute_error(y_train, y_train_pred),
},
'test_metrics': {
'r2_score': r2_score(y_test, y_test_pred),
'rmse': np.sqrt(mean_squared_error(y_test, y_test_pred)),
'mae': mean_absolute_error(y_test, y_test_pred),
}
}
# 如果是線性模型,提取特徵重要性
if model_type == 'linear' and hasattr(self.model, 'coef_'):
feature_names = ['坪數', '臥室數', '浴室數', '總房間數', '地理位置']
results['feature_importance'] = dict(zip(
feature_names,
self.model.coef_
))
# 如果是隨機森林,提取特徵重要性
if model_type == 'random_forest':
feature_names = ['坪數', '臥室數', '浴室數', '總房間數', '地理位置']
results['feature_importance'] = dict(zip(
feature_names,
self.model.feature_importances_
))
return results
def predict_price(self, size_sqft: float, bedrooms: int,
bathrooms: int, location: str) -> float:
"""
預測房屋價格
參數:
size_sqft: 房屋坪數
bedrooms: 臥室數
bathrooms: 浴室數
location: 地理位置
回傳:
預測價格
"""
if self.model is None:
raise ValueError("請先建立預測模型")
# 準備輸入特徵
total_rooms = bedrooms + bathrooms
location_encoded = self.label_encoder.transform([location])[0]
features = np.array([[size_sqft, bedrooms, bathrooms,
total_rooms, location_encoded]])
# 標準化特徵
features_scaled = self.scaler.transform(features)
# 進行預測
predicted_price = self.model.predict(features_scaled)[0]
return predicted_price
def generate_price_analysis_report(self) -> str:
"""
生成房價分析報告
回傳:
格式化的報告文字
"""
if self.data is None:
raise ValueError("請先載入數據")
report = []
report.append("=" * 70)
report.append("房地產價格分析報告")
report.append("=" * 70)
report.append("")
# 總體統計
report.append("【總體市場統計】")
report.append(f"樣本數: {len(self.data)}")
report.append(f"平均售價: ${self.data['sale_price'].mean():,.0f}")
report.append(f"中位數售價: ${self.data['sale_price'].median():,.0f}")
report.append(f"價格範圍: ${self.data['sale_price'].min():,.0f} - "
f"${self.data['sale_price'].max():,.0f}")
report.append(f"平均單價: ${self.data['price_per_sqft'].mean():.2f}/平方英尺")
report.append("")
# 地區分析
report.append("【地區別價格分析】")
location_stats = self.analyze_by_location()
report.append(location_stats.to_string())
report.append("")
# 建立預測模型
report.append("【預測模型評估】")
for model_type in ['linear', 'random_forest']:
results = self.build_prediction_model(model_type)
report.append(f"\n{model_type.upper()} 模型:")
report.append(f" 訓練集表現:")
report.append(f" R² 分數: {results['train_metrics']['r2_score']:.4f}")
report.append(f" RMSE: ${results['train_metrics']['rmse']:,.0f}")
report.append(f" MAE: ${results['train_metrics']['mae']:,.0f}")
report.append(f" 測試集表現:")
report.append(f" R² 分數: {results['test_metrics']['r2_score']:.4f}")
report.append(f" RMSE: ${results['test_metrics']['rmse']:,.0f}")
report.append(f" MAE: ${results['test_metrics']['mae']:,.0f}")
if 'feature_importance' in results:
report.append(f" 特徵重要性:")
for feature, importance in results['feature_importance'].items():
report.append(f" {feature}: {importance:.4f}")
report.append("\n" + "=" * 70)
return "\n".join(report)
# 使用範例
if __name__ == '__main__':
# 建立分析器實例
analyzer = HousingPriceAnalyzer()
# 載入數據
print("載入房地產數據...")
data = analyzer.load_housing_data()
print(f"成功載入 {len(data)} 筆房屋資料\n")
# 生成完整分析報告
report = analyzer.generate_price_analysis_report()
print(report)
# 價格預測範例
print("\n\n【價格預測範例】")
test_cases = [
(2500, 3, 2, 'Urban'),
(3000, 4, 3, 'Suburban'),
(2000, 3, 2, 'Rural'),
]
for size, beds, baths, location in test_cases:
predicted = analyzer.predict_price(size, beds, baths, location)
print(f"{location} 地區, {size} 平方英尺, {beds} 房 {baths} 衛")
print(f" 預測價格: ${predicted:,.0f}\n")
這個完整的房價分析系統展示了如何整合統計分析與機器學習技術。程式碼不僅提供描述性統計,更建立了實用的價格預測模型。透過比較線性迴歸與隨機森林兩種模型,我們能夠評估模型的適用性與預測準確度。
特徵重要性分析是理解價格影響因素的關鍵。在線性模型中,係數的大小與符號直接反映了各特徵對價格的影響方向與程度。在隨機森林模型中,特徵重要性則反映了該特徵在決策樹分裂過程中的貢獻程度。這些資訊能夠協助我們理解哪些因素最顯著地影響房價,為投資決策與政策制定提供依據。
以下的架構圖展示了房價預測模型的完整架構:
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
package "房價預測系統" {
[原始數據] as RAW_DATA
package "特徵工程" {
[數據清理] as CLEAN
[特徵選擇] as SELECT
[特徵轉換] as TRANSFORM
[特徵生成] as GENERATE
}
package "模型訓練" {
[訓練集] as TRAIN
[驗證集] as VALID
[測試集] as TEST
[模型選擇] as MODEL
}
package "評估與優化" {
[性能評估] as EVAL
[超參數調整] as TUNE
[模型選擇] as SELECT_MODEL
}
[最終模型] as FINAL
[價格預測] as PREDICT
}
RAW_DATA --> CLEAN
CLEAN --> SELECT
SELECT --> TRANSFORM
TRANSFORM --> GENERATE
GENERATE --> TRAIN
GENERATE --> VALID
GENERATE --> TEST
TRAIN --> MODEL
VALID --> MODEL
MODEL --> EVAL
EVAL --> TUNE
TUNE --> SELECT_MODEL
SELECT_MODEL --> FINAL
FINAL --> PREDICT
TEST --> PREDICT
note right of GENERATE
衍生特徵:
- 單價
- 總房間數
- 地區編碼
end note
note right of MODEL
模型類型:
- 線性迴歸
- 隨機森林
- 梯度提升
end note
@enduml這個架構圖清楚呈現了從原始數據到最終預測的完整流程。特徵工程階段處理數據品質問題並生成有用的衍生特徵。模型訓練階段使用訓練集訓練模型,使用驗證集調整參數。評估與優化階段確保模型具有良好的泛化能力。最終模型在測試集上評估,確認其預測準確度後投入實際應用。
信用風險評估模型建構與應用
信用風險評估是金融機構核心的風險管理活動,直接影響貸款決策的品質與機構的財務健全性。準確的信用評估能夠識別出優質借款人,在控制風險的同時擴大業務規模。相反地,不準確的評估可能導致大量違約損失,危及機構的生存。傳統的信用評估主要依賴專家經驗與簡單的評分卡模型,而現代的機器學習方法能夠處理更複雜的變數關係,提供更精準的風險預測。
信用風險評估涉及多個維度的資訊整合。借款人的財務狀況是最直接的考量因素,包含收入水準、資產負債比、現金流穩定性等。就業狀況反映了未來收入的穩定性,不同職業與產業的風險特徵差異顯著。信用歷史記錄展現了借款人過去的還款行為,是預測未來表現的重要依據。
房地產因素在信用評估中扮演重要角色。對於房貸申請,房屋價值與貸款金額的比率是關鍵指標。房屋所在地區的經濟狀況、房價趨勢也會影響違約風險。如果借款人居住在經濟繁榮、房價穩定上漲的地區,其違約風險通常較低。相反地,經濟衰退地區的借款人面臨更高的失業風險與房價下跌風險,違約機率相對較高。
以下是一個整合人口統計、房地產與財務資訊的信用風險評估系統:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import (classification_report, confusion_matrix,
roc_auc_score, roc_curve)
from typing import Dict, List, Tuple
class CreditRiskAssessor:
"""
信用風險評估系統
整合多維度資訊進行貸款風險預測
"""
def __init__(self):
"""
初始化評估系統
"""
self.data = None
self.model = None
self.scaler = StandardScaler()
self.label_encoders = {}
def load_credit_data(self) -> pd.DataFrame:
"""
載入信用評估數據
回傳:
包含申請人資訊的 DataFrame
"""
# 建立範例數據集
np.random.seed(42)
n_samples = 500
data = {
'applicant_id': list(range(1, n_samples + 1)),
'credit_score': np.random.randint(300, 850, n_samples),
'annual_income': np.random.randint(30000, 150000, n_samples),
'loan_amount': np.random.randint(50000, 500000, n_samples),
'employment_length': np.random.randint(0, 30, n_samples),
'region_type': np.random.choice(['Urban', 'Suburban', 'Rural'], n_samples),
'home_ownership': np.random.choice(['Own', 'Rent', 'Mortgage'], n_samples),
'property_value': np.random.randint(100000, 1000000, n_samples),
}
df = pd.DataFrame(data)
# 計算衍生特徵
df['debt_to_income'] = df['loan_amount'] / df['annual_income']
df['loan_to_value'] = df['loan_amount'] / df['property_value']
df['income_stability'] = df['employment_length'] / df['employment_length'].max()
# 生成目標變數 (違約與否)
# 基於多個因素的邏輯規則
risk_score = (
(df['credit_score'] < 600) * 0.3 +
(df['debt_to_income'] > 0.43) * 0.25 +
(df['loan_to_value'] > 0.8) * 0.25 +
(df['employment_length'] < 2) * 0.2
)
# 添加隨機性
random_factor = np.random.random(n_samples) * 0.3
final_risk = risk_score + random_factor
df['default'] = (final_risk > 0.5).astype(int)
self.data = df
return self.data
def analyze_default_patterns(self) -> Dict:
"""
分析違約模式
回傳:
違約分析結果
"""
if self.data is None:
raise ValueError("請先載入數據")
# 計算違約率
default_rate = self.data['default'].mean()
# 按地區類型分析違約率
default_by_region = self.data.groupby('region_type')['default'].agg([
'count', 'sum', 'mean'
])
default_by_region.columns = ['總數', '違約數', '違約率']
# 按房屋所有權狀態分析違約率
default_by_ownership = self.data.groupby('home_ownership')['default'].agg([
'count', 'sum', 'mean'
])
default_by_ownership.columns = ['總數', '違約數', '違約率']
# 分析關鍵指標的差異
feature_comparison = {}
for feature in ['credit_score', 'debt_to_income', 'loan_to_value']:
feature_comparison[feature] = {
'非違約平均': self.data[self.data['default'] == 0][feature].mean(),
'違約平均': self.data[self.data['default'] == 1][feature].mean(),
'差異': (self.data[self.data['default'] == 1][feature].mean() -
self.data[self.data['default'] == 0][feature].mean())
}
return {
'overall_default_rate': default_rate,
'default_by_region': default_by_region,
'default_by_ownership': default_by_ownership,
'feature_comparison': feature_comparison
}
def prepare_features_for_modeling(self) -> Tuple[np.ndarray, np.ndarray]:
"""
準備建模特徵
回傳:
特徵矩陣與目標變數
"""
if self.data is None:
raise ValueError("請先載入數據")
# 選擇數值特徵
numerical_features = [
'credit_score', 'annual_income', 'loan_amount',
'employment_length', 'property_value',
'debt_to_income', 'loan_to_value', 'income_stability'
]
# 類別特徵編碼
categorical_features = ['region_type', 'home_ownership']
# 複製數據避免修改原始數據
df_encoded = self.data.copy()
for feature in categorical_features:
if feature not in self.label_encoders:
self.label_encoders[feature] = LabelEncoder()
df_encoded[feature + '_encoded'] = self.label_encoders[feature].fit_transform(
df_encoded[feature]
)
else:
df_encoded[feature + '_encoded'] = self.label_encoders[feature].transform(
df_encoded[feature]
)
# 組合所有特徵
all_features = numerical_features + [f + '_encoded' for f in categorical_features]
X = df_encoded[all_features].values
y = df_encoded['default'].values
return X, y
def build_risk_model(self, model_type: str = 'logistic') -> Dict:
"""
建立風險預測模型
參數:
model_type: 模型類型
回傳:
模型評估結果
"""
# 準備數據
X, y = self.prepare_features_for_modeling()
# 分割數據
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 特徵標準化
X_train_scaled = self.scaler.fit_transform(X_train)
X_test_scaled = self.scaler.transform(X_test)
# 選擇並訓練模型
if model_type == 'logistic':
self.model = LogisticRegression(random_state=42, max_iter=1000)
elif model_type == 'random_forest':
self.model = RandomForestClassifier(
n_estimators=100,
max_depth=10,
random_state=42
)
elif model_type == 'gradient_boosting':
self.model = GradientBoostingClassifier(
n_estimators=100,
max_depth=5,
random_state=42
)
else:
raise ValueError("不支援的模型類型")
# 訓練模型
self.model.fit(X_train_scaled, y_train)
# 預測
y_train_pred = self.model.predict(X_train_scaled)
y_test_pred = self.model.predict(X_test_scaled)
# 預測機率
y_train_proba = self.model.predict_proba(X_train_scaled)[:, 1]
y_test_proba = self.model.predict_proba(X_test_scaled)[:, 1]
# 評估結果
results = {
'model_type': model_type,
'train_metrics': {
'accuracy': (y_train_pred == y_train).mean(),
'auc': roc_auc_score(y_train, y_train_proba),
},
'test_metrics': {
'accuracy': (y_test_pred == y_test).mean(),
'auc': roc_auc_score(y_test, y_test_proba),
'confusion_matrix': confusion_matrix(y_test, y_test_pred),
'classification_report': classification_report(y_test, y_test_pred),
}
}
# 交叉驗證
cv_scores = cross_val_score(
self.model, X_train_scaled, y_train,
cv=5, scoring='roc_auc'
)
results['cross_validation'] = {
'mean_auc': cv_scores.mean(),
'std_auc': cv_scores.std(),
}
return results
def assess_applicant_risk(self, applicant_data: Dict) -> Dict:
"""
評估個別申請人風險
參數:
applicant_data: 申請人資訊字典
回傳:
風險評估結果
"""
if self.model is None:
raise ValueError("請先建立預測模型")
# 準備特徵
features = {
'credit_score': applicant_data['credit_score'],
'annual_income': applicant_data['annual_income'],
'loan_amount': applicant_data['loan_amount'],
'employment_length': applicant_data['employment_length'],
'property_value': applicant_data['property_value'],
'debt_to_income': applicant_data['loan_amount'] / applicant_data['annual_income'],
'loan_to_value': applicant_data['loan_amount'] / applicant_data['property_value'],
'income_stability': applicant_data['employment_length'] / 30,
'region_type_encoded': self.label_encoders['region_type'].transform(
[applicant_data['region_type']]
)[0],
'home_ownership_encoded': self.label_encoders['home_ownership'].transform(
[applicant_data['home_ownership']]
)[0],
}
# 轉換為數組
feature_array = np.array([list(features.values())])
# 標準化
feature_scaled = self.scaler.transform(feature_array)
# 預測
default_probability = self.model.predict_proba(feature_scaled)[0, 1]
prediction = self.model.predict(feature_scaled)[0]
# 風險等級判定
if default_probability < 0.3:
risk_level = '低風險'
recommendation = '建議核准'
elif default_probability < 0.6:
risk_level = '中風險'
recommendation = '建議進一步審查'
else:
risk_level = '高風險'
recommendation = '建議拒絕'
return {
'default_probability': default_probability,
'prediction': '違約' if prediction == 1 else '正常',
'risk_level': risk_level,
'recommendation': recommendation,
'key_factors': {
'信用分數': applicant_data['credit_score'],
'負債收入比': features['debt_to_income'],
'貸款成數': features['loan_to_value'],
}
}
def generate_risk_report(self) -> str:
"""
生成風險評估報告
回傳:
格式化的報告文字
"""
if self.data is None:
raise ValueError("請先載入數據")
# 分析違約模式
patterns = self.analyze_default_patterns()
report = []
report.append("=" * 70)
report.append("信用風險評估分析報告")
report.append("=" * 70)
report.append("")
# 總體統計
report.append("【總體風險概況】")
report.append(f"總申請數: {len(self.data)}")
report.append(f"整體違約率: {patterns['overall_default_rate']:.2%}")
report.append(f"違約案件數: {self.data['default'].sum()}")
report.append("")
# 地區分析
report.append("【地區別違約分析】")
report.append(patterns['default_by_region'].to_string())
report.append("")
# 房屋所有權分析
report.append("【房屋所有權別違約分析】")
report.append(patterns['default_by_ownership'].to_string())
report.append("")
# 關鍵指標比較
report.append("【關鍵指標比較分析】")
for feature, comparison in patterns['feature_comparison'].items():
report.append(f"\n{feature}:")
report.append(f" 非違約平均: {comparison['非違約平均']:.4f}")
report.append(f" 違約平均: {comparison['違約平均']:.4f}")
report.append(f" 差異: {comparison['差異']:.4f}")
report.append("")
# 模型評估
report.append("【預測模型評估】")
for model_type in ['logistic', 'random_forest', 'gradient_boosting']:
results = self.build_risk_model(model_type)
report.append(f"\n{model_type.upper()} 模型:")
report.append(f" 訓練集 AUC: {results['train_metrics']['auc']:.4f}")
report.append(f" 測試集 AUC: {results['test_metrics']['auc']:.4f}")
report.append(f" 測試集準確率: {results['test_metrics']['accuracy']:.4f}")
report.append(f" 交叉驗證 AUC: {results['cross_validation']['mean_auc']:.4f} "
f"(± {results['cross_validation']['std_auc']:.4f})")
report.append("\n" + "=" * 70)
return "\n".join(report)
# 使用範例
if __name__ == '__main__':
# 建立評估系統
assessor = CreditRiskAssessor()
# 載入數據
print("載入信用評估數據...")
data = assessor.load_credit_data()
print(f"成功載入 {len(data)} 筆申請記錄\n")
# 生成完整報告
report = assessor.generate_risk_report()
print(report)
# 個別申請人評估範例
print("\n\n【個別申請人風險評估】")
test_applicants = [
{
'credit_score': 750,
'annual_income': 80000,
'loan_amount': 250000,
'employment_length': 10,
'region_type': 'Urban',
'home_ownership': 'Own',
'property_value': 350000,
},
{
'credit_score': 550,
'annual_income': 45000,
'loan_amount': 200000,
'employment_length': 1,
'region_type': 'Rural',
'home_ownership': 'Rent',
'property_value': 220000,
}
]
for i, applicant in enumerate(test_applicants, 1):
print(f"\n申請人 {i}:")
risk_assessment = assessor.assess_applicant_risk(applicant)
print(f" 違約機率: {risk_assessment['default_probability']:.2%}")
print(f" 風險等級: {risk_assessment['risk_level']}")
print(f" 審核建議: {risk_assessment['recommendation']}")
print(f" 關鍵因素:")
for factor, value in risk_assessment['key_factors'].items():
print(f" {factor}: {value:.4f}")
這個完整的信用風險評估系統整合了多個維度的資訊,建立了實用的風險預測模型。程式碼不僅分析違約模式,更比較了多種機器學習模型的預測效能。透過交叉驗證,我們能夠評估模型的穩定性與泛化能力。
個別申請人風險評估功能展示了模型的實際應用價值。系統不僅提供違約機率的數值預測,更將其轉換為可理解的風險等級與審核建議。這種人性化的輸出格式讓非技術背景的審核人員也能理解評估結果,做出適當的決策。
結論
本文透過完整的數據分析流程,展示了如何運用 Python 與機器學習技術,進行地區人口、房地產市場與信用風險的深度分析。從數據收集與預處理,到描述性統計與視覺化,再到預測模型的建立與評估,我們涵蓋了數據科學項目的完整生命週期。
地區分析揭示了都市、郊區與農村在人口密度、房屋供需與經濟發展上的顯著差異。這些差異不僅反映了不同地區的發展特徵,更影響著房地產市場的價格形成機制。透過 ANOVA 檢定等統計方法,我們確認了這些差異的顯著性,為後續的建模工作提供了堅實基礎。
房價分析展示了機器學習在價格預測中的應用價值。透過整合地理位置、房屋特徵與市場資訊,我們建立了能夠準確預測房價的模型。特徵重要性分析揭示了坪數、地理位置與房間配置對價格的相對影響,這些洞察對於買賣雙方的決策都具有參考價值。
信用風險評估則展示了數據科學在金融領域的應用。透過整合個人財務資訊、就業狀況與房地產因素,我們建立了全面的風險評估框架。模型不僅提供準確的違約預測,更能識別關鍵的風險因素,協助金融機構優化審核流程,在控制風險的同時擴大業務規模。
然而,我們也必須認識到數據分析的局限性。模型的預測能力受限於訓練數據的品質與代表性。如果訓練數據存在偏差,模型可能會複製並放大這些偏差。外部環境的變化,如經濟衰退或政策調整,可能改變變數之間的關係,降低模型的預測準確度。因此,模型需要定期更新與驗證,確保其持續有效。
展望未來,隨著數據來源的多元化與計算能力的提升,我們能夠建構更複雜、更準確的預測模型。整合地理資訊系統、社交網路數據、即時市場資訊等多元數據源,將提供更全面的分析視角。深度學習技術在處理非結構化數據方面的優勢,可能為房地產影像分析、文字評論情感分析等領域帶來突破。人工智慧與人類專家的協作,將是未來數據科學應用的重要方向,結合機器的計算能力與人類的判斷智慧,創造更大的價值。