
功能说明:散点观测样本,同时绘制一阶(线性)、二阶、三阶多项式回归拟合曲线 ;底层依靠
mathjs矩阵库实现最小二乘多项式回归计算。 原代码依赖mathjs做矩阵转置、求逆、矩阵乘法;下面提供完整可直接运行HTML,附带详细解析。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>散点图‑多项式回归拟合</title>
<!-- mathjs矩阵运算库 -->
<script src="https://cdn.jsdelivr.net/npm/mathjs@11.8.0/lib/browser/math.min.js"></script>
<!-- Highcharts官方CDN -->
<script src="https://code.highcharts.com/highcharts.js"></script>
<style>
#container {
width: 100%;
height: 620px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
function generateChart(dataset) {
Highcharts.chart('container', {
title: {
text: '散点图与多项式回归拟合曲线'
},
subtitle: {
text: '最小二乘法:一阶、二阶、三阶多项式对比'
},
xAxis: {
title: {
text: 'X变量'
},
minPadding: 0.1,
maxPadding: 0.1
},
yAxis: {
title: {
text: 'Y变量'
}
},
tooltip: {
shared: false
},
series: [{
type: 'scatter',
name: '观测样本点',
data: dataset,
marker: {
radius: 4
}
}, {
type: 'spline',
name: '一阶回归(线性)',
data: generateLineData(linearRegression(dataset, 1), -3, 5, 10),
marker: {
enabled: false
},
states: {
hover: {
lineWidth: 0
}
},
enableMouseTracking: false
}, {
type: 'spline',
name: '二阶回归(二次多项式)',
data: generateLineData(linearRegression(dataset, 2), -3, 5, 10),
marker: {
enabled: false
},
states: {
hover: {
lineWidth: 0
}
},
enableMouseTracking: false
}, {
type: 'spline',
name: '三阶回归(三次多项式)',
data: generateLineData(linearRegression(dataset, 3), -3, 5, 10),
marker: {
enabled: false
},
states: {
hover: {
lineWidth: 0
}
},
enableMouseTracking: false
}],
credits: {
enabled: false
}
});
}
const dataset = [
[4.648, 2.013],
[4.583, 1.354],
[-2.548, 1.066],
[-2.321, -0.733],
[3.684, 1.013],
[2.888, -0.539],
[2.358, 1.496],
[-0.535, 1.718],
[1.848, -0.462],
[1.854, 2.748],
[1.65, 3.253],
[-1.733, 2.058],
[0.445, 2.586],
[0.148, 1.168],
[2.784, 1.399],
[4.959, 4.581],
[4.595, 3.141],
[1.353, 2.451],
[0.559, 2.402],
[-0.854, 0.831],
[-2.713, 0.781],
[-2.78, -1.127],
[0.719, 0.905],
[-0.452, 3.767],
[0.04, 2.959],
[4.134, 1.68],
[1.206, 1.339],
[1.484, 1.781],
[-1.111, 1.82],
[-2.809, -0.987],
[-0.399, 2.752],
[-1.906, 0.949],
[1.082, 1.394],
[4.989, 4.606],
[2.396, 0.42],
[-1.545, 1.738],
[4.149, 2.807],
[3.374, 1.321],
[2.875, 0.939],
[4.253, 3.535],
[3.103, -0.248],
[3.318, 2.644],
[-0.17, 1.078],
[4.848, 3.636],
[4.695, 2.203],
[-1.711, 1.126],
[3.032, -0.522],
[2.721, 0.315],
[0.691, 2.694],
[1.243, 2.708],
[0.92, 2.536],
[4.399, 2.117],
[1.007, 2.395],
[3.652, 1.265],
[-0.169, 2.138],
[4.063, 1.791],
[4.198, 1.705],
[0.688, 3.712],
[1.542, 1.832],
[4.363, 1.436],
[2.79, 0.954],
[0.893, 1.342],
[-1.226, 3.519],
[-0.403, 2.466],
[2.597, -0.78],
[-1.671, 0.765],
[4.264, 2.736],
[-0.855, 3.988],
[4.291, 2.888],
[-0.523, 2.865],
[4.659, 3.201],
[2.65, 2.046],
[1.034, 0.55],
[1.142, 1.522],
[2.211, 1.456],
[1.704, 2.286],
[-0.505, 3.947],
[-1.337, 1.281],
[1.095, 1.113],
[4.473, 1.199],
[1.986, 2.308],
[-2.397, 1.838],
[3.563, 1.649],
[2.808, 1.676],
[4.261, 0.631],
[-1.469, 2.266],
[2.958, 0.901],
[-2.53, 0.325],
[2.223, 1.89],
[-0.815, 2.656],
[-1.187, 2.236],
[4.004, 1.712],
[-2.15, -0.832],
[1.179, 2.359],
[3.832, 2.834],
[-1.041, 3.408],
[-1.316, 1.606],
[4.045, 1.696],
[0.383, 3.496],
[2.736, 0.766]
];
/**
* @param dataset 二维样本数组 [[x,y]]
* @param nDegrees 多项式阶数 1=线性,2=二次,3=三次
* @returns 回归系数数组 [a0,a1,a2...] y = a0 + a1*x +a2*x² ...
*/
function linearRegression(dataset, nDegrees) {
const x = dataset.map(val => val[0]);
const y = dataset.map(val => val[1]);
const X = [];
// 构造范德蒙德矩阵
for (let i = 0; i <= nDegrees; i++) {
X.push(x.map(num => num ** i));
}
// 最小二乘矩阵求解:b = (X·Xᵀ)⁻¹ · X · y
const Xt = math.transpose(X);
const XtX = math.multiply(X, Xt);
const invXtX = math.inv(XtX);
const Xty = math.multiply(y, Xt);
const b = math.multiply(Xty, invXtX);
return b;
}
/**
* 根据回归系数生成拟合曲线采样点
* @param constants 回归系数
* @param start x起始
* @param end x结束
* @param resolution 采样密度
* @returns [[x,y]]
*/
function generateLineData(constants, start, end, resolution) {
const outData = [];
for (let x = start; x <= end; x += 1 / resolution) {
let y = 0;
for (let j = 0; j < constants.length; j++) {
y += constants[j] * (x ** j);
}
outData.push([x, y]);
}
return outData;
}
generateChart(dataset);
</script>
</body>
</html>
代码模块讲解
1、图表层 generateChart()
scatter:原始观测散点样本;- 3组
spline:分别渲染一/二/三阶多项式拟合曲线; enableMouseTracking:false:关闭曲线鼠标交互,避免干扰散点tooltip;states.hover.lineWidth:0:鼠标悬浮不改变曲线粗细。
2、linearRegression() 多项式回归函数
采用矩阵最小二乘法求解多项式系数:
- 拆分样本x、y;
- 构建范德蒙德矩阵 X;
- 公式: B=(XXT)−1Xy,使用
mathjs完成矩阵转置、相乘、求逆; - 返回多项式系数数组 a0,a1,a2...an,对应 y=a0+a1x+a2x2+...。
⚠️依赖说明:该算法强依赖 mathjs 做矩阵求逆;不能移除,否则矩阵运算报错。
3、generateLineData() 拟合曲线采样函数
根据得到多项式系数,在区间[start ~ end]按分辨率采样,生成Highcharts可以渲染的[[x,y]]曲线数组。
4、数据集 dataset
二维数组格式 [[x,y], [x,y]],每一组代表一个观测样本。
业务与可视化要点
- 过拟合现象:阶数越高,曲线对样本贴合越强,但容易过拟合;三阶曲线波动会大于线性。
- 适用场景:相关性分析、实验数据拟合、统计分析大屏。
- 扩展方向:
- 增加R²拟合优度计算展示;
- 异常点标记(离群点高亮marker);
- 增加下拉切换多项式阶数;
- 导出拟合系数。
常见坑
- 缺少mathjs库:直接报
window.math is undefined; - x范围设置不合理:
generateLineData(start, end)区间必须覆盖样本x范围; - 矩阵奇异:当样本高度共线,矩阵求逆报错,需要对数据预处理。