// 函数拟合工具 - 回归分析模块
// 页面加载完成后执行初始化
document.addEventListener('DOMContentLoaded', function() {
// 初始化回归分析功能
initRegressionAnalysis();
// 初始化回归分析表格
initRegressionTable();
// 初始化输入模式切换
initInputModeToggle();
});
/**
* 初始化回归分析功能
*/
function initRegressionAnalysis() {
const runButton = document.getElementById('run-regression');
const regressionType = document.getElementById('regression-type');
const polynomialDegree = document.getElementById('polynomial-degree');
const regressionData = document.getElementById('regression-data');
const tableInputMode = document.getElementById('table-input-mode');
const csvInputMode = document.getElementById('csv-input-mode');
// 监听回归类型变化,显示/隐藏多项式次数选择
regressionType.addEventListener('change', function() {
const degreeContainer = document.getElementById('polynomial-degree-container');
if (this.value === 'polynomial') {
degreeContainer.style.display = 'block';
} else {
degreeContainer.style.display = 'none';
}
});
runButton.addEventListener('click', function() {
let data;
// 根据当前激活的输入模式获取数据
if (tableInputMode.classList.contains('active')) {
// 从表格获取数据
data = getDataFromTable();
} else if (csvInputMode.classList.contains('active')) {
// 从CSV文本框获取数据
data = parseCSVData(regressionData.value);
}
// 检查数据是否有效
if (!data || data.X.length === 0) {
alert('请输入有效的数据');
return;
}
// 检查数据点是否足够
if (data.X.length < 3) {
alert('请至少输入三个有效的数据点');
return;
}
// 根据选择的回归类型进行分析
let result;
if (regressionType.value === 'linear') {
result = runMultipleLinearRegression(data.X, data.y);
} else if (regressionType.value === 'polynomial') {
const degree = parseInt(polynomialDegree.value);
result = runPolynomialRegression(data.X, data.y, degree);
}
// 显示回归结果
displayRegressionResult(result, data);
});
}
/**
* 解析CSV格式的数据
* @param {string} csvText - CSV格式的文本数据
* @returns {Object} 解析后的数据对象,包含X(自变量矩阵)和y(因变量向量)
*/
function parseCSVData(csvText) {
if (!csvText.trim()) {
return null;
}
try {
// 按行分割
const lines = csvText.trim().split('\n');
// 检查是否有足够的行
if (lines.length < 2) {
throw new Error('数据行数不足');
}
// 解析数据
const X = [];
const y = [];
for (let i = 0; i < lines.length; i++) {
const values = lines[i].split(',').map(val => parseFloat(val.trim()));
// 检查是否所有值都是有效数字
if (values.some(isNaN)) {
continue; // 跳过包含非数字的行
}
// 最后一个值作为因变量y,其余作为自变量X
if (values.length >= 2) {
X.push(values.slice(0, -1));
y.push(values[values.length - 1]);
}
}
return { X, y };
} catch (error) {
console.error('解析CSV数据错误:', error);
alert('解析CSV数据错误: ' + error.message);
return null;
}
}
/**
* 从表格中获取回归数据
* @returns {Object} 数据对象,包含X(自变量矩阵)和y(因变量向量)
*/
function getDataFromTable() {
try {
const table = document.getElementById('regression-excel-table');
if (!table) {
throw new Error('找不到回归分析表格');
}
const rows = table.querySelectorAll('.excel-row');
if (rows.length === 0) {
throw new Error('表格中没有数据行');
}
const X = [];
const y = [];
rows.forEach(row => {
const cells = row.querySelectorAll('.excel-cell');
if (cells.length < 2) {
return; // 跳过不完整的行
}
// 获取X值(除最后一列外的所有列)
const xValues = [];
let allValid = true;
// 遍历除最后一列外的所有列作为X值
for (let i = 0; i < cells.length - 1; i++) {
const value = parseFloat(cells[i].textContent.trim());
if (isNaN(value)) {
allValid = false;
break;
}
xValues.push(value);
}
// 获取Y值(最后一列)
const yValue = parseFloat(cells[cells.length - 1].textContent.trim());
if (isNaN(yValue)) {
allValid = false;
}
// 如果所有值都有效,则添加到数据集
if (allValid) {
X.push(xValues);
y.push(yValue);
}
});
if (X.length === 0) {
throw new Error('没有找到有效的数据点');
}
return { X, y };
} catch (error) {
console.error('从表格获取数据错误:', error);
alert('从表格获取数据错误: ' + error.message);
return null;
}
}
/**
* 初始化回归分析表格
*/
function initRegressionTable() {
// 获取表格元素
const regressionTable = document.getElementById('regression-table');
const tableBody = regressionTable.querySelector('tbody');
// 绑定添加行按钮事件
const addRowBtn = document.getElementById('add-regression-row');
if (addRowBtn) {
addRowBtn.addEventListener('click', function() {
addRegressionTableRow(tableBody);
updateRegressionRowNumbers();
});
}
// 绑定添加变量按钮事件
const addColumnBtn = document.getElementById('add-regression-column');
if (addColumnBtn) {
addColumnBtn.addEventListener('click', function() {
addRegressionTableColumn(regressionTable);
});
}
// 绑定已有的删除按钮事件
const removeButtons = regressionTable.querySelectorAll('.remove-row');
removeButtons.forEach(button => {
button.addEventListener('click', function() {
const row = this.closest('tr');
if (row) {
tableBody.removeChild(row);
updateRegressionRowNumbers();
}
});
});
}
/**
* 添加回归表格行
* @param {HTMLElement} tableBody - 表格体元素
*/
function addRegressionTableRow(tableBody) {
const newRow = document.createElement('tr');
const headerRow = document.querySelector('#regression-table thead tr');
const numColumns = headerRow.children.length;
// 创建序号单元格
const indexCell = document.createElement('td');
indexCell.className = 'row-index';
indexCell.textContent = tableBody.children.length + 1;
newRow.appendChild(indexCell);
// 创建X输入单元格(根据当前表头数量)
for (let i = 1; i < numColumns - 2; i++) { // 减去序号、Y和操作列
const xCell = document.createElement('td');
const xInput = document.createElement('input');
xInput.type = 'number';
xInput.step = 'any';
xInput.className = `x${i}-value`;
xCell.appendChild(xInput);
newRow.appendChild(xCell);
}
// 创建Y输入单元格
const yCell = document.createElement('td');
const yInput = document.createElement('input');
yInput.type = 'number';
yInput.step = 'any';
yInput.className = 'y-value';
yCell.appendChild(yInput);
newRow.appendChild(yCell);
// 创建操作单元格(删除按钮)
const actionCell = document.createElement('td');
const removeBtn = document.createElement('button');
removeBtn.textContent = '删除';
removeBtn.className = 'remove-row';
removeBtn.addEventListener('click', function() {
tableBody.removeChild(newRow);
updateRegressionRowNumbers();
});
actionCell.appendChild(removeBtn);
newRow.appendChild(actionCell);
// 将行添加到表格
tableBody.appendChild(newRow);
}
/**
* 添加回归表格列(变量)
* @param {HTMLElement} table - 表格元素
*/
function addRegressionTableColumn(table) {
const headerRow = table.querySelector('thead tr');
const bodyRows = table.querySelectorAll('tbody tr');
// 计算新变量的索引(减去序号、Y和操作列)
const varIndex = headerRow.children.length - 2;
// 添加表头
const newHeader = document.createElement('th');
newHeader.className = 'sortable';
newHeader.textContent = `X${varIndex}`;
// 在Y列之前插入新列
const yHeader = headerRow.children[headerRow.children.length - 2];
headerRow.insertBefore(newHeader, yHeader);
// 为每一行添加新的输入单元格
bodyRows.forEach(row => {
const newCell = document.createElement('td');
const newInput = document.createElement('input');
newInput.type = 'number';
newInput.step = 'any';
newInput.className = `x${varIndex}-value`;
newCell.appendChild(newInput);
// 在Y单元格之前插入新单元格
const yCell = row.children[row.children.length - 2];
row.insertBefore(newCell, yCell);
});
}
/**
* 更新回归表格行号
*/
function updateRegressionRowNumbers() {
const rows = document.querySelectorAll('#regression-table tbody tr');
rows.forEach((row, index) => {
const indexCell = row.querySelector('td:first-child');
if (indexCell) {
indexCell.textContent = index + 1;
}
});
}
/**
* 初始化输入模式切换
*/
function initInputModeToggle() {
const tableModeBtn = document.getElementById('table-mode-btn');
const csvModeBtn = document.getElementById('csv-mode-btn');
const tableInputMode = document.getElementById('table-input-mode');
const csvInputMode = document.getElementById('csv-input-mode');
tableModeBtn.addEventListener('click', function() {
// 切换按钮状态
tableModeBtn.classList.add('active');
csvModeBtn.classList.remove('active');
// 切换输入模式
tableInputMode.classList.add('active');
csvInputMode.classList.remove('active');
});
csvModeBtn.addEventListener('click', function() {
// 切换按钮状态
csvModeBtn.classList.add('active');
tableModeBtn.classList.remove('active');
// 切换输入模式
csvInputMode.classList.add('active');
tableInputMode.classList.remove('active');
});
}
/**
* 运行多元线性回归
* @param {Array} X - 自变量矩阵,每行是一个数据点,每列是一个特征
* @param {Array} y - 因变量向量
* @returns {Object} 回归结果
*/
function runMultipleLinearRegression(X, y) {
try {
// 添加常数项(截距)
const Xwith1 = X.map(row => [1, ...row]);
// 转换为math.js矩阵
const Xmatrix = math.matrix(Xwith1);
const ymatrix = math.matrix(y);
// 计算回归系数: β = (X'X)^(-1)X'y
const Xt = math.transpose(Xmatrix);
const XtX = math.multiply(Xt, Xmatrix);
const XtXinv = math.inv(XtX);
const Xty = math.multiply(Xt, ymatrix);
const beta = math.multiply(XtXinv, Xty);
// 计算拟合值
const yFit = math.multiply(Xmatrix, beta);
// 计算R²和调整后的R²
const stats = calculateRegressionStats(y, yFit.valueOf(), Xwith1[0].length - 1);
// 构建回归方程
const equation = buildLinearRegressionEquation(beta.valueOf(), X[0].length);
return {
type: 'linear',
equation: equation,
coefficients: beta.valueOf(),
r2: stats.r2,
adjustedR2: stats.adjustedR2,
numVars: X[0].length
};
} catch (error) {
console.error('多元线性回归计算错误:', error);
alert('多元线性回归计算错误: ' + error.message);
return null;
}
}
/**
* 运行多项式回归
* @param {Array} X - 自变量矩阵,每行是一个数据点,每列是一个特征
* @param {Array} y - 因变量向量
* @param {number} degree - 多项式次数
* @returns {Object} 回归结果
*/
function runPolynomialRegression(X, y, degree) {
try {
// 检查是否只有一个自变量
if (X[0].length > 1) {
throw new Error('多项式回归目前只支持一个自变量');
}
// 提取单个自变量
const x = X.map(row => row[0]);
// 创建多项式特征矩阵
const polyX = [];
for (let i = 0; i < x.length; i++) {
const row = [1]; // 常数项
for (let j = 1; j <= degree; j++) {
row.push(Math.pow(x[i], j));
}
polyX.push(row);
}
// 转换为math.js矩阵
const Xmatrix = math.matrix(polyX);
const ymatrix = math.matrix(y);
// 计算回归系数: β = (X'X)^(-1)X'y
const Xt = math.transpose(Xmatrix);
const XtX = math.multiply(Xt, Xmatrix);
const XtXinv = math.inv(XtX);
const Xty = math.multiply(Xt, ymatrix);
const beta = math.multiply(XtXinv, Xty);
// 计算拟合值
const yFit = math.multiply(Xmatrix, beta);
// 计算R²和调整后的R²
const stats = calculateRegressionStats(y, yFit.valueOf(), degree);
// 构建回归方程
const equation = buildPolynomialRegressionEquation(beta.valueOf(), degree);
return {
type: 'polynomial',
equation: equation,
coefficients: beta.valueOf(),
r2: stats.r2,
adjustedR2: stats.adjustedR2,
degree: degree
};
} catch (error) {
console.error('多项式回归计算错误:', error);
alert('多项式回归计算错误: ' + error.message);
return null;
}
}
/**
* 计算回归统计量
* @param {Array} yActual - 实际y值
* @param {Array} yFit - 拟合y值
* @param {number} numVars - 自变量数量
* @returns {Object} 统计量对象
*/
function calculateRegressionStats(yActual, yFit, numVars) {
const n = yActual.length;
// 计算y的平均值
let yMean = 0;
for (let i = 0; i < n; i++) {
yMean += yActual[i];
}
yMean /= n;
// 计算总平方和 (SST) 和残差平方和 (SSE)
let sst = 0;
let sse = 0;
for (let i = 0; i < n; i++) {
sst += Math.pow(yActual[i] - yMean, 2);
sse += Math.pow(yActual[i] - yFit[i], 2);
}
// 计算R²
const r2 = 1 - (sse / sst);
// 计算调整后的R²
const adjustedR2 = 1 - ((1 - r2) * (n - 1) / (n - numVars - 1));
return { r2, adjustedR2 };
}
/**
* 构建线性回归方程字符串
* @param {Array} coefficients - 回归系数
* @param {number} numVars - 自变量数量
* @returns {string} 回归方程字符串
*/
function buildLinearRegressionEquation(coefficients, numVars) {
let equation = 'y = ';
// 添加截距
equation += coefficients[0].toFixed(4);
// 添加各个自变量的系数
for (let i = 0; i < numVars; i++) {
const coeff = coefficients[i + 1];
if (coeff >= 0) {
equation += ' + ' + coeff.toFixed(4) + 'x' + (i + 1);
} else {
equation += ' - ' + Math.abs(coeff).toFixed(4) + 'x' + (i + 1);
}
}
return equation;
}
/**
* 构建多项式回归方程字符串
* @param {Array} coefficients - 回归系数
* @param {number} degree - 多项式次数
* @returns {string} 回归方程字符串
*/
function buildPolynomialRegressionEquation(coefficients, degree) {
let equation = 'y = ';
// 添加截距
equation += coefficients[0].toFixed(4);
// 添加各次项的系数
for (let i = 1; i <= degree; i++) {
const coeff = coefficients[i];
if (coeff >= 0) {
equation += ' + ' + coeff.toFixed(4);
} else {
equation += ' - ' + Math.abs(coeff).toFixed(4);
}
if (i === 1) {
equation += 'x';
} else {
equation += 'x^' + i;
}
}
return equation;
}
/**
* 显示回归分析结果
* @param {Object} result - 回归结果
* @param {Object} data - 原始数据
*/
function displayRegressionResult(result, data) {
if (!result) return;
// 显示回归方程
const equationResult = document.getElementById('equation-result');
equationResult.textContent = result.equation;
// 显示统计信息
const statsResult = document.getElementById('stats-result');
statsResult.innerHTML = `
拟合优度 R² = ${(result.r2 * 100).toFixed(2)}%
调整后的 R² = ${(result.adjustedR2 * 100).toFixed(2)}%
数据点数量: ${data.X.length}
变量数量: ${result.numVars || result.degree}
`;
// 绘制回归结果
if (data.X[0].length === 1) {
// 只有一个自变量时才绘制图表
plotRegressionResult(result, data);
} else {
// 多变量回归不绘制图表
Plotly.purge('plot-area');
const plotArea = document.getElementById('plot-area');
plotArea.innerHTML = '