监控数据分布偏移的脚本如何编写

wen 实用脚本 26

本文目录导读:

监控数据分布偏移的脚本如何编写

  1. 基于统计检验的Python脚本
  2. 实时监控脚本(带报警功能)
  3. 多维度分布监控脚本
  4. 自动化监控与告警系统

基于统计检验的Python脚本

import numpy as np
import pandas as pd
from scipy import stats
from scipy.spatial.distance import jensenshannon
import warnings
warnings.filterwarnings('ignore')
class DistributionShiftMonitor:
    def __init__(self, reference_data, threshold=0.05):
        """
        初始化监控器
        reference_data: 参考数据集(训练数据分布)
        threshold: 检测阈值
        """
        self.reference_data = reference_data
        self.threshold = threshold
        self.reference_stats = self._calculate_statistics(reference_data)
    def _calculate_statistics(self, data):
        """计算数据的统计特征"""
        stats_dict = {
            'mean': np.mean(data),
            'std': np.std(data),
            'median': np.median(data),
            'percentiles': np.percentile(data, [25, 50, 75]),
            'skewness': stats.skew(data),
            'kurtosis': stats.kurtosis(data)
        }
        return stats_dict
    def ks_test(self, new_data):
        """Kolmogorov-Smirnov检验"""
        statistic, p_value = stats.ks_2samp(self.reference_data, new_data)
        return {
            'test': 'KS Test',
            'statistic': statistic,
            'p_value': p_value,
            'drift_detected': p_value < self.threshold
        }
    def js_distance(self, new_data, bins=50):
        """Jensen-Shannon散度"""
        # 计算直方图
        hist_ref, edges = np.histogram(self.reference_data, bins=bins, density=True)
        hist_new, _ = np.histogram(new_data, edges, density=True)
        # 确保非零值
        hist_ref = hist_ref + 1e-10
        hist_new = hist_new + 1e-10
        # 归一化
        hist_ref = hist_ref / hist_ref.sum()
        hist_new = hist_new / hist_new.sum()
        js_div = jensenshannon(hist_ref, hist_new)
        # JS距离的阈值通常设置为0.1
        js_threshold = 0.1
        return {
            'test': 'JS Distance',
            'distance': js_div,
            'drift_detected': js_div > js_threshold
        }
    def chi_square_test(self, new_data, bins=20):
        """卡方检验"""
        # 分箱
        hist_ref, edges = np.histogram(self.reference_data, bins=bins)
        hist_new, _ = np.histogram(new_data, edges)
        # 执行卡方检验
        chi2_stat, p_value = stats.chisquare(hist_new, f_exp=hist_ref)
        return {
            'test': 'Chi-Square Test',
            'statistic': chi2_stat,
            'p_value': p_value,
            'drift_detected': p_value < self.threshold
        }
    def wasserstein_distance(self, new_data):
        """Wasserstein距离(Earth Mover's Distance)"""
        w_dist = stats.wasserstein_distance(self.reference_data, new_data)
        # Wasserstein距离阈值根据数据尺度调整
        std_ref = self.reference_stats['std']
        threshold = 0.1 * std_ref
        return {
            'test': 'Wasserstein Distance',
            'distance': w_dist,
            'normalized_distance': w_dist / std_ref,
            'drift_detected': w_dist > threshold
        }
    def comprehensive_monitor(self, new_data):
        """综合监控报告"""
        results = {
            'reference_stats': self.reference_stats,
            'new_data_stats': self._calculate_statistics(new_data),
            'tests': {
                'ks_test': self.ks_test(new_data),
                'js_distance': self.js_distance(new_data),
                'chi_square': self.chi_square_test(new_data),
                'wasserstein': self.wasserstein_distance(new_data)
            }
        }
        # 整体判定:多数测试检测到漂移
        drift_count = sum(1 for test in results['tests'].values() 
                         if test['drift_detected'])
        results['overall_drift'] = drift_count >= 2
        return results
# 使用示例
if __name__ == "__main__":
    # 生成示例数据
    np.random.seed(42)
    reference_data = np.random.normal(0, 1, 10000)
    shifted_data = np.random.normal(0.5, 1.2, 10000)
    normal_data = np.random.normal(0, 1, 10000)
    # 初始化监控器
    monitor = DistributionShiftMonitor(reference_data, threshold=0.05)
    # 监控有漂移的数据
    print("="*50)
    print("检测有漂移的数据:")
    results_shifted = monitor.comprehensive_monitor(shifted_data)
    print_results(results_shifted)
    print("\n" + "="*50)
    print("检测正常数据:")
    results_normal = monitor.comprehensive_monitor(normal_data)
    print_results(results_normal)
