增加了回归分析
parent
4f89950fc1
commit
3de0cfb2e2
|
@ -33,6 +33,8 @@ declare module '@vue/runtime-core' {
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||||
|
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||||
|
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<button class="edit-btn" @click="showEditDialog = true">
|
<button class="edit-btn" @click="showEditDialog = true">
|
||||||
编辑数据
|
编辑数据
|
||||||
</button>
|
</button>
|
||||||
<button class="edit-btn" @click="() => chartData.push(Math.floor(Math.random() * 100))">
|
<button class="edit-btn" @click="() => chartData.push(Math.floor(Math.random() * 250))">
|
||||||
插入数据
|
插入数据
|
||||||
</button>
|
</button>
|
||||||
<button class="edit-btn" @click="() => chartData.pop()">
|
<button class="edit-btn" @click="() => chartData.pop()">
|
||||||
|
@ -47,7 +47,15 @@
|
||||||
</div>
|
</div>
|
||||||
<br><br><br>
|
<br><br><br>
|
||||||
<div class="predict-chart-container">
|
<div class="predict-chart-container">
|
||||||
<h2>未来趋势</h2>
|
<div style="display: flex; gap: 20px; align-items: center;">
|
||||||
|
<h2>未来趋势</h2>
|
||||||
|
<el-radio-group v-model="ModelMethod" style="border: 1px solid black; padding: 5px; border-radius:15px;">
|
||||||
|
<el-radio value="1" size="large">线性回归</el-radio>
|
||||||
|
<el-radio value="2" size="large">指数回归</el-radio>
|
||||||
|
<el-radio value="3" size="large">多项式回归</el-radio>
|
||||||
|
<el-radio value="4" size="large">ARIMA回归</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
<v-chart class="map-chart" :option="predictOptions" />
|
<v-chart class="map-chart" :option="predictOptions" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -87,6 +95,8 @@ import {
|
||||||
} from 'echarts/components';
|
} from 'echarts/components';
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from 'echarts/core';
|
||||||
|
|
||||||
|
const ModelMethod = ref('1')
|
||||||
|
|
||||||
// 注册必要的组件
|
// 注册必要的组件
|
||||||
use([
|
use([
|
||||||
CanvasRenderer,
|
CanvasRenderer,
|
||||||
|
@ -177,41 +187,172 @@ const mapOptions = computed(() => ({
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// const predictData = ref([120, 132, 301, 134, 500, 230, 210]);
|
// 线性回归预测
|
||||||
|
function linearRegression(data) {
|
||||||
|
const n = data.length;
|
||||||
|
const x = Array.from({ length: n }, (_, i) => i);
|
||||||
|
const y = data;
|
||||||
|
|
||||||
|
const sumX = x.reduce((sum, val) => sum + val, 0);
|
||||||
|
const sumY = y.reduce((sum, val) => sum + val, 0);
|
||||||
|
const sumXY = x.reduce((sum, val, i) => sum + val * y[i], 0);
|
||||||
|
const sumXX = x.reduce((sum, val) => sum + val * val, 0);
|
||||||
|
|
||||||
|
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
|
||||||
|
const intercept = (sumY - slope * sumX) / n;
|
||||||
|
|
||||||
|
return slope * n + intercept;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指数平滑预测
|
||||||
|
function exponentialSmoothing(data, alpha) {
|
||||||
|
if (data.length === 0) return 0;
|
||||||
|
|
||||||
|
let smoothed = data[0];
|
||||||
|
for (let i = 1; i < data.length; i++) {
|
||||||
|
smoothed = alpha * data[i] + (1 - alpha) * smoothed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算趋势
|
||||||
|
const recent = data.slice(-Math.min(5, data.length));
|
||||||
|
const trend = recent.length > 1 ?
|
||||||
|
(recent[recent.length - 1] - recent[0]) / (recent.length - 1) : 0;
|
||||||
|
|
||||||
|
return smoothed + trend;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 多项式回归预测
|
||||||
|
function polynomialRegression(data, degree) {
|
||||||
|
const n = data.length;
|
||||||
|
if (n < degree + 1) return data[n - 1];
|
||||||
|
|
||||||
|
// 使用最小二乘法拟合多项式
|
||||||
|
const x = Array.from({ length: n }, (_, i) => i);
|
||||||
|
const y = data;
|
||||||
|
|
||||||
|
// 构建范德蒙德矩阵
|
||||||
|
const matrix = [];
|
||||||
|
const target = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const row = [];
|
||||||
|
for (let j = 0; j <= degree; j++) {
|
||||||
|
row.push(Math.pow(x[i], j));
|
||||||
|
}
|
||||||
|
matrix.push(row);
|
||||||
|
target.push(y[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简化的多项式拟合 (仅处理二次)
|
||||||
|
if (degree === 2 && n >= 3) {
|
||||||
|
const x1 = x[n - 3], y1 = y[n - 3];
|
||||||
|
const x2 = x[n - 2], y2 = y[n - 2];
|
||||||
|
const x3 = x[n - 1], y3 = y[n - 1];
|
||||||
|
|
||||||
|
// 二次函数拟合: y = ax² + bx + c
|
||||||
|
const denom = (x1 - x2) * (x1 - x3) * (x2 - x3);
|
||||||
|
if (Math.abs(denom) < 1e-10) return y3 + (y3 - y2);
|
||||||
|
|
||||||
|
const a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom;
|
||||||
|
const b = (x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1) + x1 * x1 * (y2 - y3)) / denom;
|
||||||
|
const c = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom;
|
||||||
|
|
||||||
|
const nextX = n;
|
||||||
|
return a * nextX * nextX + b * nextX + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data[n - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ARIMA简化版本 (自回归移动平均)
|
||||||
|
function arimaSimplified(data) {
|
||||||
|
const n = data.length;
|
||||||
|
if (n < 3) return data[n - 1];
|
||||||
|
|
||||||
|
// AR(2) 模型: y(t) = φ1*y(t-1) + φ2*y(t-2) + ε
|
||||||
|
const y1 = data[n - 1];
|
||||||
|
const y2 = data[n - 2];
|
||||||
|
|
||||||
|
// 简单的AR系数估计
|
||||||
|
const phi1 = 0.7; // 一阶自回归系数
|
||||||
|
const phi2 = 0.2; // 二阶自回归系数
|
||||||
|
|
||||||
|
// 计算残差的移动平均
|
||||||
|
const errors = [];
|
||||||
|
for (let i = 2; i < n; i++) {
|
||||||
|
const predicted = phi1 * data[i - 1] + phi2 * data[i - 2];
|
||||||
|
errors.push(data[i] - predicted);
|
||||||
|
}
|
||||||
|
|
||||||
|
const avgError = errors.length > 0 ?
|
||||||
|
errors.reduce((sum, err) => sum + err, 0) / errors.length : 0;
|
||||||
|
|
||||||
|
// MA(1) 组件
|
||||||
|
const theta = 0.3; // 移动平均系数
|
||||||
|
const prediction = phi1 * y1 + phi2 * y2 + theta * avgError;
|
||||||
|
|
||||||
|
return prediction;
|
||||||
|
}
|
||||||
|
|
||||||
const predictData = computed(() => {
|
const predictData = computed(() => {
|
||||||
const data = chartData.value;
|
const data = chartData.value;
|
||||||
if (data.length < 2) return [];
|
|
||||||
|
|
||||||
const predictions = [];
|
const predictions = [];
|
||||||
const windowSize = Math.min(3, data.length); // 使用最近3个点或全部点
|
|
||||||
|
// 确保有足够的数据进行预测
|
||||||
|
if (data.length < 3) return Array(5).fill(data[data.length - 1] || 0);
|
||||||
|
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
let predictedValue;
|
let predictedValue;
|
||||||
|
const currentData = [...data, ...predictions];
|
||||||
if (i % 2 === 0) {
|
const len = currentData.length;
|
||||||
// 使用线性趋势预测
|
|
||||||
const recentData = [...data, ...predictions];
|
switch (ModelMethod.value) {
|
||||||
const len = recentData.length;
|
case '1': // 线性回归
|
||||||
const x1 = len - windowSize, y1 = recentData[len - windowSize];
|
predictedValue = linearRegression(currentData);
|
||||||
const x2 = len - 1, y2 = recentData[len - 1];
|
break;
|
||||||
|
case '2': // 指数平滑
|
||||||
// 计算斜率
|
predictedValue = exponentialSmoothing(currentData, 0.3);
|
||||||
const slope = (y2 - y1) / (x2 - x1);
|
break;
|
||||||
predictedValue = y2 + slope;
|
case '3': // 多项式回归 (二次)
|
||||||
} else {
|
predictedValue = polynomialRegression(currentData, 2);
|
||||||
const lastValues = [...data, ...predictions];
|
break;
|
||||||
const sum = lastValues.slice(-windowSize).reduce((acc, val) => acc + val, 0);
|
case '4': // ARIMA简化版本
|
||||||
predictedValue = sum / Math.min(windowSize, lastValues.length);
|
predictedValue = arimaSimplified(currentData);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加一些随机波动,使预测更真实
|
// 季节性调整 (模拟周期性波动)
|
||||||
const noise = (Math.random() - 0.5) * 20;
|
const seasonalFactor = Math.sin((len + i) * Math.PI / 6) * 0.1 + 1;
|
||||||
|
predictedValue *= seasonalFactor;
|
||||||
|
|
||||||
|
// 趋势衰减 (距离越远,趋势影响越小)
|
||||||
|
const trendDecay = Math.exp(-i * 0.1);
|
||||||
|
const baseline = currentData.slice(-Math.min(5, currentData.length))
|
||||||
|
.reduce((sum, val) => sum + val, 0) / Math.min(5, currentData.length);
|
||||||
|
|
||||||
|
predictedValue = baseline + (predictedValue - baseline) * trendDecay;
|
||||||
|
|
||||||
|
// 添加渐减的随机噪声
|
||||||
|
const noiseIntensity = 15 * Math.exp(-i * 0.3);
|
||||||
|
const noise = (Math.random() - 0.5) * noiseIntensity;
|
||||||
|
|
||||||
|
// 确保预测值合理
|
||||||
predictedValue = Math.max(0, Math.round(predictedValue + noise));
|
predictedValue = Math.max(0, Math.round(predictedValue + noise));
|
||||||
|
|
||||||
|
// 异常值检测和修正
|
||||||
|
if (predictions.length > 0) {
|
||||||
|
const lastPrediction = predictions[predictions.length - 1];
|
||||||
|
const maxChange = Math.abs(lastPrediction * 0.3); // 最大变化30%
|
||||||
|
|
||||||
|
if (Math.abs(predictedValue - lastPrediction) > maxChange) {
|
||||||
|
const direction = predictedValue > lastPrediction ? 1 : -1;
|
||||||
|
predictedValue = lastPrediction + direction * maxChange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
predictions.push(predictedValue);
|
predictions.push(predictedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return predictions;
|
return predictions;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue