sigma图的计算和绘图

Posted by Shallow Dreameron August 24, 2025

下面给出完整方案,包括 sigma 计算方法前端 echarts 绘图示例


1. Sigma 计算方法

假设:

  • y_true:真实值数组(numpy array 或 list)
  • y_pred:预测值数组(对应 loss 最小的那一轮预测结果)

Python 实现

import numpy as np

def calculate_sigma(y_true, y_pred):
    """
    计算每个样本的残差标准差 (sigma)
    """
    y_true = np.array(y_true)
    y_pred = np.array(y_pred)

    # 残差
    residuals = y_true - y_pred

    # 残差的标准差 (总体标准差)
    sigma = np.std(residuals)

    return sigma, residuals.tolist()

# 示例
y_true = [3.0, 5.0, 7.5, 10.0]
y_pred = [2.8, 5.2, 7.0, 9.8]

sigma, residuals = calculate_sigma(y_true, y_pred)
print("sigma:", sigma)
print("residuals:", residuals)  # 每个点的残差可用于绘制散点图

返回值:

  • sigma:整体的标准差,可绘制水平参考线
  • residuals:每个样本的残差,可作为图表的 y 轴数据

2. 前端 ECharts 绘图示例

以下是一个 残差与样本索引的散点图,并添加 ±sigma 的参考线。

<div id="sigmaChart" style="width: 800px; height: 400px;"></div>

<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script>
    // 假设后端返回以下数据
    const residuals = [0.2, -0.2, 0.5, -0.2]; // 每个点的残差
    const sigma = 0.3; // 标准差

    // x 轴为样本索引
    const xData = residuals.map((_, index) => index + 1);

    const chartDom = document.getElementById('sigmaChart');
    const myChart = echarts.init(chartDom);

    const option = {
        title: { text: '训练过程 Sigma 残差图' },
        tooltip: { trigger: 'axis' },
        xAxis: {
            type: 'category',
            data: xData,
            name: '样本索引'
        },
        yAxis: {
            type: 'value',
            name: '残差'
        },
        series: [
            {
                name: '残差',
                type: 'scatter',
                data: residuals,
                symbolSize: 8,
                itemStyle: {
                    color: '#5470C6'
                }
            },
            // 正 sigma 参考线
            {
                name: '+sigma',
                type: 'line',
                data: Array(xData.length).fill(sigma),
                lineStyle: { type: 'dashed', color: '#91cc75' },
                symbol: 'none'
            },
            // 负 sigma 参考线
            {
                name: '-sigma',
                type: 'line',
                data: Array(xData.length).fill(-sigma),
                lineStyle: { type: 'dashed', color: '#ee6666' },
                symbol: 'none'
            }
        ]
    };

    myChart.setOption(option);
</script>

3. 可扩展点

  • 若需要绘制 训练过程中每一轮的 sigma 曲线,可以把每轮计算出的 sigma 存储为数组,在 echarts 中绘制折线图。
  • 若需要区分训练集和验证集,可分别绘制两组残差。

下面详细说明如何计算需要的数据、定义数据格式及其含义,并提供大数据量时的优化方案。


1. 需要计算的数据

基本概念

  • 真实值 (y_true):模型在测试集/验证集上的真实标签。
  • 预测值 (y_pred):对应某一轮次(通常是 loss 最小轮次)的预测结果。
  • 残差 (residual):残差 = y_true - y_pred,表示每个点的预测误差。
  • σ (sigma):残差的标准差,衡量整体预测的离散程度。

数据计算步骤

  1. 计算每个样本的残差

    residuals = y_true - y_pred
    
  2. 计算整体 sigma

    sigma = np.std(residuals)
    
  3. 可选:分段统计

    • 若样本量很大,可以按区间计算残差的均值和标准差(见大数据量处理部分)。

2. 数据格式设计

单轮残差图(Scatter + 参考线)

适用于显示 loss 最小轮次 的残差分布。

{
  "xData": [1, 2, 3, 4, 5],
  "residuals": [0.2, -0.1, 0.5, -0.3, 0.1],
  "sigma": 0.25
}
  • xData:样本索引或时间戳
  • residuals:对应的残差值
  • sigma:整体标准差(可绘制参考线)

多轮 sigma 曲线(Loss + Sigma 监控)

如果要展示每一轮次的 sigma(例如判断收敛情况),格式可为:

{
  "epochs": [1, 2, 3, 4, 5],
  "sigmaList": [0.8, 0.6, 0.4, 0.35, 0.3],
  "lossList": [1.2, 0.9, 0.5, 0.4, 0.3]
}
  • epochs:训练轮次
  • sigmaList:每轮残差标准差
  • lossList:每轮损失值(用于双轴图或参考最优点)

3. 数据量大的处理方案

当样本量很大(> 5万点)时,前端直接绘制所有点会卡顿,需要数据降采样或聚合。

3.1 后端降采样

仅取等间隔的一部分点:

def downsample(residuals, step=10):
    return residuals[::step]

3.2 区间统计(推荐)

将数据按区间聚合,展示统计特征而非每个点:

import numpy as np

def aggregate_residuals(residuals, bin_size=100):
    n_bins = len(residuals) // bin_size
    agg_data = []
    for i in range(n_bins):
        segment = residuals[i*bin_size:(i+1)*bin_size]
        agg_data.append({
            "mean": float(np.mean(segment)),
            "std": float(np.std(segment)),
            "start": i*bin_size,
            "end": (i+1)*bin_size
        })
    return agg_data

前端绘制区间均值曲线 + 上下标准差带,更直观且性能好。


4. 推荐工作流

  1. 训练过程中
    • 每轮保存 y_predloss
    • 计算并记录 sigma
  2. 训练结束后
    • 找出 loss 最小轮次。
    • 获取该轮次的 residualssigma
  3. 前端展示
    • 小数据:散点图 + sigma 参考线。
    • 大数据:区间统计图或降采样曲线。

以下是完整方案,包括 Python 数据计算脚本ECharts 可视化(支持大数据量)


1. Python 数据计算脚本

该脚本完成以下功能:

  1. 在每个训练轮次计算 sigma 并记录。
  2. 找出 loss 最小轮次
  3. 计算该轮次的残差数据并进行降采样/聚合处理。
import numpy as np

def calculate_sigma(y_true, y_pred):
    """
    计算残差及其标准差
    """
    residuals = np.array(y_true) - np.array(y_pred)
    sigma = float(np.std(residuals))
    return sigma, residuals.tolist()

def find_best_epoch(loss_list):
    """
    找出loss最小的轮次索引
    """
    return int(np.argmin(loss_list))

def aggregate_residuals(residuals, bin_size=100):
    """
    将残差数据按区间聚合,减少前端绘图数据量
    """
    agg_data = []
    n_bins = len(residuals) // bin_size
    for i in range(n_bins):
        segment = residuals[i*bin_size:(i+1)*bin_size]
        agg_data.append({
            "mean": float(np.mean(segment)),
            "std": float(np.std(segment)),
            "start": i*bin_size,
            "end": (i+1)*bin_size
        })
    return agg_data

# ======= 模拟训练过程 =======

epochs = 50
samples = 5000

# 模拟真实值
y_true = np.linspace(0, 10, samples)

# 模拟每轮预测与loss
y_preds = [y_true + np.random.normal(0, 1/(ep+1), samples) for ep in range(epochs)]
loss_list = [np.mean((y_true - y_pred)**2) for y_pred in y_preds]

# 每轮sigma记录
sigma_list = []
for y_pred in y_preds:
    sigma, _ = calculate_sigma(y_true, y_pred)
    sigma_list.append(sigma)

# 找出loss最小轮次
best_epoch = find_best_epoch(loss_list)
best_y_pred = y_preds[best_epoch]

# 获取残差数据
_, residuals = calculate_sigma(y_true, best_y_pred)

# 降采样 / 聚合
agg_residuals = aggregate_residuals(residuals, bin_size=100)

# 返回给前端的数据
result = {
    "epochs": list(range(1, epochs+1)),
    "lossList": loss_list,
    "sigmaList": sigma_list,
    "bestEpochResiduals": residuals,
    "sigma": sigma_list[best_epoch],
    "aggregatedResiduals": agg_residuals
}

print(result.keys())  # ['epochs', 'lossList', 'sigmaList', 'bestEpochResiduals', 'sigma', 'aggregatedResiduals']

返回数据说明

  • epochs:训练轮次数组。
  • lossList:每轮次损失值。
  • sigmaList:每轮次的 sigma。
  • bestEpochResiduals:最佳轮次(loss 最小)对应的所有残差(原始数据)。
  • sigma:最佳轮次的整体标准差。
  • aggregatedResiduals:聚合后的残差数据,用于大数据量可视化。