def print_results(results):
    """打印结果"""
    print("\n参考数据统计:")
    for key, value in results['reference_stats'].items():
        print(f"  {key}: {value:.3f}")
    print("\n新数据统计:")
    for key, value in results['new_data_stats'].items():
        print(f"  {key}: {value:.3f}")
    print("\n检测结果:")
    for test_name, test_results in results['tests'].items():
        drift_status = "⚠️ 漂移" if test_results['drift_detected'] else "✅ 正常"
        print(f"  {test_results['test']}: {drift_status}")
    overall = "⚠️ 检测到分布漂移" if results['overall_drift'] else "✅ 分布正常"
    print(f"\n综合判定: {overall}")

实时监控脚本(带报警功能)

import numpy as np
import pandas as pd
from datetime import datetime, timedelta
import logging
import json
import smtplib
from email.mime.text import MIMEText
import time
class RealTimeDistributionMonitor:
    def __init__(self, reference_distribution, config=None):
        """
        实时分布监控器
        reference_distribution: 参考分布数据或统计参数
        config: 配置字典
        """
        self.reference = reference_distribution
        self.config = config or self._default_config()
        self.alerts_history = []
        self._setup_logging()
    def _default_config(self):
        return {
            'window_size': 100,  # 滑动窗口大小
            'check_frequency': 10,  # 检查频率(每N个样本)
            'alert_threshold': 0.05,  # 告警阈值
            'email_alerts': False,
            'email_config': {
                'smtp_server': 'smtp.gmail.com',
                'smtp_port': 587,
                'sender': 'monitor@example.com',
                'password': 'your_password',
                'recipients': ['alert@example.com']
            },
            'slack_webhook': None,
            'log_file': 'distribution_monitor.log'
        }
    def _setup_logging(self):
        """设置日志"""
        logging.basicConfig(
            filename=self.config['log_file'],
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s'
        )
        self.logger = logging.getLogger(__name__)
    def process_stream(self, data_stream, delay=0.01):
        """
        处理数据流
        data_stream: 数据生成器或迭代器
        delay: 处理延迟(秒)
        """
        buffer = []
        sample_count = 0
        for sample in data_stream:
            buffer.append(sample)
            sample_count += 1
            # 每收集到一定数量样本进行检查
            if sample_count % self.config['check_frequency'] == 0:
                if len(buffer) >= self.config['window_size']:
                    window_data = buffer[-self.config['window_size']:]
                    self._check_and_alert(window_data)
            time.sleep(delay)
            # 清理旧数据
            if len(buffer) > self.config['window_size'] * 2:
                buffer = buffer[-self.config['window_size']:]
    def _check_and_alert(self, window_data):
        """检查并触发告警"""
        from scipy import stats
        # 执行KS检验
        statistic, p_value = stats.ks_2samp(self.reference, window_data)
        alert_data = {
            'timestamp': datetime.now().isoformat(),
            'window_size': len(window_data),
            'ks_statistic': statistic,
            'p_value': p_value,
            'drift_detected': p_value < self.config['alert_threshold'],
            'window_stats': {
                'mean': np.mean(window_data),
                'std': np.std(window_data),
                'percentiles': np.percentile(window_data, [25, 50, 75])
            }
        }
        # 记录日志
        self.logger.info(f"监测结果: {json.dumps(alert_data, indent=2)}")
        # 如果检测到漂移,触发告警
        if alert_data['drift_detected']:
            self.alerts_history.append(alert_data)
            self._trigger_alerts(alert_data)
    def _trigger_alerts(self, alert_data):
        """触发告警机制"""
        # 日志告警(已包含)
        self.logger.warning(f"检测到分布漂移: {alert_data}")
        # 打印告警
        print(f"⚠️ 告警: 检测到数据分布漂移 (p-value: {alert_data['p_value']:.6f})")
        # 邮件告警
        if self.config['email_alerts']:
            self._send_email_alert(alert_data)
        # Slack告警(如果需要)
        if self.config['slack_webhook']:
            self._send_slack_alert(alert_data)
    def _send_email_alert(self, alert_data):
        """发送邮件告警"""
        try:
            msg = MIMEText(f"""
            数据分布漂移告警
            时间: {alert_data['timestamp']}
            KS统计量: {alert_data['ks_statistic']:.4f}
            P值: {alert_data['p_value']:.6f}
            窗口大小: {alert_data['window_size']}
            统计信息:
            - 均值: {alert_data['window_stats']['mean']:.3f}
            - 标准差: {alert_data['window_stats']['std']:.3f}
            - 中位数: {alert_data['window_stats']['percentiles'][1]:.3f}
            """)
            msg['Subject'] = '数据分布漂移告警'
            msg['From'] = self.config['email_config']['sender']
            msg['To'] = ', '.join(self.config['email_config']['recipients'])
            server = smtplib.SMTP(
                self.config['email_config']['smtp_server'],
                self.config['email_config']['smtp_port']
            )
            server.starttls()
            server.login(
                self.config['email_config']['sender'],
                self.config['email_config']['password']
            )
            server.send_message(msg)
            server.quit()
        except Exception as e:
            self.logger.error(f"发送邮件告警失败: {e}")
    def get_alerts_summary(self):
        """获取告警汇总"""
        return {
            'total_alerts': len(self.alerts_history),
            'last_alert': self.alerts_history[-1] if self.alerts_history else None,
            'alert_rate': len(self.alerts_history) / max(1, len(self.alerts_history) + 1)
        }
