Unity中画2D图表(5)——给定一系列散点,拟合出一条曲线

news2024/9/27 17:33:17

一、散点数据、拟合直线、拟合曲线

  • 蓝色圆点是数据样本
  • 直线为拟合的直线
  • 曲线是拟合出来的曲线
    在这里插入图片描述

二、C#中曲线拟合的实现

  • 0、曲线拟合的一般步骤(以平面坐标XY为例)
    【1】给定计算拟合的阶数k,k的取值最大为【样本个数-1】
    【2】计算出拟合参数列表
    【3】给定一个x值,用拟合参数计算对应的y值
    【4】计算所有的y值

注意:拟合阶数是一个参数变量,根据值的不同,存在欠拟合和过拟合的情况

1、使用到的包——MathNet

using MathNet.Numerics;

2、曲线拟合的函数

Fit.Polynomial(X, Y, k)

X和Y是一个同型的double数组
k是阶数,为int类型

3、一个简单地测试例子

double[] X = Enumerable.Range(1, 7).Select(r => (double)r).ToArray();
double[] Y = new double[] { 1, 3, 90, 150, 7, 6, 4 };
double[] parameters = Fit.Polynomial(X, Y, 5);  //此处拟合阶数为5(阶数能取的最大值为:【样本个数 - 1】 = 6),此时返回的参数列表长度为【阶数+1】= 6
  • 此处拟合阶数为5(阶数能取的最大值为:【样本个数 - 1】 = 6)
  • 参数列表长度为【阶数+1】= 6

4、如何通过给定的x坐标值和拟合参数,计算y值

以上图为例,参数组合parameters计算出来为:[913.28 , -1786.49 , 1156.04 , -317.26 , 38.92 , -1.76]
简要记录为[p0,p1,p2,p3,p4,p5]

给定一个x值,要计算对应的y值,公式为
y = f(x) = p0 * x^0 + p1 * x^1 + p2 * x^2 + … + p5 * x^5

/// <summary>
/// 根据拟合参数和给定的x值,计算对应的y值
  /// y = f(x) = p0 * x^0 + p1 * x^1 + p2 * x^2 + .... + pk * x^k
  /// </summary>
  /// <param name="X">x的坐标系列</param>
  /// <param name="paras">拟合参数数组</param>
  /// <returns>拟合出来的Y系列值</returns>
  public double[] getNewY(double[]X,double[]paras)
  {
      var newY = X.Select(x =>
              paras
                  .Select((p, index) => p * math.pow(x, index))  //计算多项式的每一项
                  .Sum()                                                     //所有项求和  
      ).ToArray();

      return newY;
  }

5、原有样本太少,中间插入更多的估值点

  • 【1】生成更密的x系列
 /// <summary>
    /// 在x1和x2之间插值,步进为step,生成一个新的系列[x1...x2]
    /// </summary>
    /// <param name="x1">起始值</param>
    /// <param name="x2">结束值</param>
    /// <param name="step">步进值</param>
    /// <returns>返回的系列</returns>
    public double[] genNewX(double x1, double x2, double step)
    {
        List<double> newX = new();
        newX.Add(x1);

        double currentX = x1;
        while (currentX <= x2)
        {
            currentX += step;
            newX.Add(currentX);
        }

        if (!newX.Contains(x2)) newX.Add(x2);

        return newX.ToArray();
    }
  • 【2】计算x新列对应的y系列值
double[] parameters = Fit.Polynomial(X.ToArray(), Y.ToArray(), 5);

double[] newX = genNewX(X.Min(), X.Max(), 0.1f);
Debug.Log("生成新的X坐标完成");

//计算新的拟合值
var newY = getNewY(newX, parameters).ToList();
Debug.Log("生成新的Y坐标完成");

三、代码清单

using MathNet.Numerics;
using System;
using System.Collections.Generic;
using UnityEngine;
using XCharts.Runtime;
using System.Linq;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;