2. 前端 ECharts 可视化

2.1 多轮次 sigma & loss 曲线

展示训练收敛情况。

<div id="sigmaLossChart" style="width: 800px; height: 400px;"></div>
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script>
const epochs = [1,2,3,4,5]; // Python返回
const sigmaList = [0.8,0.6,0.4,0.35,0.3];
const lossList = [1.2,0.9,0.5,0.4,0.3];

const chartDom = document.getElementById('sigmaLossChart');
const myChart = echarts.init(chartDom);
const option = {
    title: { text: 'Sigma & Loss 曲线' },
    tooltip: { trigger: 'axis' },
    legend: { data: ['Sigma', 'Loss'] },
    xAxis: { type: 'category', data: epochs, name: 'Epoch' },
    yAxis: [
        { type: 'value', name: 'Sigma' },
        { type: 'value', name: 'Loss' }
    ],
    series: [
        {
            name: 'Sigma',
            type: 'line',
            yAxisIndex: 0,
            data: sigmaList
        },
        {
            name: 'Loss',
            type: 'line',
            yAxisIndex: 1,
            data: lossList
        }
    ]
};
myChart.setOption(option);
</script>

2.2 残差图(大数据量可选聚合)

原始散点图(小数据集)

<div id="residualChart" style="width: 800px; height: 400px;"></div>
<script>
const residuals = [0.2, -0.1, 0.5, -0.3]; // Python返回
const sigma = 0.25;

const xData = residuals.map((_, i) => i+1);
const myChart2 = echarts.init(document.getElementById('residualChart'));
const option2 = {
    title: { text: '残差散点图' },
    xAxis: { type: 'category', data: xData, name: '样本索引' },
    yAxis: { type: 'value', name: '残差' },
    series: [
        { type: 'scatter', data: residuals },
        { type: 'line', data: Array(xData.length).fill(sigma), lineStyle:{type:'dashed'}, symbol:'none' },
        { type: 'line', data: Array(xData.length).fill(-sigma), lineStyle:{type:'dashed'}, symbol:'none' }
    ]
};
myChart2.setOption(option2);
</script>

聚合后带标准差带状图(大数据集)

<div id="aggResidualChart" style="width: 800px; height: 400px;"></div>
<script>
const aggregatedResiduals = [
    {start:0,end:100,mean:0.1,std:0.3},
    {start:100,end:200,mean:0.05,std:0.25}
]; // Python返回

const xData3 = aggregatedResiduals.map((d,i) => i);
const meanData = aggregatedResiduals.map(d => d.mean);
const upperBound = aggregatedResiduals.map(d => d.mean + d.std);
const lowerBound = aggregatedResiduals.map(d => d.mean - d.std);

const myChart3 = echarts.init(document.getElementById('aggResidualChart'));
const option3 = {
    title: { text: '聚合残差带状图' },
    xAxis: { type: 'category', data: xData3 },
    yAxis: { type: 'value' },
    series: [
        {
            type: 'line',
            data: meanData,
            name: '平均残差'
        },
        {
            type: 'line',
            data: upperBound,
            lineStyle: { opacity: 0 },
            stack: 'conf',
            symbol: 'none'
        },
        {
            type: 'line',
            data: lowerBound,
            lineStyle: { opacity: 0 },
            areaStyle: { color: 'rgba(91,155,213,0.3)' },
            stack: 'conf',
            symbol: 'none'
        }
    ]
};
myChart3.setOption(option3);
</script>

以下是完整方案,支持 绝对残差百分比残差 两种计算方式,并在前端可切换显示模式。


1. Python 计算脚本

import numpy as np

def calculate_residuals(y_true, y_pred, use_percentage=False):
    """
    计算残差及其标准差
    :param y_true: 真实值数组
    :param y_pred: 预测值数组
    :param use_percentage: 是否使用百分比残差
    :return: sigma, residuals (list)
    """
    y_true = np.array(y_true)
    y_pred = np.array(y_pred)

    # 避免除零
    safe_y_true = np.where(y_true == 0, 1e-8, y_true)

    if use_percentage:
        residuals = ((y_true - y_pred) / safe_y_true) * 100
    else:
        residuals = y_true - y_pred

    sigma = float(np.std(residuals))
    return sigma, residuals.tolist()


def aggregate_residuals(residuals, bin_size=100):
    """
    聚合残差数据以减少前端绘图量
    """
    agg_data = []
    n_bins = len(residuals) // bin_size
    for i in range(n_bins):
        segment = residuals[i*bin_size:(i+1)*bin_size]
        agg_data.append({
            "mean": float(np.mean(segment)),
            "std": float(np.std(segment)),
            "start": i*bin_size,
            "end": (i+1)*bin_size
        })
    return agg_data


# ===== 模拟数据 =====
samples = 5000
y_true = np.linspace(1, 100, samples)  # 避免为零
y_pred = y_true + np.random.normal(0, 5, samples)

# 计算绝对残差
sigma_abs, residuals_abs = calculate_residuals(y_true, y_pred, use_percentage=False)
agg_abs = aggregate_residuals(residuals_abs)

# 计算百分比残差
sigma_pct, residuals_pct = calculate_residuals(y_true, y_pred, use_percentage=True)
agg_pct = aggregate_residuals(residuals_pct)

# 输出给前端
result = {
    "absolute": {
        "sigma": sigma_abs,
        "residuals": residuals_abs,
        "aggregatedResiduals": agg_abs
    },
    "percentage": {
        "sigma": sigma_pct,
        "residuals": residuals_pct,
        "aggregatedResiduals": agg_pct
    }
}

print(result.keys())  # dict_keys(['absolute', 'percentage'])

2. 前端 ECharts 切换显示模式

HTML & JS

<div>
    <button onclick="switchMode('absolute')">绝对值残差</button>
    <button onclick="switchMode('percentage')">百分比残差</button>
</div>
<div id="residualChart" style="width: 800px; height: 400px;"></div>

<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script>
const chartDom = document.getElementById('residualChart');
const myChart = echarts.init(chartDom);

// 模拟后端返回数据
const chartData = {
    absolute: {
        residuals: [0.5, -0.2, 1.2, -0.8],
        sigma: 0.7
    },
    percentage: {
        residuals: [1.5, -0.4, 2.1, -1.6],
        sigma: 1.3
    }
};

function renderChart(mode) {
    const residuals = chartData[mode].residuals;
    const sigma = chartData[mode].sigma;
    const xData = residuals.map((_, i) => i + 1);

    const yAxisName = mode === 'absolute' ? '残差' : '残差 (%)';

    const option = {
        title: { text: mode === 'absolute' ? '绝对值残差图' : '百分比残差图' },
        xAxis: { type: 'category', data: xData },
        yAxis: { type: 'value', name: yAxisName },
        series: [
            { type: 'scatter', data: residuals },
            { type: 'line', data: Array(xData.length).fill(sigma), lineStyle:{type:'dashed'}, symbol:'none' },
            { type: 'line', data: Array(xData.length).fill(-sigma), lineStyle:{type:'dashed'}, symbol:'none' }
        ]
    };
    myChart.setOption(option);
}

function switchMode(mode) {
    renderChart(mode);
}

// 默认显示绝对值残差
renderChart('absolute');
</script>

3. 方案说明

  • 后端:一次性计算并返回 absolutepercentage 两种模式的数据。
  • 前端:提供按钮切换,避免重复请求。
  • 聚合数据:可扩展为带均值+标准差带状图(大数据量优化)。

明白了,你指的是 ±1σ、±2σ、±3σ 区间 的统计控制图(类似质量管理中的控制图)。


1. Sigma 区间计算

残差区间计算公式:

Upperk=k×σ,Lowerk=−k×σ(k=1,2,3)\text{Upper}_k = k \times \sigma,\quad \text{Lower}_k = -k \times \sigma \quad (k = 1,2,3)

Python 示例:

import numpy as np

def calculate_sigma_bands(y_true, y_pred, use_percentage=False):
    y_true = np.array(y_true)
    y_pred = np.array(y_pred)
    safe_y_true = np.where(y_true == 0, 1e-8, y_true)

    residuals = ((y_true - y_pred) / safe_y_true) * 100 if use_percentage else (y_true - y_pred)
    sigma = np.std(residuals)

    # 计算1σ、2σ、3σ上下限
    bands = {
        "1sigma": {"upper": sigma, "lower": -sigma},
        "2sigma": {"upper": 2*sigma, "lower": -2*sigma},
        "3sigma": {"upper": 3*sigma, "lower": -3*sigma},
    }
    return residuals.tolist(), sigma, bands

