// 函数拟合工具 - 回归分析模块 // 页面加载完成后执行初始化 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 = '
多变量回归无法在二维图表中显示
'; } } /** * 绘制回归分析结果 * @param {Object} result - 回归结果 * @param {Object} data - 原始数据 */ function plotRegressionResult(result, data) { // 提取数据点的x和y值 const xData = data.X.map(row => row[0]); const yData = data.y; // 找出x的最小值和最大值 const xMin = Math.min(...xData); const xMax = Math.max(...xData); // 为了平滑曲线,生成更多的点 const xRange = xMax - xMin; const xStart = xMin - xRange * 0.1; const xEnd = xMax + xRange * 0.1; const step = xRange / 100; // 生成回归曲线的点 const xFit = []; const yFit = []; for (let x = xStart; x <= xEnd; x += step) { let y; if (result.type === 'linear') { // 线性回归: y = b0 + b1*x y = result.coefficients[0] + result.coefficients[1] * x; } else if (result.type === 'polynomial') { // 多项式回归: y = b0 + b1*x + b2*x^2 + ... + bn*x^n y = result.coefficients[0]; for (let i = 1; i <= result.degree; i++) { y += result.coefficients[i] * Math.pow(x, i); } } if (!isNaN(y) && isFinite(y)) { xFit.push(x); yFit.push(y); } } // 创建数据点散点图 const dataTrace = { x: xData, y: yData, mode: 'markers', type: 'scatter', name: '数据点', marker: { size: 8, color: 'rgba(255, 0, 0, 0.7)' } }; // 创建回归曲线 const fitTrace = { x: xFit, y: yFit, mode: 'lines', type: 'scatter', name: '回归曲线', line: { color: 'blue', width: 2 } }; // 绘图布局 const layout = { title: '回归分析结果', xaxis: { title: 'X' }, yaxis: { title: 'Y' }, legend: { x: 0, y: 1 } }; // 绘制图表 Plotly.newPlot('plot-area', [dataTrace, fitTrace], layout); }