/// <summary>
/// 曲线拟合
/// </summary>
public class FitPolynomial : MonoBehaviour
{
    public GameObject charRoot;
    public List<double> X = new List<double>();
    public List<double> Y = new List<double>();

    // Start is called before the first frame update

#if UNITY_EDITOR
    [ContextMenu("曲线拟合")]
#endif
    void FitPolynomialTest()
    {
        /*
         * 多项式拟合如何计算?
         */
        double[] X = Enumerable.Range(1, 7).Select(r => (double)r).ToArray();
        double[] Y = new double[] { 1, 3, 90, 150, 7, 6, 4 };
        double[] parameters = Fit.Polynomial(X, Y, 5);  //阶数最大为【样本个数 - 1】

        Debug.Log($"参数数组的大小为:{parameters.Length}");

        //拟合参数的数组:数组大小为【阶数 + 1】,阶数最大只能为【样本个数 - 1】
        parameters.ToList().ForEach(x => Debug.Log(x));  //显示参数

        //计算结果值
        List<double> result = new List<double>();
        for (int i = 1; i < 8; i++)
        {
            result.Add(parameters[0] * math.pow(i, 0) + parameters[1] * math.pow(i, 1) + parameters[2] * math.pow(i, 2) + parameters[3] * math.pow(i, 3) + parameters[4] * math.pow(i, 4) + parameters[5] * math.pow(i, 5));
        }

        List<double> result2 = getNewY(X, parameters).ToList();

        //Debug.Log($"Pow(2,0) = {math.pow(2,0)}  \n  Pow(2,1) = {math.pow(2, 1)}  \n  Pow(2,2) = {math.pow(2, 2)} ");
        for (int i = 0; i < X.Length; i++)
        {
            Debug.Log($"{X[i]}    {Y[i]}   {result[i]}   {result2[i]}");
        }
    }

    /// <summary>
    /// 根据拟合参数和给定的x值,计算对应的y值
    /// y = f(x) = p0 * x^0 + p1 * x^1 + p2 * x^2 + .... + pk * x^k
    /// </summary>
    /// <param name="X">x的坐标系列</param>
    /// <param name="paras">拟合参数数组</param>
    /// <returns>拟合出来的Y系列值</returns>
    public double[] getNewY(double[]X,double[]paras)
    {
        var newY = X.Select(x =>
                paras
                    .Select((p, index) => p * math.pow(x, index))  //计算多项式的每一项
                    .Sum()                                                     //所有项求和  
        ).ToArray();

        return newY;
    }

    /// <summary>
    /// 在x1和x2之间插值,步进为step,生成一个新的系列[x1...x2]
    /// </summary>
    /// <param name="x1">起始值</param>
    /// <param name="x2">结束值</param>
    /// <param name="step">步进值</param>
    /// <returns>返回的系列</returns>
    public double[] genNewX(double x1, double x2, double step)
    {
        List<double> newX = new();
        newX.Add(x1);

        double currentX = x1;
        while (currentX <= x2)
        {
            currentX += step;
            newX.Add(currentX);
        }

        if (!newX.Contains(x2)) newX.Add(x2);

        return newX.ToArray();
    }

#if UNITY_EDITOR
    [ContextMenu("画散点图并拟合一条直线")]
#endif
    void DrawLine()
    {
        Flow();
    }