2. ECharts 3σ 图示例

可运行 HTML

保存为 sigma_chart.html 并在浏览器打开:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>3σ 控制图</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
</head>
<body>
    <div id="sigmaChart" style="width: 900px; height: 400px;"></div>

    <script>
    const residuals = [0.5, -0.2, 1.2, -0.8, 0.4, -0.1, 0.7];  // 残差
    const sigma = 0.7;  // 标准差

    const xData = residuals.map((_, i) => i + 1);

    const option = {
        title: { text: '3σ 控制图' },
        tooltip: { trigger: 'axis' },
        xAxis: { type: 'category', data: xData, name: '样本索引' },
        yAxis: { type: 'value', name: '残差' },
        series: [
            { type: 'scatter', data: residuals, name: '残差', symbolSize: 6 },

            // 1σ 线
            { type: 'line', data: Array(xData.length).fill(sigma), lineStyle:{color:'#91cc75',type:'dashed'}, symbol:'none', name:'+1σ' },
            { type: 'line', data: Array(xData.length).fill(-sigma), lineStyle:{color:'#91cc75',type:'dashed'}, symbol:'none', name:'-1σ' },

            // 2σ 线
            { type: 'line', data: Array(xData.length).fill(2*sigma), lineStyle:{color:'#fac858',type:'dashed'}, symbol:'none', name:'+2σ' },
            { type: 'line', data: Array(xData.length).fill(-2*sigma), lineStyle:{color:'#fac858',type:'dashed'}, symbol:'none', name:'-2σ' },

            // 3σ 线
            { type: 'line', data: Array(xData.length).fill(3*sigma), lineStyle:{color:'#ee6666',type:'dashed'}, symbol:'none', name:'+3σ' },
            { type: 'line', data: Array(xData.length).fill(-3*sigma), lineStyle:{color:'#ee6666',type:'dashed'}, symbol:'none', name:'-3σ' },
        ]
    };

    const myChart = echarts.init(document.getElementById('sigmaChart'));
    myChart.setOption(option);
    </script>
</body>
</html>

3. 特点

  • 显示 ±1σ、±2σ、±3σ 的虚线参考线。
  • 残差点若超出 ±3σ,表示异常点。

明白了。我们需要绘制一个 Sigma 图,其中:

  • x 轴:绝对差值与实际值的百分比,即

    percentage_error=∣ypred−ytrue∣∣ytrue∣×100\text{percentage_error} = \frac{ y_{\text{pred}} - y_{\text{true}} }{ y_{\text{true}} } \times 100
  • y 轴:该百分比误差出现的概率(通过直方图估计)。

  • Sigma 线:基于误差分布的均值和标准差,绘制 ±nσ 范围(n 可为 3、4、5 等)。


完整可运行代码

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# 示例数据:真实值和预测值
np.random.seed(42)
y_true = np.random.uniform(50, 150, 1000)   # 真实值
y_pred = y_true + np.random.normal(0, 10, 1000)  # 预测值

# 计算百分比误差
percentage_error = np.abs(y_pred - y_true) / np.abs(y_true) * 100

# 绘制 Sigma 图函数
def plot_sigma_chart(errors, sigma_level=3, bins=20):
    """
    绘制 Sigma 图(带直方图和概率密度曲线)
    :param errors: 百分比误差数组
    :param sigma_level: sigma 范围(如3,4,5)
    :param bins: 直方图分箱数
    """
    # 计算均值和标准差
    mu, sigma = np.mean(errors), np.std(errors)
    
    # 直方图统计
    counts, bin_edges = np.histogram(errors, bins=bins, density=True)
    bin_centers = 0.5 * (bin_edges[1:] + bin_edges[:-1])
    
    # 高斯拟合曲线
    x = np.linspace(0, np.max(errors), 500)
    pdf = norm.pdf(x, mu, sigma)
    
    # 绘图
    plt.figure(figsize=(10, 6))
    plt.bar(bin_centers, counts, width=(bin_edges[1]-bin_edges[0]),
            alpha=0.6, color='skyblue', label='误差分布 (直方图)')
    plt.plot(x, pdf, 'r-', lw=2, label='正态拟合曲线')
    
    # 绘制 ±nσ 范围虚线
    for n in [-sigma_level, sigma_level]:
        plt.axvline(mu + n * sigma, color='green', linestyle='--', 
                    label=f'{n:+}σ 范围' if n > 0 else None)
    
    # 均值线
    plt.axvline(mu, color='black', linestyle='-', label='均值')
    
    # 标签和标题
    plt.xlabel('误差百分比 (%)')
    plt.ylabel('概率密度')
    plt.title(f'{sigma_level}-Sigma 图 (误差百分比分布)')
    plt.legend()
    plt.grid(True)
    plt.show()

# 绘制 3-Sigma 图
plot_sigma_chart(percentage_error, sigma_level=3, bins=20)

# 可切换为 4-Sigma、5-Sigma
# plot_sigma_chart(percentage_error, sigma_level=4, bins=20)
# plot_sigma_chart(percentage_error, sigma_level=5, bins=20)

代码说明

  1. percentage_error:计算预测值与真实值的绝对差的百分比。
  2. 直方图:用 np.histogram 获取频率分布,density=True 转换为概率密度。
  3. 正态曲线:用 scipy.stats.norm.pdf 绘制拟合曲线。
  4. Sigma 范围:用 ±nσ 在图中画出虚线。
  5. bins 参数可以控制柱状图数量(推荐 20 左右)。

理解了,你需要的是:

  • x轴:预测值与实际值之间的相对误差百分比 ((预测值 - 实际值) / 实际值) * 100
  • y轴:该误差出现的概率(直方图归一化)
  • 附加功能:绘制不同 σ 区间(如 1σ、2σ、3σ、4σ、5σ) 的范围线

完整代码(可直接运行)

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# 模拟数据(实际使用时替换为真实数据)
np.random.seed(42)
actual = np.random.uniform(50, 150, 1000)  # 实际值
predicted = actual + np.random.normal(0, 10, 1000)  # 预测值

# 计算相对误差百分比(可正可负)
relative_error_percent = (predicted - actual) / actual * 100

# 统计直方图数据
num_bins = 20  # 直方图柱数,可调整
counts, bin_edges = np.histogram(relative_error_percent, bins=num_bins, density=True)

# 计算每个柱的中心
bin_centers = 0.5 * (bin_edges[:-1] + bin_edges[1:])

# 估计误差分布的均值和标准差
mu, sigma = np.mean(relative_error_percent), np.std(relative_error_percent)

# 生成正态分布曲线
x = np.linspace(min(relative_error_percent), max(relative_error_percent), 500)
pdf = norm.pdf(x, mu, sigma)

# 绘图
plt.figure(figsize=(10, 6))

# 绘制直方图
plt.bar(bin_centers, counts, width=bin_edges[1]-bin_edges[0], alpha=0.6, color='skyblue', edgecolor='black', label='误差分布')

# 绘制正态分布曲线
plt.plot(x, pdf, 'r-', lw=2, label=f'正态分布拟合\nμ={mu:.2f}%, σ={sigma:.2f}%')

# 绘制多级sigma虚线
for s in [1, 2, 3, 4, 5]:  # 可自行调整sigma级数
    left, right = mu - s * sigma, mu + s * sigma
    plt.axvline(left, color='gray', linestyle='--', linewidth=1)
    plt.axvline(right, color='gray', linestyle='--', linewidth=1)
    plt.text(right, max(counts)*0.9, f'{s}σ', rotation=90, verticalalignment='top')

# 添加标签和图例
plt.xlabel('预测误差百分比 (%)')
plt.ylabel('概率密度')
plt.title('预测误差分布及多σ区间')
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()

主要特性

  1. x轴:误差百分比(带正负号,非绝对值)。
  2. y轴:概率密度,由 density=True 归一化保证。
  3. 可调整 sigma 级数:修改 for s in [1, 2, 3, 4, 5]: 即可。
  4. 直方图 + 正态拟合曲线 + 多级 σ 虚线

以下是基于你现有的 xy 数据绘制 可调 sigma 控制图 的完整示例,支持 等任意级别:


完整 Python 代码

import numpy as np
import matplotlib.pyplot as plt

