Python案例如何用Scikit-learn做协方差估计

wen python案例 1

本文目录导读:

Python案例如何用Scikit-learn做协方差估计

  1. 基础协方差估计
  2. 稳健协方差估计
  3. 收缩协方差估计
  4. 实际案例:金融数据协方差分析
  5. 异常检测应用
  6. 性能评估与选择
  7. 关键要点

我来详细讲解如何使用Scikit-learn进行协方差估计,包含多个实际案例。

基础协方差估计

import numpy as np
import pandas as pd
from sklearn.covariance import EmpiricalCovariance
import matplotlib.pyplot as plt
import seaborn as sns
# 生成示例数据
np.random.seed(42)
n_samples = 100
n_features = 3
# 创建具有相关性的数据
mean = [0, 0, 0]
cov = [[1, 0.8, 0.3],
       [0.8, 1, 0.2],
       [0.3, 0.2, 1]]
X = np.random.multivariate_normal(mean, cov, n_samples)
df = pd.DataFrame(X, columns=['特征1', '特征2', '特征3'])
# 基本协方差估计
cov_estimator = EmpiricalCovariance().fit(X)
cov_matrix = cov_estimator.covariance_
print("协方差矩阵:")
print(np.round(cov_matrix, 3))

稳健协方差估计

from sklearn.covariance import MinCovDet, EllipticEnvelope
# 添加异常值
X_outliers = np.copy(X)
X_outliers[:10] = X_outliers[:10] + 5  # 添加10个异常值
# 比较不同方法
methods = {
    '经验协方差': EmpiricalCovariance(),
    'MCD稳健估计': MinCovDet(support_fraction=0.8),
}
# 可视化对比
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for idx, (name, method) in enumerate(methods.items()):
    method.fit(X_outliers)
    cov = method.covariance_
    # 计算马氏距离
    if name == '经验协方差':
        mahal = method.mahalanobis(X_outliers)
    else:
        mahal = method.mahalanobis(X_outliers)
    axes[idx].scatter(X_outliers[:, 0], X_outliers[:, 1], 
                     c=mahal, cmap='viridis', s=50)
    axes[idx].set_title(f'{name}\n协方差矩阵迹: {np.trace(cov):.2f}')
    axes[idx].set_xlabel('特征1')
    axes[idx].set_ylabel('特征2')
plt.tight_layout()
plt.show()

收缩协方差估计

from sklearn.covariance import ShrunkCovariance, LedoitWolf, OAS
# 生成高维数据
np.random.seed(42)
n_samples_small = 50
n_features_big = 100
X_high_dim = np.random.randn(n_samples_small, n_features_big)
# 比较不同收缩方法
shrinkage_methods = {
    '经验协方差': EmpiricalCovariance(),
    '收缩协方差(α=0.1)': ShrunkCovariance(shrinkage=0.1),
    'Ledoit-Wolf': LedoitWolf(),
    'Oracle近似(OAS)': OAS(),
}
print("不同收缩方法的协方差矩阵迹对比:")
for name, method in shrinkage_methods.items():
    method.fit(X_high_dim)
    cov = method.covariance_
    # 计算特征值分布
    eigenvalues = np.linalg.eigvalsh(cov)
    condition_number = eigenvalues[-1] / eigenvalues[0]
    print(f"{name:30s}: 迹={np.trace(cov):.2f}, "
          f"条件数={condition_number:.2f}, "
          f"最小特征值={eigenvalues[0]:.4f}")

实际案例:金融数据协方差分析

import yfinance as yf
from datetime import datetime, timedelta
# 下载股票数据
tickers = ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'META']
end_date = datetime.now()
start_date = end_date - timedelta(days=365)
# 获取股票价格数据
stock_data = yf.download(tickers, start=start_date, end=end_date)['Close']
# 计算日收益率
returns = stock_data.pct_change().dropna()
print("收益率数据形状:", returns.shape)
# 协方差估计
estimators = {
    '经验协方差': EmpiricalCovariance(),
    'Ledoit-Wolf': LedoitWolf(),
    'MCD稳健估计': MinCovDet(support_fraction=0.75),
}
# 可视化协方差矩阵
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 原始收益率相关性矩阵
sns.heatmap(returns.corr(), annot=True, fmt='.2f', 
            cmap='coolwarm', ax=axes[0, 0])
axes[0, 0].set_title('收益率相关系数矩阵')
# 不同方法的协方差估计
for ax, (name, estimator) in zip(axes.flatten()[1:], estimators.items()):
    estimator.fit(returns.values)
    cov_mat = estimator.covariance_
    # 转换为相关系数矩阵便于比较
    std = np.sqrt(np.diag(cov_mat))
    corr_mat = cov_mat / np.outer(std, std)
    im = ax.imshow(corr_mat, cmap='coolwarm', vmin=-1, vmax=1)
    ax.set_xticks(range(len(tickers)))
    ax.set_yticks(range(len(tickers)))
    ax.set_xticklabels(tickers, rotation=45)
    ax.set_yticklabels(tickers)
    ax.set_title(f'{name}')
    # 添加数值标签
    for i in range(len(tickers)):
        for j in range(len(tickers)):
            ax.text(j, i, f'{corr_mat[i,j]:.2f}', 
                   ha='center', va='center')
plt.tight_layout()
plt.show()
# 计算投资组合风险
def portfolio_risk(weights, cov_matrix):
    """计算投资组合风险(方差)"""
    return np.dot(weights.T, np.dot(cov_matrix, weights))
# 等权投资组合
n_assets = len(tickers)
equal_weights = np.ones(n_assets) / n_assets
print("\n等权投资组合风险对比:")
for name, estimator in estimators.items():
    estimator.fit(returns.values)
    risk = portfolio_risk(equal_weights, estimator.covariance_)
    print(f"{name:20s}: 年化波动率 = {np.sqrt(252 * risk):.4f}")

异常检测应用

from sklearn.covariance import EllipticEnvelope
# 生成混合数据
np.random.seed(42)
X_normal = np.random.randn(200, 2) * 2
X_outliers = np.random.uniform(low=-6, high=6, size=(20, 2))
X_mixed = np.vstack([X_normal, X_outliers])
# 应用协方差估计进行异常检测
cov_estimator = EllipticEnvelope(contamination=0.1, random_state=42)
labels = cov_estimator.fit_predict(X_mixed)
# 可视化异常检测结果
plt.figure(figsize=(10, 6))
colors = np.array(['blue', 'red'])
plt.scatter(X_mixed[:, 0], X_mixed[:, 1], 
           c=colors[(labels + 1) // 2], 
           s=50, alpha=0.7)
# 绘制协方差椭圆
from matplotlib.patches import Ellipse
import matplotlib.transforms as transforms
def plot_ellipse(ax, mean, cov, color, alpha=0.3):
    """绘制协方差椭圆"""
    eigenvalues, eigenvectors = np.linalg.eigh(cov)
    angle = np.arctan2(eigenvectors[1, 0], eigenvectors[0, 0])
    angle = np.degrees(angle)
    width, height = 2 * np.sqrt(eigenvalues)
    ellipse = Ellipse(xy=mean, width=width, height=height, 
                     angle=angle, facecolor=color, 
                     alpha=alpha, edgecolor='black')
    ax.add_patch(ellipse)
plot_ellipse(plt.gca(), 
             cov_estimator.location_, 
             cov_estimator.covariance_,
             'green', 0.2)
'基于协方差估计的异常检测')
plt.xlabel('特征1')
plt.ylabel('特征2')
plt.legend(['正常样本', '异常样本', '协方差椭圆'])
plt.grid(True, alpha=0.3)
plt.show()

性能评估与选择

from sklearn.model_selection import cross_val_score
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
# 生成受污染的数据
np.random.seed(42)
X_clean = np.random.randn(300, 10)
X_contaminated = X_clean.copy()
# 添加异常值
outlier_indices = np.random.choice(300, 30, replace=False)
X_contaminated[outlier_indices] = X_contaminated[outlier_indices] + 8
# 分割数据
X_train, X_test = train_test_split(X_contaminated, test_size=0.3, random_state=42)
# 评估不同估计器
estimators_to_test = {
    'Empirical': EmpiricalCovariance(),
    'Shrunk(0.1)': ShrunkCovariance(shrinkage=0.1),
    'Shrunk(0.5)': ShrunkCovariance(shrinkage=0.5),
    'Ledoit-Wolf': LedoitWolf(),
    'OAS': OAS(),
    'MCD': MinCovDet(support_fraction=0.75)
}
results = {}
for name, estimator in estimators_to_test.items():
    try:
        estimator.fit(X_train)
        # 计算协方差矩阵估计误差
        true_cov = np.cov(X_clean, rowvar=False)
        estimated_cov = estimator.covariance_
        # Frobenius范数误差
        error = np.linalg.norm(estimated_cov - true_cov, 'fro')
        results[name] = {
            'error': error,
            'condition_number': np.linalg.cond(estimated_cov)
        }
    except Exception as e:
        print(f"{name}: 估算失败 - {e}")
# 显示结果
print("\n协方差估计性能对比:")
results_df = pd.DataFrame(results).T
results_df = results_df.round(3)
print(results_df)
# 可视化结果
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# 误差对比
colors = ['skyblue', 'lightgreen', 'lightgreen', 'lightcoral', 'lightcoral', 'gold']
ax1.bar(results_df.index, results_df['error'], color=colors[:len(results_df)])
ax1.set_title('Frobenius范数估计误差')
ax1.set_xticklabels(results_df.index, rotation=45)
ax1.set_ylabel('误差')
# 条件数对比
ax2.bar(results_df.index, np.log10(results_df['condition_number']), color=colors[:len(results_df)])
ax2.set_title('条件数(log10)')
ax2.set_xticklabels(results_df.index, rotation=45)
ax2.set_ylabel('log10(条件数)')
ax2.axhline(y=np.median(np.log10(results_df['condition_number'])), 
           color='red', linestyle='--', label='中位数')
ax2.legend()
plt.tight_layout()
plt.show()

关键要点

  1. 选择建议

    • 数据干净:使用经验协方差
    • 高维数据:使用Ledoit-Wolf或OAS
    • 存在异常值:使用MCD稳健估计
    • 需要正则化:使用收缩协方差
  2. 实际应用

    • 资产组合优化
    • 异常检测
    • 特征选择
    • 降维预处理
  3. 注意事项

    • 小样本高维数据要谨慎
    • 异常值会严重影响估计
    • 不同的应用场景选择不同的方法

这些案例展示了Scikit-learn中协方差估计的多种方法和实际应用场景,根据具体需求选择合适的方法非常重要。

抱歉,评论功能暂时关闭!