浏览代码

残差分析-基本结束

gmy 5 月之前
父节点
当前提交
f627fc80b5

+ 5 - 0
models/residual_analysis_model/calculate_residual_analysis_config.go

@@ -50,6 +50,10 @@ type ResidualAnalysisResp struct {
 type ChartResp struct {
 	ChartInfo   *ResidualAnalysisChartInfo
 	EdbInfoList []ResidualAnalysisChartEdbInfoMapping
+	A           float64 `description:"斜率"`
+	B           float64 `description:"截距"`
+	R           float64 `description:"相关系数"`
+	R2          float64 `description:"决定系数"`
 }
 
 type ResidualAnalysisChartInfo struct {
@@ -109,6 +113,7 @@ type ResidualAnalysisChartEdbInfoMapping struct {
 }
 
 type ResidualAnalysisIndexSaveReq struct {
+	Source       int                    `description:"99-映射残差 100-拟合残差"`
 	EdbCode      string                 `description:"指标编码"`
 	EdbName      string                 `description:"指标名称"`
 	EdbNameEn    string                 `description:"英文指标名称"`

+ 73 - 24
services/residual_analysis_service/residual_analysis_service.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_api/models/system"
 	"eta/eta_api/utils"
 	"fmt"
+	"math"
 	"time"
 )
 
@@ -86,7 +87,7 @@ func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (r
 	}
 
 	// 映射图表信息
-	mappingEdbList, err := fillMappingChartInfo(req, edbInfoMappingA, edbInfoMappingB, originalEdbList, indexADataMap)
+	mappingEdbList, a, b, r, err := fillMappingChartInfo(req, edbInfoMappingA, edbInfoMappingB, originalEdbList, indexADataMap)
 	if err != nil {
 		return residual_analysis_model.ResidualAnalysisResp{}, err
 	}
@@ -98,7 +99,7 @@ func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (r
 	}
 
 	// 残差图表信息
-	ResidualEdbList, err := fillResidualChartInfo(edbInfoMappingA, edbInfoMappingB, mappingEdbList)
+	ResidualEdbList, R2, err := fillResidualChartInfo(edbInfoMappingA, edbInfoMappingB, mappingEdbList)
 	if err != nil {
 		return residual_analysis_model.ResidualAnalysisResp{}, err
 	}
@@ -108,12 +109,16 @@ func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (r
 	resp.MappingChartData = residual_analysis_model.ChartResp{
 		ChartInfo:   baseChartInfo,
 		EdbInfoList: ResidualEdbList,
+		A:           a,
+		B:           b,
+		R:           r,
+		R2:          R2,
 	}
 
 	return resp, nil
 }
 
-func fillResidualChartInfo(edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, mappingEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
+func fillResidualChartInfo(edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, mappingEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, float64, error) {
 	// 计算公式 映射残差 = 因变量指标 - 映射指标
 	var edbInfoA, edbInfoB residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
 	if mappingEdbList[0].EdbInfoId == edbInfoMappingA.EdbInfoId {
@@ -125,26 +130,46 @@ func fillResidualChartInfo(edbInfoMappingA *data_manage.ChartEdbInfoMapping, edb
 	}
 	dataAList, ok := edbInfoA.DataList.([]*data_manage.EdbDataList)
 	if !ok {
-		return nil, fmt.Errorf("数据类型转换失败", nil)
+		return nil, 0, fmt.Errorf("数据类型转换失败", nil)
 	}
 	edbData := dataAList
 	dataBList, ok := edbInfoB.DataList.([]*data_manage.EdbDataList)
 	if !ok {
-		return nil, fmt.Errorf("数据类型转换失败", nil)
+		return nil, 0, fmt.Errorf("数据类型转换失败", nil)
 	}
 	var indexDataBMap = make(map[string]*data_manage.EdbDataList)
 	for _, data := range dataBList {
 		indexDataBMap[data.DataTime] = data
 	}
-	var valueB float64
+	// 求R2
+	var valueB, sumValueA, averageValueA, residualQuadraticSum, totalQuadraticSum, R2 float64
+
+	for _, indexData := range edbData {
+		// 因变量的值总和
+		sumValueA += indexData.Value
+	}
+	// 因变量平均值
+	averageValueA = sumValueA / float64(len(edbData))
+
 	for _, indexData := range edbData {
 		if dataB, ok := indexDataBMap[indexData.DataTime]; ok {
 			valueB = dataB.Value
 		} else {
 			valueB = 0
 		}
+
+		// 总因变量平方和
+		totalQuadraticSum += math.Pow(indexData.Value-averageValueA, 2)
+
+		// 补全残差值
 		indexData.Value = indexData.Value - valueB
+
+		// 残差平方和
+		residualQuadraticSum += math.Pow(indexData.Value, 2)
 	}
+	// 计算R2 公式:R2=1-SSE/SST R2越大,越符合线性  R2 = 1 - 残差平方和/总平方和
+	R2 = 1 - residualQuadraticSum/totalQuadraticSum
+
 	for _, mapping := range mappingEdbList {
 		if mapping.EdbInfoId != edbInfoMappingA.EdbInfoId {
 			mapping.DataList = edbData
@@ -152,32 +177,36 @@ func fillResidualChartInfo(edbInfoMappingA *data_manage.ChartEdbInfoMapping, edb
 		}
 	}
 
-	return mappingEdbList, nil
+	return mappingEdbList, R2, nil
 }
 
-func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, originalEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, indexADataMap map[string]*data_manage.EdbDataList) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
+func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, originalEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, indexADataMap map[string]*data_manage.EdbDataList) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, float64, float64, float64, error) {
 	// 计算公式:Y=aX+b,Y为映射后的指标,X为自变量指标
 	// 正序:a=(L2-L1)/(R2-R1)	b=L2-R2*a
 	// 逆序:a=(L2-L1)/(R1-R2)	b=L2-R1*a
 	// L2:左轴下限 R2:右轴上限 L1:左轴上限 R1:右轴下限
-	var a, b float64
-	if req.IsOrder {
-		a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMax - req.RightIndexMin)
-		b = req.LeftIndexMax - req.RightIndexMax*a
-	} else {
-		a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMin - req.RightIndexMax)
-		b = req.LeftIndexMax - req.RightIndexMin*a
+	var a, b, r float64
+
+	// 映射残差 计算a,b
+	if req.ResidualType == 1 {
+		if req.IsOrder {
+			a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMax - req.RightIndexMin)
+			b = req.LeftIndexMax - req.RightIndexMax*a
+		} else {
+			a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMin - req.RightIndexMax)
+			b = req.LeftIndexMax - req.RightIndexMin*a
+		}
 	}
 
 	dataList, ok := edbInfoMappingB.DataList.([]*data_manage.EdbDataList)
 	if !ok {
-		return nil, fmt.Errorf("数据类型转换失败", nil)
+		return nil, a, b, r, fmt.Errorf("数据类型转换失败", nil)
 	}
 
 	// 领先指标 dataList进行数据处理
 	if req.IndexType == 2 {
 		if req.LeadValue < 0 {
-			return nil, fmt.Errorf("领先值不能小于0", nil)
+			return nil, a, b, r, fmt.Errorf("领先值不能小于0", nil)
 		} else if req.LeadValue > 0 {
 			for _, indexData := range dataList {
 				switch req.LeadFrequency {
@@ -196,14 +225,12 @@ func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbIn
 		}
 	}
 
+	// 指标B数据补充
 	for index := 0; index < len(dataList)-1; index++ {
 		// 获取当前数据和下一个数据
 		beforeIndexData := dataList[index]
 		afterIndexData := dataList[index+1]
 
-		// 计算映射值
-		beforeIndexData.Value = a*beforeIndexData.Value + b
-
 		// 从最早时间开始,补充时间为自然日
 		if utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
 			// 创建补充数据
@@ -217,10 +244,32 @@ func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbIn
 			dataList = append(dataList, &replenishIndexData)
 		}
 	}
+
+	// 拟合残差 计算a,b
+	var coordinateList []utils.Coordinate
+	if req.ResidualType == 2 {
+		for _, indexData := range dataList {
+			if _, ok := indexADataMap[indexData.DataTime]; ok {
+
+				coordinate := utils.Coordinate{
+					X: indexData.Value,
+					Y: indexADataMap[indexData.DataTime].Value,
+				}
+				coordinateList = append(coordinateList, coordinate)
+			}
+		}
+		a, b = utils.GetLinearResult(coordinateList)
+		r = utils.CalculationDecisive(coordinateList)
+	}
+
 	// 根据指标A的时间key,在B的映射指标中筛选出对应的值
 	var dataBList []*data_manage.EdbDataList
+
 	for _, indexData := range dataList {
 		if _, ok := indexADataMap[indexData.DataTime]; ok {
+			// 计算指标B映射值
+			indexData.Value = a*indexData.Value + b
+
 			dataBList = append(dataBList, indexData)
 		}
 	}
@@ -231,10 +280,10 @@ func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbIn
 			mapping.EdbInfoId = 0
 			mapping.EdbCode = ""
 			mapping.EdbName = edbInfoMappingB.EdbName + "映射" + edbInfoMappingA.EdbName
-			mapping.DataList = dataList
+			mapping.DataList = dataBList
 		}
 	}