def plot_sigma_chart(x, y, sigma_level=3):
    """
    使用已有的 x 和 y 数据绘制 sigma 控制图。
    
    参数:
        x: 自变量数据 (列表或 NumPy 数组)
        y: 因变量数据 (列表或 NumPy 数组)
        sigma_level: sigma 水平(如 3 表示 3σ,4 表示 4σ)
    """
    # 转换为 NumPy 数组以便计算
    x = np.array(x)
    y = np.array(y)

    # 计算均值和标准差
    mean_y = np.mean(y)
    std_y = np.std(y)

    # 计算上限线和下限线
    upper_limit = mean_y + sigma_level * std_y
    lower_limit = mean_y - sigma_level * std_y

    # 开始绘图
    plt.figure(figsize=(10, 6))
    plt.plot(x, y, 'bo-', label='Data')  # 数据点

    # 绘制均值线
    plt.axhline(y=mean_y, color='green', linestyle='-', linewidth=2, label='Mean')

    # 绘制上限线和下限线
    plt.axhline(y=upper_limit, color='red', linestyle='--', linewidth=1.5, label=f'+{sigma_level}σ')
    plt.axhline(y=lower_limit, color='red', linestyle='--', linewidth=1.5, label=f'-{sigma_level}σ')

    # 标题和标签
    plt.title(f'Sigma Chart ({sigma_level}σ)')
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.legend()
    plt.grid(True)
    plt.show()

# 示例:假设你已有训练数据 x 和 y
x = np.arange(1, 21)  # 训练数据的索引
y = np.random.normal(loc=50, scale=5, size=20)  # 模拟符合正态分布的训练结果

# 绘制 3σ 控制图
plot_sigma_chart(x, y, sigma_level=3)

# 可以修改 sigma_level 参数来绘制 4σ 或 5σ
# plot_sigma_chart(x, y, sigma_level=4)
# plot_sigma_chart(x, y, sigma_level=5)

代码特点

  1. 完全独立:可直接运行,无需重新指定 xy
  2. sigma 可调:通过 sigma_level 轻松切换 3σ、4σ、5σ。
  3. 适合线性回归训练结果:直接传入模型训练中的数据即可。

下面是一个可直接运行的 Python 脚本,用于绘制线性回归预测残差的 N-Sigma 控制图,并且支持调整为 3σ、4σ、5σ 等:


完整代码

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

# ========== 数据准备 ==========
np.random.seed(42)
X = np.linspace(0, 10, 100).reshape(-1, 1)  # 特征
y = 3 * X.flatten() + 5 + np.random.normal(0, 2, 100)  # 线性关系 + 噪声

# 切分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ========== 训练线性回归模型 ==========
model = LinearRegression()
model.fit(X_train, y_train)

# 预测
y_pred = model.predict(X_test)

# ========== 计算残差 ==========
residuals = y_test - y_pred
mean_residual = np.mean(residuals)
std_residual = np.std(residuals)

# ========== 可调参数:sigma 范围 ==========
sigma_level = 3  # 可改为 4、5 等

# 计算控制限
upper_limit = mean_residual + sigma_level * std_residual
lower_limit = mean_residual - sigma_level * std_residual

# ========== 绘制 N-Sigma 控制图 ==========
plt.figure(figsize=(10, 6))
plt.plot(residuals, marker='o', linestyle='-', label='Residuals')
plt.axhline(mean_residual, color='green', linestyle='--', label='Mean')
plt.axhline(upper_limit, color='red', linestyle='--', label=f'+{sigma_level}σ')
plt.axhline(lower_limit, color='red', linestyle='--', label=f'-{sigma_level}σ')

# 标记超出 Nσ 的点
outliers = np.where((residuals > upper_limit) | (residuals < lower_limit))[0]
plt.scatter(outliers, residuals[outliers], color='red', s=80, edgecolors='black', label='Outliers')

plt.title(f'{sigma_level}-Sigma Control Chart for Regression Residuals')
plt.xlabel('Sample Index')
plt.ylabel('Residual')
plt.legend()
plt.grid(True)
plt.show()

运行效果

  • 横轴是测试样本索引。
  • 纵轴是残差值。
  • 绿色虚线是残差均值,红色虚线是 ±Nσ 控制线。
  • 超出 ±Nσ 的点会被标记为红色点。

下面是一个可以直接运行的示例代码,用于绘制符合正态分布的线性回归残差数据的 3-Sigma 图,包含:

  • 柱状图(误差百分比直方图)
  • 累积分布曲线(σ线)
  • 3 条虚线(±1σ、±2σ、±3σ 位置)

Python 代码

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# ================================
# 1. 生成示例数据(线性回归残差)
# ================================
np.random.seed(42)
# 假设真实误差为正态分布 N(0, 0.1)
errors = np.random.normal(loc=0, scale=0.1, size=1000)

# 转换为绝对差值百分比 (例如预测值的 1 为基准)
abs_errors_pct = np.abs(errors) * 100

# ================================
# 2. 计算均值和标准差
# ================================
mean = np.mean(abs_errors_pct)
std = np.std(abs_errors_pct)

# ================================
# 3. 绘制直方图 + 累积分布曲线
# ================================
plt.figure(figsize=(10, 6))

# --- 直方图 ---
counts, bins, patches = plt.hist(
    abs_errors_pct, bins=20, density=True, alpha=0.6, color='skyblue', label='误差直方图'
)

# --- 计算正态分布拟合曲线 ---
x = np.linspace(0, max(abs_errors_pct) * 1.1, 500)
pdf = norm.pdf(x, mean, std)  # 概率密度函数
cdf = norm.cdf(x, mean, std)  # 累积分布函数 (σ线)

# --- 绘制概率密度曲线 ---
plt.plot(x, pdf, 'r-', lw=2, label='正态分布曲线')

# --- 绘制累积分布曲线 ---
plt.plot(x, cdf, 'g-', lw=2, label='累积分布曲线 (σ)')

# ================================
# 4. 绘制 1σ、2σ、3σ 虚线
# ================================
for i in range(1, 4):
    plt.axvline(mean + i * std, color='gray', linestyle='--', lw=1)
    plt.axvline(max(0, mean - i * std), color='gray', linestyle='--', lw=1)
    plt.text(mean + i * std, max(pdf)*0.8, f'+{i}σ', rotation=90)
    plt.text(max(0, mean - i * std), max(pdf)*0.8, f'-{i}σ', rotation=90)

# ================================
# 5. 图形美化
# ================================
plt.title('3-Sigma 误差分布图')
plt.xlabel('绝对差值百分比 (%)')
plt.ylabel('概率密度 / 累积分布')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()

运行说明

  1. 复制代码到 sigma_plot.py 或 Jupyter Notebook。

  2. 确保安装依赖:

    pip install numpy matplotlib scipy
    
  3. 运行后会显示包含 直方图 + 正态分布拟合曲线 + 累积分布 σ 线 的图。


以下是一个可复用的 Python 函数,可以直接传入残差数据,自动生成符合 3σ 原则 的分布图,并可选择是否绘制正态拟合曲线与累积分布曲线 (CDF)。


可复用函数

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

def plot_residuals_distribution(
    residuals,
    bins=30,
    show_fit=True,
    show_cdf=False,
    title="Residuals Distribution with 3σ Rule"
):
    """
    绘制残差分布图,包含 3σ 区间,可选正态拟合曲线和CDF曲线。

    参数:
        residuals (array-like): 残差数据
        bins (int): 直方图的柱数
        show_fit (bool): 是否显示正态分布拟合曲线
        show_cdf (bool): 是否显示累积分布曲线
        title (str): 图表标题
    """
    residuals = np.array(residuals)
    mean = np.mean(residuals)
    std = np.std(residuals)

    # 绘制直方图
    fig, ax1 = plt.subplots(figsize=(8, 5))
    n, bins_hist, patches = ax1.hist(residuals, bins=bins, density=True, alpha=0.6, color='skyblue')

    # 绘制正态拟合曲线
    if show_fit:
        x = np.linspace(min(residuals), max(residuals), 200)
        y = norm.pdf(x, mean, std)
        ax1.plot(x, y, 'r-', lw=2, label="Normal Fit")

    # 标记 1σ、2σ、3σ 区间
    for i, color in zip([1, 2, 3], ['g', 'orange', 'r']):
        ax1.axvline(mean + i*std, color=color, linestyle='--', label=f'+{i}σ')
        ax1.axvline(mean - i*std, color=color, linestyle='--', label=f'-{i}σ')

    ax1.set_title(title)
    ax1.set_xlabel("Residual")
    ax1.set_ylabel("Density")
    ax1.legend()

    # 可选:绘制累积分布曲线
    if show_cdf:
        ax2 = ax1.twinx()
        sorted_res = np.sort(residuals)
        cdf = np.arange(1, len(residuals)+1) / len(residuals)
        ax2.plot(sorted_res, cdf, 'k-', lw=1.5, label='CDF')
        ax2.set_ylabel("CDF")
        ax2.legend(loc='lower right')

    plt.show()

使用示例

# 示例残差数据
residuals = np.random.normal(0, 2, 1000)

# 仅绘制直方图+拟合曲线+3σ
plot_residuals_distribution(residuals)

# 绘制直方图+拟合曲线+CDF
plot_residuals_distribution(residuals, show_cdf=True)

下面提供了一个完整的 Python 示例,满足以下要求:

  • x 轴:差值与实际值的百分比(可为正负值)。
  • y 轴:概率密度(概率)。
  • 图形:柱状图(直方图)+ σ 范围的累计概率曲线(可选择 1σ、2σ、3σ、4σ、5σ)。
  • 直方图分箱:默认 20 个,可调整。

完整示例代码

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# 示例数据
np.random.seed(42)
actual = np.random.normal(100, 10, 1000)  # 实际值
predicted = actual + np.random.normal(0, 5, 1000)  # 预测值

# 计算差值百分比 (不取绝对值)
diff_percentage = (predicted - actual) / actual * 100

# 绘制 sigma 图函数
def plot_sigma_hist(diff_percentage, bins=20, sigma_level=3):
    """
    绘制包含直方图和 sigma 线的图像
    :param diff_percentage: 差值百分比数组
    :param bins: 直方图分箱数
    :param sigma_level: sigma 范围(例如 3 表示 ±3σ)
    """
    mean = np.mean(diff_percentage)
    std = np.std(diff_percentage)

    # 绘制直方图,density=True 使 y 轴为概率密度
    counts, bin_edges, _ = plt.hist(diff_percentage, bins=bins, density=True,
                                    alpha=0.6, color='skyblue', edgecolor='black',
                                    label='差值百分比直方图')

    # 生成平滑的 x 轴数据用于绘制正态分布曲线
    x = np.linspace(min(diff_percentage), max(diff_percentage), 1000)
    pdf = norm.pdf(x, mean, std)  # 正态分布概率密度函数

    plt.plot(x, pdf, 'r-', linewidth=2, label='正态分布拟合曲线')

    # 绘制 sigma 虚线
    for i in range(1, sigma_level + 1):
        lower = mean - i * std
        upper = mean + i * std
        plt.axvline(lower, color='gray', linestyle='--', linewidth=1)
        plt.axvline(upper, color='gray', linestyle='--', linewidth=1)
        plt.text(upper, max(pdf) * 0.9, f'+{i}σ', rotation=0, fontsize=8)
        plt.text(lower, max(pdf) * 0.9, f'-{i}σ', rotation=0, fontsize=8)

    plt.xlabel("差值百分比 (%)")
    plt.ylabel("概率密度")
    plt.title(f"差值百分比直方图及 ±{sigma_level}σ 范围")
    plt.legend()
    plt.grid(alpha=0.3)
    plt.show()

# 调用函数
plot_sigma_hist(diff_percentage, bins=20, sigma_level=4)

代码说明

  1. diff_percentage:直接计算 (predicted - actual) / actual * 100,保留正负值。
  2. 直方图:通过 plt.hist 绘制,density=True 使 y 轴为概率密度而非计数。
  3. sigma 虚线:根据均值和标准差绘制 ±1σ, ±2σ, …,数量可通过参数 sigma_level 调整。
  4. 拟合曲线:使用 scipy.stats.norm.pdf 绘制与数据匹配的正态分布曲线。

下面是将 Matplotlib 版本转换为 ECharts 版本的示例代码,并支持差值百分比、概率计算、柱状图与 sigma 线条(虚线)绘制。


1. 计算数据部分 (Python)

先使用 Python 处理数据,生成适合前端 ECharts 的 JSON 格式。

import numpy as np
import json

# 生成示例数据
np.random.seed(42)
actual = np.random.normal(100, 10, 500)  # 实际值
predicted = actual + np.random.normal(0, 5, 500)  # 预测值

# 计算差值百分比 (不取绝对值)
diff_percent = (predicted - actual) / actual * 100  # 差值百分比

# 计算直方图数据
num_bins = 20
counts, bin_edges = np.histogram(diff_percent, bins=num_bins, density=True)

# 转换为 ECharts 所需的格式 (柱状图数据)
bar_data = [{"value": round(c, 6), "bin": f"{round(bin_edges[i],2)}~{round(bin_edges[i+1],2)}"} 
            for i, c in enumerate(counts)]

# 生成 x 轴坐标(使用每个区间的中点)
x_data = [(bin_edges[i] + bin_edges[i+1]) / 2 for i in range(len(bin_edges)-1)]

# 计算 sigma 线条
sigma_levels = [1, 2, 3, 4, 5]  # 可动态选择
mean = np.mean(diff_percent)
std = np.std(diff_percent)

sigma_lines = {}
for s in sigma_levels:
    # 理论正态分布曲线
    x_line = np.linspace(mean - s * std, mean + s * std, 200)
    y_line = (1/(std*np.sqrt(2*np.pi))) * np.exp(-0.5*((x_line-mean)/std)**2)
    sigma_lines[s] = {"x": x_line.tolist(), "y": y_line.tolist()}

# 导出为 JSON 供前端使用
chart_data = {
    "x_data": [round(x, 3) for x in x_data],
    "bar_data": [round(c, 6) for c in counts],
    "sigma_lines": sigma_lines
}

with open("chart_data.json", "w") as f:
    json.dump(chart_data, f, indent=2)

输出文件 chart_data.json 的格式类似:

{
  "x_data": [-20.4, -18.3, ..., 21.7],
  "bar_data": [0.0012, 0.0035, ...],
  "sigma_lines": {
    "1": {"x": [...], "y": [...]},
    "2": {"x": [...], "y": [...]},
    ...
  }
}

2. ECharts 绘制部分 (HTML + JS)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Sigma 图</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
    <style>
        #main {width: 900px; height: 600px;}
    </style>
</head>
<body>
<div id="main"></div>
<script>
fetch('chart_data.json') // 加载 Python 生成的数据
  .then(response => response.json())
  .then(data => {
      var chartDom = document.getElementById('main');
      var myChart = echarts.init(chartDom);

      var option = {
          title: { text: '差值百分比概率分布 (Sigma 图)' },
          tooltip: { trigger: 'axis' },
          xAxis: { 
              type: 'category',
              data: data.x_data,
              name: '差值百分比(%)'
          },
          yAxis: {
              type: 'value',
              name: '概率密度'
          },
          series: [
              {
                  name: '概率分布',
                  type: 'bar',
                  data: data.bar_data,
                  barWidth: '60%',
                  itemStyle: { color: '#5470C6' }
              },
              ...Object.keys(data.sigma_lines).map(s => ({
                  name: s + 'σ',
                  type: 'line',
                  data: data.sigma_lines[s].x.map((x, i) => [x, data.sigma_lines[s].y[i]]),
                  lineStyle: { type: 'dashed' },
                  smooth: true
              }))
          ]
      };

      myChart.setOption(option);
  });
</script>
</body>
</html>

特点

  • bar_data 显示柱状图(概率分布)。
  • sigma_lines 以虚线显示不同 sigma 范围内的理论正态分布曲线。
  • 只需修改 sigma_levels 即可支持 4σ、5σ 等。

好的,以下是优化后的版本,确保:

  • 只有一条主曲线(正态分布拟合曲线)。
  • 通过虚线标记不同 σ 区间
  • 在 Tooltip 中显示区间的累计概率

1. Python 端计算数据

import numpy as np
import json
from scipy.stats import norm

# 模拟数据
np.random.seed(42)
actual = np.random.normal(100, 10, 500)
predicted = actual + np.random.normal(0, 5, 500)

# 差值百分比
diff_percent = (predicted - actual) / actual * 100

# 统计直方图数据
num_bins = 20
counts, bin_edges = np.histogram(diff_percent, bins=num_bins, density=True)
x_data = [(bin_edges[i] + bin_edges[i+1]) / 2 for i in range(len(bin_edges)-1)]

# 正态分布拟合
mean = np.mean(diff_percent)
std = np.std(diff_percent)
x_line = np.linspace(mean - 4*std, mean + 4*std, 200)
y_line = norm.pdf(x_line, mean, std)