# 使用示例
if __name__ == "__main__":
    # 模拟数据流
    def data_stream_generator():
        np.random.seed(42)
        phase = 0
        while True:
            if phase < 1000:
                # 正常数据
                yield np.random.normal(0, 1)
            elif phase < 2000:
                # 开始漂移
                shift = (phase - 1000) / 1000
                yield np.random.normal(shift, 1 + shift/2)
            else:
                # 严重漂移
                yield np.random.normal(1, 1.5)
            phase += 1
    # 参考分布
    reference_data = np.random.normal(0, 1, 10000)
    # 初始化监控器
    monitor = RealTimeDistributionMonitor(
        reference_data,
        config={
            'window_size': 200,
            'check_frequency': 50,
            'alert_threshold': 0.01,
            'log_file': 'real_time_monitor.log'
        }
    )
    # 开始监控(运行时间有限)
    print("开始实时监控...")
    stream = data_stream_generator()
    # 只处理前500个样本作为演示
    for i, sample in enumerate(stream):
        if i > 500:  # 限制运行时间
            break
        # 在实际应用中,这里会调用实时监控

多维度分布监控脚本

import numpy as np
import pandas as pd
from sklearn.ensemble import IsolationForest
from sklearn.covariance import EllipticEnvelope
from sklearn.preprocessing import StandardScaler
import plotly.graph_objects as go
from plotly.subplots import make_subplots
class MultiDimensionalShiftMonitor:
    def __init__(self, reference_data, feature_names=None):
        """
        多维数据分布监控器
        reference_data: DataFrame或二维数组
        feature_names: 特征名称列表
        """
        if isinstance(reference_data, pd.DataFrame):
            self.reference_df = reference_data
            self.feature_names = reference_data.columns.tolist()
        else:
            self.reference_df = pd.DataFrame(reference_data, columns=feature_names)
            self.feature_names = feature_names or [f'feature_{i}' for i in range(reference_data.shape[1])]
        self.n_features = len(self.feature_names)
        self.reference_statistics = self._calculate_multivariate_stats()
        self.models = {}
    def _calculate_multivariate_stats(self):
        """计算多维统计特征"""
        stats = {
            'mean': self.reference_df.mean(),
            'covariance': self.reference_df.cov(),
            'correlation': self.reference_df.corr(),
            'feature_stats': {}
        }
        for col in self.feature_names:
            stats['feature_stats'][col] = {
                'mean': self.reference_df[col].mean(),
                'std': self.reference_df[col].std(),
                'skewness': self.reference_df[col].skew(),
                'kurtosis': self.reference_df[col].kurtosis(),
                'percentiles': self.reference_df[col].quantile([0.25, 0.5, 0.75]).to_dict()
            }
        return stats
    def train_anomaly_detection_models(self):
        """训练异常检测模型"""
        # 标准化数据
        scaler = StandardScaler()
        scaled_data = scaler.fit_transform(self.reference_df)
        # Isolation Forest
        isolation_forest = IsolationForest(
            contamination=0.1,
            random_state=42
        )
        isolation_forest.fit(scaled_data)
        # Elliptic Envelope
        elliptic_envelope = EllipticEnvelope(
            contamination=0.1,
            random_state=42
        )
        elliptic_envelope.fit(scaled_data)
        self.models = {
            'scaler': scaler,
            'isolation_forest': isolation_forest,
            'elliptic_envelope': elliptic_envelope
        }
    def per_feature_shift_detection(self, new_data):
        """单特征漂移检测"""
        results = {}
        for feature in self.feature_names:
            ref_values = self.reference_df[feature]
            new_values = new_data[feature] if isinstance(new_data, pd.DataFrame) else new_data[:, self.feature_names.index(feature)]
            from scipy import stats
            # KS检验
            ks_stat, ks_p = stats.ks_2samp(ref_values, new_values)
            # 均值差异检验
            t_stat, t_p = stats.ttest_ind(ref_values, new_values)
            # 分布差异度量
            ref_hist, edges = np.histogram(ref_values, bins=20, density=True)
            new_hist, _ = np.histogram(new_values, edges, density=True)
            js_div = self._js_divergence(ref_hist, new_hist)
            results[feature] = {
                'ks_test': {
                    'statistic': ks_stat,
                    'p_value': ks_p,
                    'drift': ks_p < 0.05
                },
                'ttest': {
                    'statistic': t_stat,
                    'p_value': t_p,
                    'drift': t_p < 0.05
                },
                'js_divergence': js_div,
                'statistics': {
                    'ref_mean': np.mean(ref_values),
                    'new_mean': np.mean(new_values),
                    'mean_change': np.mean(new_values) - np.mean(ref_values)
                }
            }
        return results
    def multivariate_shift_detection(self, new_data):
        """多维漂移检测"""
        if not self.models:
            self.train_anomaly_detection_models()
        if isinstance(new_data, pd.DataFrame):
            new_array = new_data.values
        else:
            new_array = new_data
        # 标准化新数据
        scaled_new = self.models['scaler'].transform(new_array)
        # 使用Isolation Forest检测异常
        if_scores = self.models['isolation_forest'].score_samples(scaled_new)
        if_predictions = self.models['isolation_forest'].predict(scaled_new)
        # 使用Elliptic Envelope检测异常
        ee_scores = self.models['elliptic_envelope'].score_samples(scaled_new)
        ee_predictions = self.models['elliptic_envelope'].predict(scaled_new)
        # 计算Mahalanobis距离
        mahalanobis_distances = self._mahalanobis_distance(
            new_array, 
            self.reference_statistics['mean'].values,
            self.reference_statistics['covariance'].values
        )
        return {
            'if_anomaly_ratio': np.mean(if_predictions == -1),
            'ee_anomaly_ratio': np.mean(ee_predictions == -1),
            'mean_mahalanobis': np.mean(mahalanobis_distances),
            'max_mahalanobis': np.max(mahalanobis_distances),
            'if_scores': if_scores,
            'ee_scores': ee_scores,
            'mahalanobis_distances': mahalanobis_distances
        }
    def _js_divergence(self, p, q):
        """计算Jensen-Shannon散度"""
        from scipy.spatial.distance import jensenshannon
        return jensenshannon(p, q)
    def _mahalanobis_distance(self, x, mean, cov):
        """计算马氏距离"""
        try:
            inv_cov = np.linalg.inv(cov)
            distances = []
            for observation in x:
                diff = observation - mean
                dist = np.sqrt(diff.T @ inv_cov @ diff)
                distances.append(dist)
            return np.array(distances)
        except np.linalg.LinAlgError:
            # 如果协方差矩阵不可逆,使用伪逆
            inv_cov = np.linalg.pinv(cov)
            distances = []
            for observation in x:
                diff = observation - mean
                dist = np.sqrt(diff.T @ inv_cov @ diff)
                distances.append(dist)
            return np.array(distances)
    def visualize_shift(self, new_data, save_path=None):
        """可视化分布漂移"""
        per_feature_results = self.per_feature_shift_detection(new_data)
        # 创建子图
        n_features = len(self.feature_names)
        fig = make_subplots(
            rows=n_features, cols=2,
            subplot_titles=[f'{feat} - 分布对比' for feat in self.feature_names] + 
                          [f'{feat} - Q-Q图' for feat in self.feature_names]
        )
        for i, feature in enumerate(self.feature_names):
            ref_values = self.reference_df[feature]
            new_values = new_data[feature] if isinstance(new_data, pd.DataFrame) else new_data[:, i]
            # 分布对比图
            fig.add_trace(
                go.Histogram(x=ref_values, name='参考分布', opacity=0.7),
                row=i+1, col=1
            )
            fig.add_trace(
                go.Histogram(x=new_values, name='新数据分布', opacity=0.7),
                row=i+1, col=1
            )
            # Q-Q图
            sorted_ref = np.sort(ref_values)
            sorted_new = np.sort(new_values)
            fig.add_trace(
                go.Scatter(x=sorted_ref, y=sorted_new, mode='markers',
                          name=f'{feature} Q-Q'),
                row=i+1, col=2
            )
            # 添加参考线
            min_val = min(sorted_ref.min(), sorted_new.min())
            max_val = max(sorted_ref.max(), sorted_new.max())
            fig.add_trace(
                go.Scatter(x=[min_val, max_val], y=[min_val, max_val],
                          mode='lines', line=dict(dash='dash', color='red'),
                          showlegend=False),
                row=i+1, col=2
            )
        fig.update_layout(height=300*n_features, title_text="分布漂移可视化")
        if save_path:
            fig.write_html(save_path)
        fig.show()
    def comprehensive_report(self, new_data):
        """生成综合报告"""
        per_feature = self.per_feature_shift_detection(new_data)
        multivariate = self.multivariate_shift_detection(new_data)
        report = {
            'per_feature_analysis': per_feature,
            'multivariate_analysis': multivariate,
            'drift_summary': {
                'features_with_drift': sum(1 for f in per_feature.values() 
                                          if any(t['drift'] for t in f.values() 
                                                if isinstance(t, dict) and 'drift' in t)),
                'anomaly_ratio': multivariate['if_anomaly_ratio'],
                'overall_drift_score': np.mean([
                    f['js_divergence'] for f in per_feature.values()
                ])
            }
        }
        # 综合判定
        drift_thresholds = {
            'feature_drift_ratio': 0.3,  # 30%的特征出现漂移
            'anomaly_ratio': 0.15,       # 15%的异常率
            'js_divergence': 0.1         # JS散度阈值
        }
        report['overall_assessment'] = {
            'drift_detected': (
                report['drift_summary']['features_with_drift'] / self.n_features > drift_thresholds['feature_drift_ratio'] or
                report['drift_summary']['anomaly_ratio'] > drift_thresholds['anomaly_ratio'] or
                report['drift_summary']['overall_drift_score'] > drift_thresholds['js_divergence']
            ),
            'severity': 'high' if report['drift_summary']['overall_drift_score'] > 0.2 else \
                       'medium' if report['drift_summary']['overall_drift_score'] > 0.1 else 'low',
            'recommendations': self._generate_recommendations(report)
        }
        return report
    def _generate_recommendations(self, report):
        """生成建议"""
        recommendations = []
        if report['drift_summary']['features_with_drift'] > self.n_features * 0.5:
            recommendations.append("多个特征出现显著漂移,建议重新训练模型")
        if report['multivariate_analysis']['if_anomaly_ratio'] > 0.2:
            recommendations.append("数据异常比例较高,建议检查数据采集流程")
        if report['drift_summary']['overall_drift_score'] > 0.15:
            recommendations.append("整体分布漂移较大,建议实施模型更新策略")
        if not recommendations:
            recommendations.append("当前数据分布基本稳定,继续监控")
        return recommendations
