标题
go最小二乘法线性模拟动态续费优惠
day | fee |
---|---|
1 | 10 |
7 | 100 |
30 | 360 |
90 | 630 |
200 | 1200 |
365 | 1800 |
package curvefit
import (
"fmt"
"github.com/wonderivan/logger"
"gonum.org/v1/plot"
"gonum.org/v1/plot/plotutil"
"gonum.org/v1/plot/vg"
"os"
"strconv"
"time"
//"log"
"math"
//"os"
"github.com/go-gota/gota/dataframe"
//"github.com/go-gota/gota/series"
"gonum.org/v1/gonum/optimize"
//"gonum.org/v1/plot"
"gonum.org/v1/plot/plotter"
//"gonum.org/v1/plot/plotutil"
//"gonum.org/v1/plot/vg"
)
var (
ColNamesBaicycle = []string{"day", "fee"}
OneYearDay = 360
DayFeeSeries_GlobalVarible DayFeePlotter
)
type DayFeePlotter struct {
XY plotter.XYs
args []float64
}
type DayFeeCalc struct {
RoundRideDay int64 // 本轮骑行天数
StartRoundDay string // 开启本轮优惠日期 example:20210827
Time string // 当前日期,可以不传 example:20210827
ExpDayFeeLast float64 // 连续RoundRideDay-1 天优惠金额 不传则必须设置DayFeeSeries_GlobalVarible
ExpDayFee float64 // 连续RoundRideDay天 优惠金额 不传则必须设置DayFeeSeries_GlobalVarible
}
func NewDataFrame(stringSlice [][]string) dataframe.DataFrame {
return dataframe.LoadRecords(stringSlice)
}
func LoadCSV(path string) dataframe.DataFrame {
clsData, err := os.Open(path)
if err != nil {
logger.Info(err)
}
defer clsData.Close()
clsDF := dataframe.ReadCSV(clsData)
// 数据预处理
clsDF = clsDF.Select(ColNamesBaicycle)
return clsDF
}
// 抛物线
func OptimizeParabola(clsDF *dataframe.DataFrame) (actPoints, expPoints plotter.XYs, fa, fb float64) {
// 开始数据拟合
// 实际观测点
actPoints = plotter.XYs{}
// N行数据产生N个点
for i := 0; i < clsDF.Nrow(); i++ {
day := clsDF.Elem(i, 0).Val().(int)
fee := clsDF.Elem(i, 1).Val().(int)
actPoints = append(actPoints, plotter.XY{
X: float64(day),
Y: float64(fee),
})
}
fmt.Println("actPoints is", actPoints)
result, err := optimize.Minimize(optimize.Problem{
Func: func(x []float64) float64 {
//if len(x) != 2 {
// panic("illegal x")
//}
a := x[0]
b := x[1]
var sum float64
for _, point := range actPoints {
y := a*point.X*point.X + b*point.X
sum += math.Pow(math.Abs(y-point.Y), 2)
}
fmt.Println("sum is:", sum)
return sum
},
}, []float64{0, 0}, &optimize.Settings{}, &optimize.NelderMead{})
if err != nil {
panic(err)
}
// 最小二乘法拟合出来的k和b值
fa, fb = result.X[0], result.X[1]
expPoints = plotter.XYs{}
for i := 0; i < OneYearDay; i++ {
day := i
x := float64(day)
expPoints = append(expPoints, plotter.XY{
X: x,
Y: fa*x*x + fb*x,
})
}
return
}
//正弦函数
func OptimizeSin(clsDF *dataframe.DataFrame) (actPoints, expPoints plotter.XYs, fa float64) {
// 开始数据拟合
// 实际观测点
actPoints = plotter.XYs{}
// N行数据产生N个点
for i := 0; i < clsDF.Nrow(); i++ {
day := clsDF.Elem(i, 0).Val().(int)
fee := clsDF.Elem(i, 1).Val().(int)
actPoints = append(actPoints, plotter.XY{
X: float64(day),
Y: float64(fee),
})
}
fmt.Println("actPoints is", actPoints)
result, err := optimize.Minimize(optimize.Problem{
Func: func(x []float64) float64 {
//if len(x) != 2 {
// panic("illegal x")
//}
a := x[0]
var sum float64
for _, point := range actPoints {
y := a * math.Sin(point.X*math.Pi/(2*float64(OneYearDay)))
sum += math.Pow(math.Abs(y-point.Y), 2)
}
return sum
},
}, []float64{0}, &optimize.Settings{}, &optimize.NelderMead{})
if err != nil {
panic(err)
}
// 最小二乘法拟合出来的k和b值
fa = result.X[0]
expPoints = plotter.XYs{}
for i := 0; i < OneYearDay; i++ {
day := i
x := float64(day)
expPoints = append(expPoints, plotter.XY{
X: x,
Y: fa * math.Sin(x*math.Pi/(2*float64(OneYearDay))),
})
}
return
}
// 3次方程
func OptimizeGrade3(clsDF *dataframe.DataFrame) (actPoints, expPoints plotter.XYs, fa, fb, fc float64) {
// 开始数据拟合
// 实际观测点
actPoints = plotter.XYs{}
// N行数据产生N个点
for i := 0; i < clsDF.Nrow(); i++ {
day := clsDF.Elem(i, 0).Val().(int)
fee := clsDF.Elem(i, 1).Val().(int)
actPoints = append(actPoints, plotter.XY{
X: float64(day),
Y: float64(fee),
})
}
fmt.Println("actPoints is", actPoints)
result, err := optimize.Minimize(optimize.Problem{
Func: func(x []float64) float64 {
//if len(x) != 2 {
// panic("illegal x")
//}
a := x[0]
b := x[1]
c := x[2]
var sum float64
for _, point := range actPoints {
y := a*point.X*point.X*point.X + b*point.X*point.X + c*point.X
sum += math.Pow(math.Abs(y-point.Y), 2)
}
return sum
},
}, []float64{0, 0, 0}, &optimize.Settings{}, &optimize.NelderMead{})
if err != nil {
panic(err)
}
// 最小二乘法拟合出来的k和b值
fa, fb, fc = result.X[0], result.X[1], result.X[2]
expPoints = plotter.XYs{}
for i := 0; i < OneYearDay; i++ {
day := i
x := float64(day)
expPoints = append(expPoints, plotter.XY{
X: x,
Y: fa*x*x*x + fb*x*x + fc*x,
})
}
return
}
func dataPlot2(actPoints, expPoints1, expPoints2, expPoints3, expPoints4 plotter.XYs) {
plt, err := plot.New()
if err != nil {
panic(err)
}
plotutil.AddScatters(plt, actPoints)
if err := plotutil.AddLinePoints(plt,
"paowuxian", expPoints1,
"zhengxian", expPoints2,
"3cifang", expPoints3,
"zhixian", expPoints4,
"actPoints", actPoints,
); err != nil {
panic(err)
}
if err := plt.Save(10*vg.Inch, 10*vg.Inch, "classification-fit.png"); err != nil {
panic(err)
}
}
func DayFee(expPoints plotter.XYs) plotter.XYs {
newExpPoints := plotter.XYs{}
for i, xy := range expPoints {
y, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", xy.Y), 64)
if i > 0 {
y, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", xy.Y-expPoints[i-1].Y), 64)
}
newExpPoints = append(newExpPoints, plotter.XY{
X: xy.X,
Y: y,
})
}
return newExpPoints
}
func SetDayFee(expPoints plotter.XYs, args []float64) {
DayFeeSeries_GlobalVarible.XY = expPoints
DayFeeSeries_GlobalVarible.args = args
}
func GetDayFee(day int64) (float64, error) {
for _, xy := range DayFeeSeries_GlobalVarible.XY {
if xy.X == float64(day) {
return xy.Y, nil
}
}
return 0.00, fmt.Errorf("没有对应的数据")
}
func GetArgs() []float64 {
return DayFeeSeries_GlobalVarible.args
}
func DataPlot(actPoints, expPoints1 plotter.XYs) {
plt, err := plot.New()
if err != nil {
panic(err)
}
plotutil.AddScatters(plt, actPoints)
if err := plotutil.AddLinePoints(plt,
"expPoints", expPoints1,
"actPoints", actPoints,
); err != nil {
panic(err)
}
if err := plt.Save(10*vg.Inch, 10*vg.Inch, "classification-fit.png"); err != nil {
panic(err)
}
}
func DataPlot2(actPoints, expPoints1, expPoints2, expPoints3, expPoints4 plotter.XYs) {
plt, err := plot.New()
if err != nil {
panic(err)
}
plotutil.AddScatters(plt, actPoints)
if err := plotutil.AddLinePoints(plt,
"paowuxian", expPoints1,
"zhengxian", expPoints2,
"3cifang", expPoints3,
"zhixian", expPoints4,
"actPoints", actPoints,
); err != nil {
panic(err)
}
if err := plt.Save(10*vg.Inch, 10*vg.Inch, "classification-fit.png"); err != nil {
panic(err)
}
}
//
func (s *DayFeeCalc) OptimizeDayFee() float64 {
if s.Time == "" {
s.Time = getLocalData()
}
if s.RoundRideDay < 2 {
fee, _ := GetDayFee(1)
return fee
}
if s.ExpDayFeeLast == 0 {
fee, err := GetDayFee(s.RoundRideDay - 1)
if err != nil {
logger.Error("error is:", err)
}
s.ExpDayFeeLast = fee
}
if s.ExpDayFee == 0 {
fee, err := GetDayFee(s.RoundRideDay)
if err != nil {
logger.Error("error is:", err)
}
s.ExpDayFee = fee
}
dayFee := s.ExpDayFeeLast
expDayFee := s.ExpDayFee
roundPassDay := (day2sec(s.Time) - day2sec(s.StartRoundDay)) / 86400 + 1
return decimalValue(dayFee-(dayFee-expDayFee)*float64(s.RoundRideDay)/float64(roundPassDay), "2")
}
//浮点数位数
func decimalValue(value float64, num string) float64 {
value, _ = strconv.ParseFloat(fmt.Sprintf("%."+num+"f", value), 64)
return value
}
// 获取当天凌晨时间
func getLocalDayZero() int64 {
timeStr := time.Now().Format("20060102")
t, _ := time.ParseInLocation("20060102", timeStr, time.Local)
timeNumber := t.Unix()
return timeNumber
}
func getLocalData() string {
timeStr := time.Now().Format("20060102")
return timeStr
}
func day2sec(timeStr string) int64 {
t, _ := time.ParseInLocation("20060102", timeStr, time.Local)
timeNumber := t.Unix()
return timeNumber
}
示例:
package curvefit
import (
"gonum.org/v1/plot/plotter"
"math"
"time"
"fmt"
)
func user_example() {
c := [][]string{
{"1", "10"},
{"7", "100"},
{"30", "360"},
{"90", "630"},
{"200", "1200"},
{"360", "1800"},
}//{"天数",“预估总金额”}
data := NewDataFrame(c)
actPoints, expPoints, fa, fb := OptimizeParabola(&data) //fa,fb为抛物线公式参数,actPoints为真实点即c中的数据集
// expPoints为day天的预估总金额数据集 {day, f(day)}
// f(day) = fa * day * day + fb * day 获取day天的预估总金额
// f(day-1) = fa * (day-1) * (day-1) + fb * (day-1) 获取day-1天的预估总金额
//实际当天收取f(day) - f(day-1)
//newExpPoints := dayfee.DayFee(expPoints) //返回每天的预估金额,保留2位小数 {day, f(day) - f(day-1)}
newExpPoints := DayFee(expPoints)
SetDayFee(newExpPoints, []float64{fa, fb}) // 全局保存
fmt.Println(GetArgs())
dayfeee, _ := GetDayFee(1)
fmt.Println("dayfeee", dayfeee) // 4是本轮使用天数
d := DayFeeCalc{
RoundRideDay: 2,
StartRoundDay : "20200821",
Time: "20200823",
}
fmt.Println(d.OptimizeDayFee())
sum := dayfeee
expPointst := plotter.XYs{
{1.00, dayfeee},
}
free := 30
for i := 2; i < 361; i++ {
day := i
x := float64(day)
c := DayFeeCalc{
RoundRideDay: int64(math.Max(float64(i - 1 - free), 2.00)),
StartRoundDay : "20210821",
}
tt := day2sec(c.StartRoundDay) + int64(i) * 86400
tt_string := time.Unix(tt, 0).Format("20060102")
c.Time = tt_string
yy := c.OptimizeDayFee()
if i - free > 0 {
sum += yy
}
expPointst = append(expPointst, plotter.XY{
X: x,
Y: sum,
})
}
fmt.Println(expPointst)
DataPlot(actPoints, expPointst)
}
// BaicycleFitClassification 分类曲线拟合函数
// 是个样板
func FitClassification() {
c := [][]string{
{"1", "10"},
{"7", "100"},
{"30", "360"},
{"90", "630"},
{"200", "1200"},
{"365", "1800"},
} //{"天数",“预估总金额”}
df := NewDataFrame(c)
// 数据拟合
actPoints, expPoints1, fa1, fb1 := OptimizeParabola(&df)
fmt.Println("Fa1", fa1, "Fb1", fb1)
_, expPoints2, fa2 := OptimizeSin(&df)
fmt.Println("Fa2", fa2)
_, expPoints3, fa3, fb3, fc3 := OptimizeGrade3(&df)
fmt.Println("Fa3", fa3, "Fb3", fb3, "Fc3", fc3, "Fd3")
_, expPoints4, fa4 := OptimizeSin(&df)
fmt.Println("Fa4", fa4)
// 拟合完成,输出fa,fb
// 数据绘图
dataPlot2(actPoints, expPoints1, expPoints2, expPoints3, expPoints4)
fmt.Println("绘制完成,图形地址: baicycleclassification-fit.png")
newExpPoints1 := DayFee(expPoints1)
fmt.Println("newExpPoints1 is:", newExpPoints1)
newExpPoints2 := DayFee(expPoints2)
fmt.Println("newExpPoints2 is:", newExpPoints2)
newExpPoints3 := DayFee(expPoints3)
fmt.Println("newExpPoints3 is:", newExpPoints3)
}