/** * 函数拟合工具 - 工具函数 */ /** * 从表格中获取数据点 * @returns {Array} 数据点数组,每个元素包含x和y属性 */ function getDataPointsFromTable() { // 检查是否使用新的Excel表格 const excelTable = document.getElementById('fit-excel-table'); if (excelTable && window.dataTable) { const data = window.dataTable.getTableData(excelTable); const dataPoints = []; for (let i = 0; i < data.length; i++) { const row = data[i]; if (row.length >= 2) { const x = row[0]; const y = row[1]; if (!isNaN(x) && !isNaN(y)) { dataPoints.push({ x, y }); } } } return dataPoints; } else { // 兼容旧版表格 const dataTable = document.getElementById('data-table'); const rows = dataTable.querySelectorAll('tbody tr'); const dataPoints = []; rows.forEach(row => { const xInput = row.querySelector('.x-value'); const yInput = row.querySelector('.y-value'); if (xInput && yInput) { const x = parseFloat(xInput.value); const y = parseFloat(yInput.value); if (!isNaN(x) && !isNaN(y)) { dataPoints.push({ x, y }); } } }); return dataPoints; } } /** * 从表格获取回归数据 * @returns {Object} 包含X(自变量矩阵)和y(因变量向量)的对象 */ function getDataFromTable() { // 检查是否使用新的Excel表格 const excelTable = document.getElementById('regression-excel-table'); if (excelTable && window.dataTable) { const data = window.dataTable.getTableData(excelTable); const X = []; const y = []; for (let i = 0; i < data.length; i++) { const row = data[i]; if (row.length >= 2) { // 至少需要一个X和一个y const xRow = row.slice(0, row.length - 1); // 最后一列是y值 const yValue = row[row.length - 1]; // 检查所有值是否有效 let allValid = true; for (let j = 0; j < xRow.length; j++) { if (isNaN(xRow[j])) { allValid = false; break; } } if (isNaN(yValue)) { allValid = false; } // 如果所有值都有效,添加到数据集 if (allValid) { X.push(xRow); y.push(yValue); } } } return { X, y }; } else { // 兼容旧版表格 const rows = document.querySelectorAll('#regression-table tbody tr'); const X = []; const y = []; rows.forEach(row => { const xInputs = row.querySelectorAll('.x1-value, .x2-value, [class^="x"][class$="-value"]:not(.x1-value):not(.x2-value)'); const yInput = row.querySelector('.y-value'); if (xInputs.length > 0 && yInput) { const xRow = []; let allValid = true; // 收集所有x值 xInputs.forEach(input => { const value = parseFloat(input.value); if (isNaN(value)) { allValid = false; } xRow.push(value); }); // 获取y值 const yValue = parseFloat(yInput.value); if (isNaN(yValue)) { allValid = false; } // 如果所有值都有效,添加到数据集 if (allValid) { X.push(xRow); y.push(yValue); } } }); return { X, y }; } } /** * 解析CSV数据 * @param {string} csvText - CSV格式的文本 * @returns {Object} 包含X(自变量矩阵)和y(因变量向量)的对象 */ function parseCSVData(csvText) { if (!csvText.trim()) { return { X: [], y: [] }; } try { // 按行分割 const lines = csvText.trim().split(/\r?\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(/[,\t]/).map(val => parseFloat(val.trim())); // 检查是否有足够的列 if (values.length < 2) { continue; } // 检查所有值是否有效 let allValid = true; for (let j = 0; j < values.length; j++) { if (isNaN(values[j])) { allValid = false; break; } } if (allValid) { // 最后一列作为y值,其余作为X值 X.push(values.slice(0, values.length - 1)); y.push(values[values.length - 1]); } } return { X, y }; } catch (error) { console.error('解析CSV数据错误:', error); return { X: [], y: [] }; } } /** * 格式化数字,保留指定小数位 * @param {number} value - 要格式化的数值 * @param {number} decimals - 小数位数 * @returns {string} 格式化后的数字字符串 */ function formatNumber(value, decimals = 4) { return Number(value).toFixed(decimals); } /** * 计算相关系数 (R²) * @param {Array} xValues - X值数组 * @param {Array} yValues - Y值数组 * @param {Function} predictFn - 预测函数,接收x返回预测的y * @returns {number} 相关系数 */ function calculateRSquared(xValues, yValues, predictFn) { if (xValues.length !== yValues.length || xValues.length === 0) { return 0; } // 计算y的平均值 const yMean = yValues.reduce((sum, y) => sum + y, 0) / yValues.length; // 计算总平方和(SST) const sst = yValues.reduce((sum, y) => sum + Math.pow(y - yMean, 2), 0); // 计算残差平方和(SSE) let sse = 0; for (let i = 0; i < xValues.length; i++) { const yPred = predictFn(xValues[i]); sse += Math.pow(yValues[i] - yPred, 2); } // 计算R² return 1 - (sse / sst); } /** * 计算均方根误差(RMSE) * @param {Array} xValues - X值数组 * @param {Array} yValues - Y值数组 * @param {Function} predictFn - 预测函数,接收x返回预测的y * @returns {number} RMSE值 */ function calculateRMSE(xValues, yValues, predictFn) { if (xValues.length !== yValues.length || xValues.length === 0) { return 0; } let sumSquaredError = 0; for (let i = 0; i < xValues.length; i++) { const yPred = predictFn(xValues[i]); sumSquaredError += Math.pow(yValues[i] - yPred, 2); } return Math.sqrt(sumSquaredError / xValues.length); } /** * 生成统计结果HTML * @param {Object} params - 统计参数 * @returns {string} HTML字符串 */ function generateStatsHTML(params) { const { coefficients, rSquared, rmse, formula, dataPoints } = params; let html = '

拟合统计

'; html += ''; html += ''; html += ``; html += ``; html += ``; html += ''; if (Array.isArray(coefficients)) { coefficients.forEach((coef, index) => { let coefName = ''; switch (index) { case 0: coefName = formula === 'linear' ? 'a (斜率)' : 'a'; break; case 1: coefName = formula === 'linear' ? 'b (截距)' : 'b'; break; default: coefName = String.fromCharCode(97 + index); // a, b, c, d... } html += ``; }); } else if (typeof coefficients === 'object') { for (const key in coefficients) { html += ``; } } html += '
参数
数据点数量${dataPoints}
决定系数 (R²)${formatNumber(rSquared)}
均方根误差 (RMSE)${formatNumber(rmse)}
系数
${coefName}${formatNumber(coef)}
${key}${formatNumber(coefficients[key])}
'; return html; }