# 使用示例
if __name__ == "__main__":
    # 生成示例数据
    np.random.seed(42)
    n_samples = 1000
    n_features = 3
    # 参考数据
    reference_data = pd.DataFrame({
        'feature_1': np.random.normal(0, 1, n_samples),
        'feature_2': np.random.normal(10, 2, n_samples),
        'feature_3': np.random.normal(-5, 3, n_samples)
    })
    # 漂移数据
    shifted_data = pd.DataFrame({
        'feature_1': np.random.normal(0.5, 1.5, n_samples),
        'feature_2': np.random.normal(12, 3, n_samples),
        'feature_3': np.random.normal(-3, 4, n_samples)
    })
    # 初始化监控器
    monitor = MultiDimensionalShiftMonitor(reference_data)
    # 执行检测
    print("执行多维分布漂移检测...")
    report = monitor.comprehensive_report(shifted_data)
    # 打印结果
    print("\n" + "="*60)
    print("漂移检测报告")
    print("="*60)
    print(f"\n整体判定: {'⚠️ 检测到漂移' if report['overall_assessment']['drift_detected'] else '✅ 分布正常'}")
    print(f"严重程度: {report['overall_assessment']['severity']}")
    print(f"\n特征分析:")
    for feature, results in report['per_feature_analysis'].items():
        drift_flags = []
        if results['ks_test']['drift']:
            drift_flags.append('KS检验')
        if isinstance(results.get('ttest'), dict) and results['ttest']['drift']:
            drift_flags.append('T检验')
        status = "⚠️ 漂移" if drift_flags else "✅ 正常"
        print(f"  {feature}: {status}")
        if drift_flags:
            print(f"    检测方法: {', '.join(drift_flags)}")
    print(f"\n多维分析:")
    print(f"  异常比例 (Isolation Forest): {report['multivariate_analysis']['if_anomaly_ratio']:.2%}")
    print(f"  平均马氏距离: {report['multivariate_analysis']['mean_mahalanobis']:.2f}")
    print(f"\n建议:")
    for rec in report['overall_assessment']['recommendations']:
        print(f"  - {rec}")
    # 可视化(可选)
    # monitor.visualize_shift(shifted_data, save_path='shift_visualization.html')