    private async UniTask Flow()
    {
        //【1】=============画散点图=============
        var scatter = charRoot.AddComponent<ScatterChart>();
        scatter.Init();//初始化极为重要,不然在running状态会报空

        var title = scatter.GetOrAddChartComponent<Title>();
        title.text = "用给定的散点拟合出一条直线和一条曲线";         //主标题

        await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
        scatter.SetSize(1024, 768);
        scatter.AddSerie<Serie>("数据分布图");
        Debug.Log($"数据长度为:{scatter.series[0].data.Count}"); //默认自带10个数据,需要删除
        scatter.series[0].data.Clear();

        await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
        var X = new List<double> { 1, 2, 3, 4, 5, 6,7,8 ,9,10,11,12,13};
        var Y = new List<double> { -20, -8, 6, 48,200,300,500, 800,500,100, 20,2,0 };

        var test = X.Zip(Y, (x, y) => new List<double> { x, y }).ToList();

        X.Zip(Y, (x, y) => new List<double> { x, y }).ToList().ForEach(t =>
        {
            //Debug.Log($"添加数据:{t[0]} => {t[1]}");
            scatter.AddData(0, t);
        });
        scatter.series[0].symbol.size = 9;//设置大小

        //【2】=============直线拟合计算=============
        await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
        var s = Fit.Line(X.ToArray(), Y.ToArray());
        double b = s.Item1; //截距
        double k = s.Item2; //斜率

        //【3】=============画拟合出来的直线=============
        var line1 = scatter.AddChartComponent<MarkLine>(); //添加一根直线
        line1.data.Clear();//清空默认值,添加组的时候,会默认包含一个item

        //增加一个端点数据
        await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
        var p1 = new MarkLineData();
        p1.type = MarkLineType.None;
        p1.group = 1;
        p1.xPosition = 0;
        p1.yPosition = 0;
        p1.xValue = X.Min();
        p1.yValue = X.Min() * k + b;
        p1.zeroPosition = false;

        //标注【直线方程式】
        await UniTask.Delay(TimeSpan.FromSeconds(2.0f));
        var op = b > 0 ? "+" : "-";

        var kStr = Math.Round(k, 2, MidpointRounding.AwayFromZero).ToString();
        var bStr = Math.Round(math.abs(b), 2, MidpointRounding.AwayFromZero).ToString();
        p1.name = $"y = {kStr} * x {op}{bStr}";
        p1.label.formatter = "{b}";

        //直线的形状设置
        p1.lineStyle.type = LineStyle.Type.Solid;
        p1.startSymbol.type = SymbolType.None;
        p1.endSymbol.type = SymbolType.None;

        //添加第二个端点数据
        var p2 = new MarkLineData();
        p2.type = MarkLineType.None;
        p2.group = 1;
        p2.xPosition = 0;
        p2.yPosition = 0;
        p2.xValue = X.Max();
        p2.yValue = X.Max() * k + b;
        p2.zeroPosition = false;
        p2.name = $"y = {kStr} * x {op}{bStr}"; ;//"y = 0 * x + 15";
        p2.label.formatter = "{b}";

        Debug.Log($"y = {kStr} * x {op}{bStr}");

        //直线的形状设置
        p2.lineStyle.type = LineStyle.Type.Solid;
        p2.startSymbol.type = SymbolType.None;
        p2.endSymbol.type = SymbolType.None;

        //端点数据加入直线中
        line1.data.Add(p1);
        line1.data.Add(p2);

        //【4】曲线拟合:拟合参数的计算
        double[] parameters = Fit.Polynomial(X.ToArray(), Y.ToArray(), 5);

        double[] newX = genNewX(X.Min(), X.Max(), 0.1f);
        Debug.Log("生成新的X坐标完成");

        //计算新的拟合值
        var newY = getNewY(newX, parameters).ToList();
        Debug.Log("生成新的Y坐标完成");

        //画拟合曲线
        var serie2 = scatter.AddSerie<Line>("拟合曲线");
        newX.Zip(newY, (x, y) => new List<double> { x, y }).ToList().ForEach(t =>
        {
            scatter.AddData(1, t);
        });

        scatter.RefreshAllComponent();
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366922.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GoLang 协程池

Goroutine 1.Goroutine 是 Golang 提供的一种轻量级线程&#xff0c;我们通常称之为「协程」&#xff0c;相比较线程&#xff0c;创建一个协程的成本是很低的。所以你会经常看到 Golang 开发的应用出现上千个协程并发的场景。 Goroutine 的优势&#xff1a; 与线程相比&#xf…

Spring Security+jwt+redis+自定义认证逻辑 权限控制

Spring Securityjwtredis自定义认证逻辑 权限控制 1.拦截访问基本思路 2.创建数据库表&#xff1a;角色表&#xff08;应该6个表&#xff0c;这里只用用户表代替角色表&#xff09;、权限表、路径表、角色-权限表、权限-路径表 /* SQLyog Professional v12.14 (64 bit) MySQL…

Leetcode第450题删除二叉搜索树中的结点|C语言

题目&#xff1a; 给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key 对应的节点&#xff0c;并保证二叉搜索树的性质不变。返回二叉搜索树&#xff08;有可能被更新&#xff09;的根节点的引用。 一般来说&#xff0c;删除节点可分为两个步骤…

一个跟蘑菇结缘的企业老板

记得那是一个很久以前的一家公司了董事长办公室里中的大型盆栽里面长了一个蘑菇董事长认为是祥瑞每天都会浇水后来一个新来的保洁阿姨以为杂草啥的给他掰掉扔垃圾桶了董事长第二天来浇水的时候发现没了就问谁动了他的蘑菇问道之后就跑到楼道大垃圾桶那里把蘑菇找回来种在花盆里…

“点工”的觉悟,5年时间从7K到24K的转变,我的测试道路历程~

2015年7月我从一个90%以上的人都不知道的二本院校毕业&#xff08;新媒体专业&#xff09;&#xff0c;凭借自学的软件测试&#xff08;点点点&#xff09;在北京找到了一份月薪7000的工作&#xff0c;在当时其实还算不错&#xff0c;毕竟我的学校起点比较差&#xff0c;跟大部…

python学习笔记——csv文件

目录 一、csv文件和Excel文件区别 二、手动转换&#xff08;文本与列表&#xff09; ①普通的写(列表嵌套转成文本的表格形式) ②普通的读&#xff08;文本的表格形式转成列表嵌套&#xff09; 二、csv库-读 1、CSV库-读-reader() 2、CSV库-读-DictReader() 三、csv库-写 …

基于YOLO的酸枣病虫害检测识别实践

在我前面的博文中对于农作物病虫害的检测识别已经做过了&#xff0c;不过那个主要是针对水稻的&#xff0c;文章如下&#xff1a;《基于yolov5的轻量级水稻虫害目标检测项目实践》感兴趣的话可以自行移步阅读。这里主要是针对酸枣常见的几种病虫害检测检测识别&#xff0c;首先…

苹果电脑如何录屏?3个方法,轻松学会

苹果电脑是很多创作者、视频制作人和经常工作用户的选择&#xff0c;但是如何在苹果电脑上录制高质量的屏幕视频呢&#xff1f;苹果电脑如何录屏&#xff1f;本文将介绍3种不同的方法&#xff0c;帮助小伙伴轻松学会如何在苹果电脑上录制屏幕视频。 方法一&#xff1a;使用Mac自…

假设检验的基本思想

假设检验 首先了解参数估计&#xff0c;比如有服从正态分布的数据集X∼N(μ,σ2)X\sim N(\mu,\sigma^{2})X∼N(μ,σ2)&#xff0c;我们希望根据样本x1,...xnx_{1},...x_{n}x1​,...xn​估计出参数μ,σ\mu,\sigmaμ,σ&#xff0c;这些参数可以是一个具体值&#xff0c;也可以…

【C++】Windows动态库【.DLL文件】制作方法总结

如题&#xff0c;我们本篇介绍如何制作DLL&#xff0c;将代码类中的方法以接口的形式暴露出来给exe程序使用。会涉及类厂创建方法实例、声明DLL接口、.def文件的使用等。 目录 一、DLL介绍 二、C制作DLL文件 2.1 DLL端 2.2 调用端 三、DLL导出类方法 四、COM技术制作DLL…

扎心话题 | 设计院背后的潜规则你知道吗?

大家好&#xff0c;我是建模助手。 大家都知道&#xff0c;在过去的2022年经济是真难&#xff01;以小编所在的广东为例&#xff0c;全年GDP增长仅1.9%。 这个数据足以呈现一个社会现象——不仅消费力咔咔下降&#xff0c;各行各业更有不同程度地嗝屁。这其中也包括一些设计院…

只要一直向前定能到达远方,社科院与杜兰大学金融管理硕士项目为你注入动力

在人生这条道路上&#xff0c;我们很远的路要走&#xff0c;不管前方是否平坦&#xff0c;我们只要坚持前向&#xff0c;终将抵达远方。一路上我们付出很多&#xff0c;也收获很多。想要变得更强大&#xff0c;就要不断优化自身&#xff0c;积攒更多的能量&#xff0c;社科院与…

Flask入门(10):Flask使用SQLAlchemy

目录11.SQLAlchemy11.1 简介11.2 安装11.3 基本使用11.4 连接11.5 数据类型11.6 执行原生sql11.7 插入数据11. 8 删改操作11.9 查询11.SQLAlchemy 11.1 简介 SQLAlchemy的是Python的SQL工具包和对象关系映射&#xff0c;给应用程序开发者提供SQL的强大功能和灵活性。它提供了…

浅析无人值守+智慧巡检变电站安全管控系统设计方案

一、项目背景 安全是电力生产的基石&#xff0c;确保电网安全和人身安全&#xff0c;是电网企业安全工作的出发点和落脚点。 随着智能信息化技术应用越来越广泛&#xff0c;智能信息化现场安全管理是近年来基于智能安全巡检技术下发展起来的现场作业安全管理新技术。 变电站运…

【机器学习】朴素贝叶斯算法

朴素贝叶斯&#xff08;Naive Bayes&#xff09;是经典的机器学习算法之一&#xff0c;也是为数不多的基于概率论的分类算法。由于朴素贝叶斯计算联合概率&#xff0c;所以朴素贝叶斯模型属于生成式模型。经典应用案例包括&#xff1a;文本分类、垃圾邮件过滤等。 1.贝叶斯公式…

rust 安装

rust 安装一、需要一个c的环境二、配置环境变量三、开始安装一、需要一个c的环境 安装Visual Studio 二、配置环境变量 Rust需要安装两个东西&#xff0c;一个是rustup&#xff0c;一个是cargo。所以你需要设置两个环境变量来分别指定他们的安装目录。 通过RUSTUP_HOME指定…

滤波算法:经典卡尔曼滤波

卡尔曼滤波实质上就是基于观测值以及估计值二者的数据对真实值进行估计的过程。预测步骤如图1所示&#xff1a; ​图1 卡尔曼滤波原理流程图 假设我们能够得到被测物体的位置和速度的测量值 ​&#xff0c;在已知上一时刻的最优估计值 ​以及它的协方差矩阵 的条件下&#xff…

ChatGPT热潮背后,金融行业大模型应用路在何方?——金融行业大模型应用探索

ChatGPT近两个月以来不断引爆热点&#xff0c;对人工智能应用发展的热潮前所未有地高涨&#xff0c;ChatGPT所代表的大模型在语义理解、多轮交互、内容生成中所展现的突出能力令人惊喜。而人工智能技术在金融行业的落地应用仍然面临挑战&#xff0c;虽然已经让大量宝贵的人力从…

易基因|ChIP-seq等组学研究鉴定出结直肠癌的致癌超级增强子:Nature子刊

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。超级增强子&#xff08;Super enhancer&#xff09;是一类包含多个普通增强子的大簇&#xff0c;主要富集高密度的转录因子、辅助因子及增强子相关表观修饰位点。与普通增强子相比&#xf…

canal实时同步mysql数据到elasticsearch(部署,配置,测试)

这里写目录标题简介工作原理MySQL主备复制原理canal 工作原理canal 使用流程环境搭建环境使用版本mysql配置修改配置创建从库权限账号创建测试数据库创建测试数据表elasticsearch配置创建索引建立映射canal的下载部署下载canal配置服务端 canal-deployer配置客户端canal-adapte…