-	return mappingEdbList, nil
+	return mappingEdbList, a, b, r, nil
 }
 
 func fillOriginalChart(req residual_analysis_model.ResidualAnalysisReq, mappingList []*data_manage.ChartEdbInfoMapping, startDate string, endDate string, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, OriginalEdbList []residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
@@ -322,7 +371,7 @@ func SaveResidualAnalysis(req residual_analysis_model.ResidualAnalysisIndexSaveR
 	// 更新or新增
 	if req.EdbCode != "" {
 		// 查询指标库指标
-		edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_RESIDUAL_ANALYSIS, req.EdbCode)
+		edbInfo, err := data_manage.GetEdbInfoByEdbCode(req.Source, req.EdbCode)
 		if err != nil {
 			return err
 		}
@@ -355,7 +404,7 @@ func SaveResidualAnalysis(req residual_analysis_model.ResidualAnalysisIndexSaveR
 		Unit:       req.Unit,
 		UnitEn:     req.UnitEn,
 		Frequency:  req.Frequency,
-		Source:     utils.DATA_SOURCE_RESIDUAL_ANALYSIS,
+		Source:     req.Source,
 		SourceName: "残差分析",
 	})
 	if err != nil {

+ 2 - 1
utils/constants.go

@@ -184,7 +184,8 @@ const (
 	DATA_SOURCE_LY                                   = 91       // 粮油商务网
 	DATA_SOURCE_TRADE_ANALYSIS                       = 92       // 持仓分析
 	DATA_SOURCE_HISUGAR                              = 93       // 泛糖科技 -> 93
-	DATA_SOURCE_RESIDUAL_ANALYSIS                    = 99       // 残差分析
+	DATA_SOURCE_MAPPING_RESIDUAL                     = 99       // 映射残差
+	DATA_SOURCE_FIT_RESIDUAL                         = 100      // 拟合残差
 )
 
 // 数据刷新频率