# Sigma 区间概率
sigma_levels = [1, 2, 3, 4, 5]
sigma_lines = []
sigma_probs = []
for s in sigma_levels:
    left = mean - s * std
    right = mean + s * std
    prob = norm.cdf(right, mean, std) - norm.cdf(left, mean, std)
    sigma_lines.append(left)
    sigma_lines.append(right)
    sigma_probs.append({"sigma": s, "probability": round(prob, 4)})

chart_data = {
    "x_data": [round(x, 3) for x in x_data],
    "bar_data": [round(c, 6) for c in counts],
    "curve": {"x": x_line.tolist(), "y": y_line.tolist()},
    "sigma_lines": sigma_lines,
    "sigma_probs": sigma_probs
}

with open("chart_data.json", "w") as f:
    json.dump(chart_data, f, indent=2)

2. ECharts 前端 HTML

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Sigma 图</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
    <style>
        #main {width: 900px; height: 600px;}
    </style>
</head>
<body>
<div id="main"></div>
<script>
fetch('chart_data.json')
  .then(response => response.json())
  .then(data => {
      var chartDom = document.getElementById('main');
      var myChart = echarts.init(chartDom);

      var option = {
          title: { text: '差值百分比概率分布 (Sigma 图)' },
          tooltip: {
              trigger: 'axis',
              formatter: function(params) {
                  let info = '';
                  params.forEach(p => {
                      info += `${p.seriesName}: ${p.value[1]?.toFixed(4) || p.value.toFixed(4)}<br/>`;
                  });
                  info += '<br/>Sigma 区间概率:<br/>';
                  data.sigma_probs.forEach(sp => {
                      info += `${sp.sigma}σ: ${sp.probability}<br/>`;
                  });
                  return info;
              }
          },
          xAxis: { type: 'value', name: '差值百分比(%)' },
          yAxis: { type: 'value', name: '概率密度' },
          series: [
              {
                  name: '概率分布',
                  type: 'bar',
                  data: data.x_data.map((x,i)=>[x, data.bar_data[i]]),
                  barWidth: '60%',
                  itemStyle: { color: '#5470C6' }
              },
              {
                  name: '拟合曲线',
                  type: 'line',
                  smooth: true,
                  data: data.curve.x.map((x, i) => [x, data.curve.y[i]]),
                  lineStyle: { width: 2, color: 'red' }
              }
          ],
          markLine: {
              symbol: 'none',
              lineStyle: { type: 'dashed', color: 'black' },
              data: data.sigma_lines.map(v => ({ xAxis: v }))
          }
      };

      myChart.setOption(option);
  });
</script>
</body>
</html>

优化内容

  • 仅一条红色拟合曲线 (拟合曲线)。
  • 虚线 (markLine) 标记不同 σ 区间。
  • tooltip 中显示所有 σ 区间的累计概率。

是的,需要提供分桶优化方案,否则:

  • 数据点过多 会导致 ECharts 渲染性能下降(卡顿或浏览器崩溃)。
  • 报错 Cannot read properties of undefined (reading 'x') 通常是因为 Python 生成的 JSON 数据中某些值缺失或格式不一致(如 xyNone、NaN 或列表索引错误)。

分桶优化思路

  1. 分桶采样:将大规模数据分成固定数量的桶,每个桶只保留平均值或中位数。
  2. 分辨率控制:例如,最多保留 500~1000 个点,避免超过浏览器渲染能力。
  3. 数据格式校验:确保 JSON 中每个点都有 xy 且为数值类型。

Python 端计算 + 分桶优化示例

import numpy as np
import json

def generate_sigma_data(mean, std, size=10000, max_points=800):
    x = np.linspace(mean - 4*std, mean + 4*std, size)
    y = 1/(std * np.sqrt(2*np.pi)) * np.exp(-0.5*((x-mean)/std)**2)
    
    # 分桶采样
    if size > max_points:
        step = size // max_points
        x = x[::step]
        y = y[::step]
    
    data = [{"x": float(xi), "y": float(yi)} for xi, yi in zip(x, y)]
    return json.dumps(data, ensure_ascii=False)

print(generate_sigma_data(0, 1, size=50000))

前端 index.html 读取 JSON + 绘制 ECharts

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Sigma 曲线</title>
  <script src="https://cdn.jsdelivr.net/npm/echarts@5"></script>
</head>
<body>
  <div id="main" style="width:100%;height:500px;"></div>
  <script>
    fetch('sigma_data.json') // Python 输出保存为该文件
      .then(response => response.json())
      .then(data => {
        // 检查数据有效性
        data = data.filter(p => p.x !== undefined && p.y !== undefined);

        var chart = echarts.init(document.getElementById('main'));
        chart.setOption({
          title: { text: 'Sigma 曲线' },
          tooltip: { trigger: 'axis' },
          xAxis: { type: 'value' },
          yAxis: { type: 'value' },
          series: [{
            type: 'line',
            smooth: true,
            data: data.map(p => [p.x, p.y]),
            markLine: {
              symbol: 'none',
              lineStyle: { type: 'dashed' },
              data: [
                { xAxis: -1 }, // -1σ
                { xAxis: 1 },  // +1σ
                { xAxis: -2 },
                { xAxis: 2 }
              ]
            }
          }]
        });
      })
      .catch(err => console.error(err));
  </script>
</body>
</html>

关键优化点

  • 分桶采样 避免性能问题。
  • JSON 结构统一:确保 xy 都是浮点数且不为空。
  • ECharts markLine 用于绘制虚线标记 sigma 区间。

在 ECharts 中,你可以使用 markLine 或者额外的 series(带 type: 'line' 和虚线样式)来绘制 sigma 竖线,同时结合 tooltiplabel 来显示概率数值提示。以下是完整示例:


1. 前端 ECharts 配置示例

var chart = echarts.init(document.getElementById('main'));

var xData = [...]; // 由 Python 计算返回
var yData = [...]; // 概率密度曲线数据

chart.setOption({
    title: { text: 'Sigma 曲线示例' },
    tooltip: {
        trigger: 'axis',
        formatter: function (params) {
            let point = params[0]; // 曲线点
            return `x: ${point.value[0].toFixed(2)}<br/>概率: ${point.value[1].toFixed(4)}`;
        }
    },
    xAxis: { type: 'value', name: 'X' },
    yAxis: { type: 'value', name: '概率密度' },
    series: [
        {
            name: 'Sigma 曲线',
            type: 'line',
            data: xData.map((x, i) => [x, yData[i]]),
            smooth: true,
            lineStyle: { color: 'blue' },
            markLine: {
                symbol: 'none',
                lineStyle: {
                    type: 'dashed',
                    color: 'red'
                },
                label: {
                    formatter: function (params) {
                        // 自定义竖线提示
                        return `Sigma: ${params.value}`;
                    }
                },
                data: [
                    { xAxis: -1 }, // -1 sigma
                    { xAxis: 1 },  // 1 sigma
                    { xAxis: 2 },  // 2 sigma
                ]
            }
        }
    ]
});

2. 关键点说明

  1. sigma 竖线
    • 通过 series.markLine.data 添加竖线。
    • 每个对象 {xAxis: value} 表示一条竖线。
  2. 概率提示
    • 使用 tooltip.formatter 获取当前曲线点概率值。
    • 也可通过 markPointmarkLine.label 显示固定文本。
  3. 虚线样式
    • lineStyle.type = 'dashed'

3. 结合 Python 计算

Python 返回的 JSON 数据应包含:

{
  "xData": [...],
  "yData": [...],
  "sigmaLines": [-1, 1, 2]
}

前端直接替换 markLine.data


可以使用 ECharts 的 markLine + markPoint + graphic 组件,将概率值直接显示在对应 σ 区域之间,而不依赖 tooltip。


方案:使用 graphic 在图上标注数值

核心思路

  1. linescatter 系列绘制分布曲线。
  2. 使用 markLine 绘制 σ 分界虚线(±1σ, ±2σ, ±3σ)。
  3. 使用 graphic 在图表上固定显示概率值,不随鼠标移动。

示例代码(带直接概率显示)

const sigmaValues = [
    {x: -1, prob: '34.1%'},
    {x: 1, prob: '34.1%'},
    {x: -2, prob: '13.6%'},
    {x: 2, prob: '13.6%'},
    {x: -3, prob: '2.1%'},
    {x: 3, prob: '2.1%'},
];

const option = {
    title: { text: '正态分布与 Sigma 区间' },
    tooltip: { show: false }, // 不使用 tooltip
    xAxis: { type: 'value', min: -4, max: 4 },
    yAxis: { type: 'value' },
    series: [
        {
            name: 'Normal Curve',
            type: 'line',
            smooth: true,
            data: generateNormalDistribution(), // 由 Python 计算并传入
            markLine: {
                symbol: 'none',
                lineStyle: { type: 'dashed', color: '#999' },
                data: [
                    {xAxis: -1}, {xAxis: 1},
                    {xAxis: -2}, {xAxis: 2},
                    {xAxis: -3}, {xAxis: 3}
                ]
            }
        }
    ],
    graphic: sigmaValues.map(item => ({
        type: 'text',
        left: `${50 + (item.x + 4) * 100 / 8}%`, // 位置换算
        top: '40%',
        style: {
            text: item.prob,
            fill: '#000',
            font: '14px sans-serif',
            textAlign: 'center'
        }
    }))
};

关键说明

  • graphic 用于直接在图上显示数值,lefttop 可通过比例换算到 x、y 坐标。
  • 若需要动态计算坐标,可通过 myChart.convertToPixel() 在图表渲染后更新位置。
  • Python 端只需提供 sigmaValues 的 x 坐标和概率值。

可选优化

  • 分桶优化:Python 端减少曲线点数,避免 JSON 过大。
  • 坐标自动映射:监听 chart.on('finished') 事件,用像素映射调整文字位置。

可以通过 markLine 绘制竖线,再用 graphic 组件 在图表内直接渲染文字,避免 tooltip。以下是示例代码(假设已有正态分布曲线数据):


1. 计算概率区间及位置

import numpy as np
from scipy.stats import norm

mu, sigma = 0, 1  # 均值和标准差
x = np.linspace(-4, 4, 500)
y = norm.pdf(x, mu, sigma)

# sigma 区间
sigmas = [1, 2, 3]
prob_values = []
for s in sigmas:
    left, right = mu - s * sigma, mu + s * sigma
    prob = norm.cdf(right, mu, sigma) - norm.cdf(left, mu, sigma)
    prob_values.append({"sigma": s, "prob": round(prob * 100, 2), "pos": right})

2. 生成 ECharts 数据

chart_data = {
    "xAxis": {"type": "value"},
    "yAxis": {"type": "value"},
    "series": [{
        "type": "line",
        "data": list(map(list, zip(x.tolist(), y.tolist()))),
        "smooth": True
    }],
    "markLine": {
        "symbol": "none",
        "lineStyle": {"type": "dashed"},
        "data": [
            {"xAxis": mu - s * sigma} for s in sigmas
        ] + [
            {"xAxis": mu + s * sigma} for s in sigmas
        ]
    }
}

3. 添加文字标签(不使用 Tooltip)

let option = JSON.parse('');

// 使用 graphic 绘制文本
option.graphic = [
    
];

关键点

  1. convertToPixel 将数据坐标转换为像素位置(需在 myChart.on('finished') 中调用更新)。
  2. graphic 组件用于绘制文字,而非 tooltip。
  3. 文字可放在顶部或竖线附近,位置通过计算得到。

好的,下面给出一个 ECharts 实现 Sigma 区域概率值显示 的方案,直接在图上显示,不依赖 tooltip。


核心思路

  1. 使用 linescatter 系列绘制原始曲线;
  2. 使用 markLine 绘制 σ 区域竖线;
  3. 使用 graphicmarkPoint 在竖线之间放置概率文本。

示例代码

var sigmaValues = [1, 2, 3];  // 1σ, 2σ, 3σ
var sigmaProbabilities = ['68.27%', '95.45%', '99.73%']; // 对应概率
var sigmaPositions = [10, 20, 30]; // x轴位置,可替换为动态计算

var option = {
  title: {
    text: 'Sigma 区域概率分布',
    left: 'center'
  },
  xAxis: { type: 'value' },
  yAxis: { type: 'value' },
  series: [
    {
      type: 'line',
      smooth: true,
      data: [...Array(100).keys()].map(x => [x, Math.exp(-(x-50)*(x-50)/200)]),
      markLine: {
        symbol: 'none',
        lineStyle: { color: '#aaa', type: 'dashed' },
        data: sigmaPositions.map(x => ({ xAxis: x }))
      }
    }
  ],
  graphic: sigmaProbabilities.map((p, i) => ({
    type: 'text',
    left: (sigmaPositions[i] + sigmaPositions[i-1] || 0) / 2, // 区域中点
    top: 50, // 调整到合适高度
    style: {
      text: p,
      fill: '#333',
      font: 'bold 14px sans-serif'
    }
  }))
};

myChart.setOption(option);

关键点

  • graphic 可在任意位置绘制文本,直接显示在图表上;
  • lefttop 可以使用函数根据坐标轴位置计算;
  • 可结合 chart.convertToPixel 方法让文本随坐标系缩放。

下面提供一个完整的 ECharts 配置示例,直接在浏览器中运行即可,展示正态分布曲线,并在 ±1σ、±2σ、±3σ 区域之间显示对应概率值(不使用 tooltip,而是直接在图中显示文本标签):


HTML + JavaScript 示例

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>正态分布 Sigma 图</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
    <style>
        #main {
            width: 1000px;
            height: 600px;
        }
    </style>
</head>
<body>
    <div id="main"></div>
    <script>
        // 生成正态分布数据
        function gaussian(x, mean, sigma) {
            return (1 / (sigma * Math.sqrt(2 * Math.PI))) *
                Math.exp(-Math.pow(x - mean, 2) / (2 * sigma * sigma));
        }

        let mean = 0;
        let sigma = 1;
        let xData = [];
        let yData = [];
        for (let x = -4; x <= 4; x += 0.1) {
            xData.push(x.toFixed(1));
            yData.push(gaussian(x, mean, sigma));
        }

        // σ 区域概率
        const sigmaLabels = [
            {pos: 0.5, text: '68.27% (±1σ)'},
            {pos: 1.5, text: '95.45% (±2σ)'},
            {pos: 2.5, text: '99.73% (±3σ)'}
        ];

        let chartDom = document.getElementById('main');
        let myChart = echarts.init(chartDom);

        let option = {
            title: {
                text: '正态分布曲线及Sigma区域',
                left: 'center'
            },
            xAxis: {
                type: 'value',
                min: -4,
                max: 4,
                name: 'X',
            },
            yAxis: {
                type: 'value',
                name: 'Probability Density'
            },
            series: [
                {
                    type: 'line',
                    data: xData.map((x, i) => [parseFloat(x), yData[i]]),
                    smooth: true,
                    lineStyle: {
                        width: 2,
                        color: '#5470C6'
                    },
                    areaStyle: {
                        opacity: 0.2
                    }
                }
            ],
            graphic: [
                // 竖线 ±1σ、±2σ、±3σ
                ...[1, 2, 3, -1, -2, -3].map(s => ({
                    type: 'line',
                    left: myChart.convertToPixel({xAxisIndex: 0}, s),
                    shape: {x1: 0, y1: 0, x2: 0, y2: 400},
                    style: {stroke: '#FF6B6B', lineWidth: 1.5},
                    silent: true
                })),
                // 概率标签
                ...sigmaLabels.map(s => ({
                    type: 'text',
                    left: myChart.convertToPixel({xAxisIndex: 0}, s.pos),
                    top: 100 + s.pos * 20,
                    style: {
                        text: s.text,
                        fill: '#333',
                        font: '14px sans-serif'
                    }
                }))
            ]
        };

        myChart.setOption(option);

        // 确保缩放时 graphic 更新位置
        window.addEventListener('resize', () => {
            myChart.setOption(option);
        });
    </script>
</body>
</html>

关键特性

  1. 竖线:使用 graphic 组件绘制,分别对应 ±1σ、±2σ、±3σ。
  2. 概率值文本:直接通过 graphic.text 绘制在区域上方,不依赖 tooltip。
  3. 数据生成:通过高斯函数生成曲线。

你遇到的错误:

Cannot read properties of undefined (reading 'queryComponents')

通常是因为 ECharts 渲染过程中 series 或 graphic 元素的定义不完整,或直接在 option 里访问了还未初始化的组件。


直接在图上标注 σ 区域概率值的完整示例

下面给出一个可直接运行的示例,使用 ECharts + graphic 元素 在高斯分布图上标注 1σ、2σ、3σ 区域对应的概率值。

<!DOCTYPE html>
<html style="height: 100%">
<head>
  <meta charset="utf-8">
  <title>ECharts Gaussian Sigma 区域标注</title>
  <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