自动化监控与告警系统

import schedule
import time
import json
from datetime import datetime
import pandas as pd
import numpy as np
from pathlib import Path
class AutomatedShiftMonitoringSystem:
    def __init__(self, config_path='monitor_config.json'):
        """
        自动化分布漂移监控系统
        config_path: 配置文件路径
        """
        self.config = self._load_config(config_path)
        self.monitors = {}
        self.setup_monitors()
    def _load_config(self, config_path):
        """加载配置"""
        default_config = {
            'databases': {
                'production': {
                    'type': 'csv',
                    'path': 'production_data.csv',
                    'check_interval': 3600  # 每小时检查一次
                },
                'staging': {
                    'type': 'csv',
                    'path': 'staging_data.csv',
                    'check_interval': 7200  # 每2小时检查一次
                }
            },
            'reference_data': {
                'path': 'reference_data.csv',
                'update_interval': 86400  # 每天更新一次
            },
            'alerts': {
                'channels': ['log', 'console'],
                'email': {
                    'enabled': False,
                    'smtp_server': 'smtp.gmail.com',
                    'sender': 'monitor@example.com'
                },
                'slack': {
                    'enabled': False,
                    'webhook_url': None
                }
            },
            'thresholds': {
                'ks_test_p_value': 0.05,
                'js_divergence': 0.1,
                'anomaly_ratio': 0.15
            },
            'logging': {
                'level': 'INFO',
                'file': 'monitoring.log',
                'max_size': 10 * 1024 * 1024  # 10MB
            }
        }
        try:
            with open(config_path, 'r') as f:
                user_config = json.load(f)
                # 合并配置
                default_config.update(user_config)
        except FileNotFoundError:
            # 保存默认配置
            with open(config_path, 'w') as f:
                json.dump(default_config, f, indent=2)
            print(f"已创建默认配置文件: {config_path}")
        return default_config
    def setup_monitors(self):
        """设置监控器"""
        # 加载参考数据
        reference_data = self._load_reference_data()
        # 为每个数据库创建监控器
        for db_name, db_config in self.config['databases'].items():
            monitor = DistributionShiftMonitor(
                reference_data,
                threshold=self.config['thresholds']['ks_test_p_value']
            )
            self.monitors[db_name] = {
                'monitor': monitor,
                'config': db_config,
                'last_check': None,
                'alerts': []
            }
    def _load_reference_data(self):
        """加载参考数据"""
        ref_path = self.config['reference_data']['path']
        if Path(ref_path).exists():
            data = pd.read_csv(ref_path)
            return data.values.flatten()  # 简化处理,实际应根据需求调整
        else:
            # 生成示例参考数据
            print(f"警告: 参考数据文件 {ref_path} 不存在,使用生成的数据")
            return np.random.normal(0, 1, 10000)
    def check_data_sources(self):
        """检查所有数据源"""
        results = {}
        for db_name, monitor_info in self.monitors.items():
            print(f"\n检查数据源: {db_name}")
            try:
                # 加载数据
                new_data = self._load_data_source(db_name)
                if new_data is not None:
                    # 执行检测
                    detection_result = monitor_info['monitor'].comprehensive_monitor(new_data)
                    # 更新监控信息
                    monitor_info['last_check'] = datetime.now()
                    # 记录结果
                    results[db_name] = {
                        'status': 'success',
                        'drift_detected': detection_result['overall_drift'],
                        'details': detection_result
                    }
                    # 如果检测到漂移,发送告警
                    if detection_result['overall_drift']:
                        self._send_alert(db_name, detection_result)
                        monitor_info['alerts'].append({
                            'timestamp': datetime.now().isoformat(),
                            'severity': 'high',
                            'details': detection_result
                        })
                else:
                    results[db_name] = {
                        'status': 'error',
                        'message': '无法加载数据'
                    }
            except Exception as e:
                results[db_name] = {
                    'status': 'error',
                    'message': str(e)
                }
                print(f"  错误: {e}")
        return results
    def _load_data_source(self, db_name):
        """从数据源加载数据"""
        config = self.config['databases'][db_name]
        try:
            if config['type'] == 'csv':
                if Path(config['path']).exists():
                    data = pd.read_csv(config['path'])
                    # 假设数据在第一列或数字列
                    numeric_cols = data.select_dtypes(include=[np.number]).columns
                    if len(numeric_cols) > 0:
                        return data[numeric_cols[0]].values
                    else:
                        print(f"  警告: {config['path']} 中没有数值列")
                        return None
                else:
                    print(f"  警告: 数据文件 {config['path']} 不存在")
                    # 返回模拟数据用于演示
                    return np.random.normal(0.2, 1.1, 1000)
            elif config['type'] == 'database':
                # 数据库连接代码(需要根据实际情况实现)
                print(f"  数据库连接尚未实现: {config['path']}")
                return None
            else:
                print(f"  不支持的数据源类型: {config['type']}")
                return None
        except Exception as e:
            print(f"  加载数据失败: {e}")
            return None
    def _send_alert(self, source_name, detection_result):
        """发送告警"""
        alert_message = f"""
        数据分布漂移告警
        来源: {source_name}
        时间: {datetime.now().isoformat()}
        详情: 检测到数据分布发生显著变化
        """
        print(f"\n⚠️ 告警: {source_name} - 检测到分布漂移")
        # 根据配置发送告警
        for channel in self.config['alerts']['channels']:
            if channel == 'log':
                self._log_alert(alert_message)
            elif channel == 'console':
                print(alert_message)
            elif channel == 'email' and self.config['alerts']['email']['enabled']:
                self._send_email_alert(alert_message)
            elif channel == 'slack' and self.config['alerts']['slack']['enabled']:
                self._send_slack_alert(alert_message)
    def _log_alert(self, message):
        """日志告警"""
        import logging
        logging.warning(message)
    def _send_email_alert(self, message):
        """发送邮件告警(占位)"""
        print("邮件告警功能待实现")
    def _send_slack_alert(self, message):
        """发送Slack告警(占位)"""
        print("Slack告警功能待实现")
    def run_scheduled_checks(self):
        """运行定时检查"""
        # 设置定时任务
        for db_name, monitor_info in self.monitors.items():
            interval = monitor_info['config']['check_interval']
            schedule.every(interval).seconds.do(
                self.check_single_source, db_name
            )
        print("开始定时监控任务...")
        print(f"监控的数据源: {list(self.monitors.keys())}")
        print("按 Ctrl+C 停止监控")
        try:
            while True:
                schedule.run_pending()
                time.sleep(1)
        except KeyboardInterrupt:
            print("\n监控已停止")
    def check_single_source(self, db_name):
        """检查单个数据源"""
        print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 检查 {db_name}")
        results = self.check_data_sources()
        if db_name in results:
            result = results[db_name]
            if result['status'] == 'success':
                drift_status = "漂移" if result['drift_detected'] else "正常"
                print(f"  {db_name}: {drift_status}")
    def get_monitoring_report(self):
        """获取监控报告"""
        report = {
            'timestamp': datetime.now().isoformat(),
            'data_sources': {},
            'summary': {
                'total_sources': len(self.monitors),
                'sources_with_drift': 0,
                'total_alerts': 0
            }
        }

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