| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672 |
- // 函数拟合工具 - 回归分析模块
- // 页面加载完成后执行初始化
- 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)}%<br>
- 调整后的 R² = ${(result.adjustedR2 * 100).toFixed(2)}%<br>
- 数据点数量: ${data.X.length}<br>
- 变量数量: ${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 = '<div class="no-plot-message">多变量回归无法在二维图表中显示</div>';
- }
- }
- /**
- * 绘制回归分析结果
- * @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);
- }
|