前面人工智能导论(2)——基本算法程序编写——回归介绍了Python中使用线性拟合工具,我就想在C#中能不能做一个线性拟合工具,方便我们进行线性拟合。
界面布局和基础准备
我们简单的绘制一个窗体,其中可以进行选择数据,显示原始数据以及进行数据拟合。 其中的存放数据的容器使用的是DataGripView,其他的应该都很熟悉。
之前在设计串口示波器的时候,我们引入了MathNet数学库,本期我们也是利用MathNet库中的方法进行数据拟合。
using MathNet.Numerics.LinearAlgebra;using MathNet.Numerics.LinearAlgebra.Double;using MathNet.Numerics.LinearRegression;
导入我们的MathNet相关库(在NuGet中获取)
除此之外我们还需要安装其他的几个库,也在NuGet管理库中添加。 CsvHelper, EPPlus
这两个库可以帮助我们解析CSV文件和EXCEL文件。 功能编写
private void DataChoose_Click(object sender, EventArgs e){ using (OpenFileDialog openFileDialog = new OpenFileDialog()) { openFileDialog.Filter = "CSV Files (*.csv)|*.csv|Excel Files (*.xls;*.xlsx)|*.xls;*.xlsx|All Files (*.*)|*.*"; DialogResult result = openFileDialog.ShowDialog(); if (result == DialogResult.OK) { string selectedFilePath = openFileDialog.FileName; LoadDataFromSelectedFile(selectedFilePath); } }}
当我们点击按钮的时候,唤醒资源管理器,选择我们的CSV文件或者Excel文件,并且将文件路劲作为变量传递给
LoadDataFromSelectedFile函数。
private void LoadDataFromSelectedFile(string filePath) { try { DataTable dataTable = new DataTable(); //作为数据源 using (StreamReader sr = new StreamReader(filePath)) { string[] headers = sr.ReadLine().Split(','); foreach (string header in headers) { dataTable.Columns.Add(header); }
while (!sr.EndOfStream) { string[] rows = sr.ReadLine().Split(','); DataRow dr = dataTable.NewRow(); for (int i = 0; i < headers.Length; i++) { dr[i] = rows[i];//读取列 } dataTable.Rows.Add(dr); }
} DataGridView.DataSource = dataTable; foreach (DataGridViewColumn column in DataGridView.Columns) { string columnHeader = column.HeaderText; XdataChoose.Items.Add(columnHeader);//添加头 YDataChoose.Items.Add(columnHeader);//添加头 } } catch (Exception ex) { MessageBox.Show("数据错误: " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
在这个函数中,我们将标题头坐标添加到ComomBox控件上,之后方便我们选择数据。新建一个DataTable变量,这个变量作为数据源将数据文件中的行列送入,
之后把DataGripView绑定到数据源上,使得我们的数据可以显示出来。
private void DataShowInChart_Click(object sender, EventArgs e) { //检查数据是否选择 if (XdataChoose.SelectedItem == null || YDataChoose.SelectedItem == null) { MessageBox.Show("未选择数据", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; }
string xColumnName = XdataChoose.SelectedItem.ToString(); string yColumnName = YDataChoose.SelectedItem.ToString();
//获取标签索引 int xColumnIndex = -1; foreach (DataGridViewColumn column in DataGridView.Columns) { if (column.HeaderText == xColumnName) { xColumnIndex = column.Index; break; } }
int yColumnIndex = -1; foreach (DataGridViewColumn column in DataGridView.Columns) { if (column.HeaderText == yColumnName) { yColumnIndex = column.Index; break; } }
if (xColumnIndex == -1 || yColumnIndex == -1) { MessageBox.Show("未找到数据", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; }
// 删除原来的曲线 DataChart.Series.Clear();
Series series = new Series("原始数据"); series.ChartType = SeriesChartType.Point;
foreach (DataGridViewRow row in DataGridView.Rows) { //检查不是空行 if (!row.IsNewRow) { double xValue = Convert.ToDouble(row.Cells[xColumnIndex].Value); double yValue = Convert.ToDouble(row.Cells[yColumnIndex].Value);
series.Points.AddXY(xValue, yValue); } } DataChart.Series.Add(series);
//设置标签 DataChart.ChartAreas[0].AxisX.Title = xColumnName; DataChart.ChartAreas[0].AxisY.Title = yColumnName; }
当我们点击数据显示按钮的时候,先检查我们的XY轴是否已经选择,如果没有选择我们则进行报错,并退出函数。
如果选择没有问题的话,我们就先检测数据是否有异常(这里应该添加更多的判断逻辑)之后根据我们选择的XY轴来将数据打印到Chart上作为原始数据。
private void LineFit() { //检查数据是否已经选择 if (XdataChoose.SelectedItem == null || YDataChoose.SelectedItem == null) { MessageBox.Show("未选择数据", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; }
string xColumnName = XdataChoose.SelectedItem.ToString();
// 创建列表来存储数据 List<double> xData = new List<double>(); List<double> yData = new List<double>();
foreach (DataPoint point in DataChart.Series[0].Points) //数据在第一个系列中 { xData.Add(point.XValue); yData.Add(point.YValues[0]); // Y 值在第一个 Y 值中 }
var regression = MathNet.Numerics.Fit.Line(xData.ToArray(), yData.ToArray());
double slope = regression.Item2;//斜率 double intercept = regression.Item1;//截距 //打印拟合函数 Result.Text = $"y = {slope:F4}x + {intercept:F4}"; //遍历XData,将XData的数据代入拟合函数打印到DataChart上 Series fitSeries = new Series("拟合曲线"); fitSeries.ChartType = SeriesChartType.Line; fitSeries.BorderWidth = 3; //计算拟合曲线 foreach (double xValue in xData) { double yValue = slope * xValue + intercept; fitSeries.Points.AddXY(xValue, yValue); } DataChart.Series.Add(fitSeries); }
当我们点击拟合按钮,我们从波形图中获取各个点的数据保存到列表中,之后调用数学库中的线性拟合函数,将XY作为参数传入。
** 效果展示 ** regression 的两个成员item1和item2分别是截距和斜率,根据我们的截距和斜率来画出我们的线性拟合曲线。