</head>
<body style="height: 100%; margin: 0">
  <div id="main" style="height: 100%"></div>
  <script>
    // 计算高斯分布 y 值
    function gaussian(x, mean = 0, std = 1) {
      return Math.exp(-0.5 * Math.pow((x - mean) / std, 2)) / (std * Math.sqrt(2 * Math.PI));
    }

    const xData = [];
    const yData = [];
    for (let i = -4; i <= 4; i += 0.01) {
      xData.push(i);
      yData.push(gaussian(i));
    }

    // 定义 sigma 区间及概率值
    const sigmaRegions = [
      { range: [-1, 1], color: 'rgba(255, 215, 0, 0.4)', text: '68.27%' },
      { range: [-2, 2], color: 'rgba(50, 205, 50, 0.3)', text: '95.45%' },
      { range: [-3, 3], color: 'rgba(100, 149, 237, 0.3)', text: '99.73%' }
    ];

    const option = {
      title: { text: '高斯分布及 σ 区域概率', left: 'center' },
      tooltip: { trigger: 'axis' },
      xAxis: { type: 'value', min: -4, max: 4 },
      yAxis: { type: 'value' },
      series: [
        {
          type: 'line',
          data: xData.map((x, i) => [x, yData[i]]),
          smooth: true,
          lineStyle: { width: 2 }
        }
      ],
      graphic: sigmaRegions.flatMap(region => [
        {
          type: 'rect',
          shape: {
            x: (region.range[0] + 4) / 8 * window.innerWidth,
            y: 0,
            width: (region.range[1] - region.range[0]) / 8 * window.innerWidth,
            height: window.innerHeight
          },
          style: { fill: region.color },
          silent: true
        },
        {
          type: 'text',
          style: {
            text: region.text,
            x: ((region.range[0] + region.range[1]) / 2 + 4) / 8 * window.innerWidth,
            y: window.innerHeight * 0.2,
            textAlign: 'center',
            textVerticalAlign: 'middle',
            font: 'bold 16px Arial',
            fill: '#000'
          }
        }
      ])
    };

    const chartDom = document.getElementById('main');
    const myChart = echarts.init(chartDom);
    myChart.setOption(option);

    // 监听 resize 更新 graphic 元素
    window.addEventListener('resize', () => {
      myChart.resize();
      myChart.setOption(option); // 重新应用,避免尺寸错位
    });
  </script>
</body>
</html>

关键点

  1. 使用 graphic 手动绘制 rect + text,直接在画布上显示概率值,而非 tooltip。
  2. shape 中的 xywidthheight 需根据容器宽高动态计算。
  3. 使用 flatMap 将多个区域的矩形与文字元素一次性添加。

以下是一个可直接运行的 ECharts 代码示例,实现了:

  • 在均值处绘制竖线。
  • 在 ±1σ、±2σ、±3σ 处绘制竖线。
  • 在竖线旁边标注对应区域的概率值,避免重叠。

HTML 文件(直接运行)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Gaussian with Sigma Regions</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
    <style>
        #main {
            width: 900px;
            height: 600px;
            margin: auto;
        }
    </style>
</head>
<body>
    <div id="main"></div>
    <script>
        var chartDom = document.getElementById('main');
        var myChart = echarts.init(chartDom);

        // 生成正态分布数据
        function gaussian(x, mean, sigma) {
            return Math.exp(-Math.pow(x - mean, 2) / (2 * sigma * sigma)) / (sigma * Math.sqrt(2 * Math.PI));
        }

        var mean = 0;
        var sigma = 1;
        var data = [];
        for (var x = -4; x <= 4; x += 0.05) {
            data.push([x, gaussian(x, mean, sigma)]);
        }

        // 区域概率值
        var sigmaRegions = [
            {sigma: 1, prob: '68.27%'},
            {sigma: 2, prob: '95.45%'},
            {sigma: 3, prob: '99.73%'}
        ];

        // 生成竖线标记
        var markLines = [
            {
                xAxis: mean,
                label: { formatter: 'Mean' },
                lineStyle: { color: 'black', type: 'solid' }
            }
        ];
        sigmaRegions.forEach(function(region, index) {
            markLines.push({
                xAxis: mean + region.sigma,
                label: {
                    formatter: region.prob,
                    position: index % 2 === 0 ? 'top' : 'bottom',
                    distance: 12 + index * 5 // 防止重叠
                },
                lineStyle: { color: '#666', type: 'dashed' }
            });
            markLines.push({
                xAxis: mean - region.sigma,
                label: {
                    formatter: region.prob,
                    position: index % 2 === 0 ? 'bottom' : 'top',
                    distance: 12 + index * 5
                },
                lineStyle: { color: '#666', type: 'dashed' }
            });
        });

        var option = {
            title: {
                text: 'Normal Distribution with Sigma Regions'
            },
            tooltip: { trigger: 'axis' },
            xAxis: { type: 'value' },
            yAxis: { type: 'value' },
            series: [
                {
                    type: 'line',
                    data: data,
                    smooth: true,
                    markLine: {
                        symbol: 'none',
                        data: markLines
                    }
                }
            ]
        };

        myChart.setOption(option);
    </script>
</body>
</html>

功能特点

  • 每条 σ 竖线分别绘制,且通过 distanceposition 调整标签避免重叠。
  • 中心线显示均值标签 “Mean”。
  • 代码可直接运行,无需额外依赖。

明白了,你希望 在每个 sigma 区间上方显示横向标注线(水平线)来表示区间范围,并且在中间标注概率,这就不是单纯用 markLine 竖线能做到的,而是需要用 额外的 series + line 或 markLine 配置 来画 横向的标注线

下面我给你一个完整示例,实现:

  1. ±1σ、±2σ、±3σ 竖线
  2. ±σ 区间上方 横向标注线
  3. 区间 中间标注概率
  4. 标注不会叠加
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sigma Chart with Horizontal Markers</title>
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
</head>
<body>
<div id="sigmaChart" style="width: 900px; height: 450px;"></div>
<script>
const mean = 0;
const std = 1;
const data = [];
const step = 0.1;

// 生成正态分布数据
for (let x = mean - 4*std; x <= mean + 4*std; x += step) {
    const y = (1 / (std * Math.sqrt(2 * Math.PI))) * Math.exp(-0.5 * ((x - mean)/std)**2);
    data.push([x, y]);
}

const sigmaChart = echarts.init(document.getElementById('sigmaChart'));

// Sigma区间
const sigmaIntervals = [
    { start: mean - 1*std, end: mean + 1*std, prob: '68.27%' },
    { start: mean - 2*std, end: mean + 2*std, prob: '95.45%' },
    { start: mean - 3*std, end: mean + 3*std, prob: '99.73%' }
];

// 生成横向标注线 series
const horizontalLines = sigmaIntervals.map((interval, index) => {
    const y = 0.25 - index*0.05; // 调整横线 y 位置
    return {
        type: 'line',
        data: [
            [interval.start, y],
            [interval.end, y]
        ],
        lineStyle: { color: 'green', width: 2 },
        markLine: { show: false },
        label: { show: false }
    };
});

// 横向标注文字 series
const horizontalLabels = sigmaIntervals.map((interval, index) => {
    const y = 0.25 - index*0.05;
    return {
        type: 'scatter',
        data: [{
            value: [(interval.start + interval.end)/2, y],
            label: { show: true, formatter: interval.prob, position: 'top', fontWeight: 'bold' }
        }],
        symbolSize: 0
    };
});

const option = {
    title: { text: 'Sigma Chart with Horizontal Markers' },
    tooltip: { trigger: 'axis' },
    xAxis: { type: 'value', name: 'x' },
    yAxis: { type: 'value', name: 'Probability Density' },
    series: [
        {
            type: 'line',
            smooth: true,
            data: data,
            name: 'Normal Distribution',
            lineStyle: { color: '#5470c6' },
            markLine: {
                symbol: 'none',
                lineStyle: { type: 'dashed', color: 'red' },
                data: [
                    { xAxis: mean - 1*std },
                    { xAxis: mean + 1*std },
                    { xAxis: mean - 2*std },
                    { xAxis: mean + 2*std },
                    { xAxis: mean - 3*std },
                    { xAxis: mean + 3*std }
                ]
            }
        },
        ...horizontalLines,
        ...horizontalLabels
    ]
};

sigmaChart.setOption(option);
</script>
</body>
</html>

✅ 特点:

  1. 竖线:通过 markLine 绘制 ±1σ、±2σ、±3σ。
  2. 横向标注线:绿色实线,覆盖每个 sigma 区间。
  3. 概率文字:横向标注线中间显示,避免叠加,可自适应调整 y 值。
  4. 折线图平滑显示,清楚展示概率密度。