让我们可以使用C99和C++11完成常见的数据科学任务。虽然Python和R等语言在数据科学中越来越受欢迎,但C和C++是高效数据科学的不错选择。在本文中,我们将使用C99和C++11编写一个使用Anscombe的四重奏数据集的程序,下面将对其进行说明。我在一篇涵盖Python和GNUOctave的文章中写了我继续学习编程语言的动机,值得回顾。这里的所有程序都需要在命令行上运行,而不是在图形用户界面(GUI)上运行。可以在polyglot_fit存储库中找到完整的示例。编程任务您将在本系列中编写的程序:从CSV文件读取数据用直线插值数据(即f(x)=m?x+q)将结果绘制到图像文件这是许多数据科学家遇到的情况一般情况。示例数据是Anscombe的四重奏的第一组,如下表所示。这是一组人为构造的数据,在拟合直线时给出相同的结果,但它们的曲线却大不相同。数据文件是一个文本文件,以制表符作为列分隔符,前几行作为标题。该任务将仅使用第一组(即前两列)。CWayC是一种通用编程语言,也是当今使用最广泛的语言之一(根据TIOBE指数、RedMonk编程语言排名、编程语言流行指数和GitHubOctoverse状态)。它是一种相当古老的语言(诞生于1973年左右),许多成功的程序都是用它编写的(例如Linux内核和Git,仅举两个例子)。它也是最接近计算机内部工作原理的语言之一,因为它直接用于操作内存。它是一种编译语言;因此,源代码必须由编译器转换为机器代码。它的标准库很小并且没有很多功能,因此开发了其他库来提供缺少的功能。我最常使用该语言进行数字运算,主要是因为它的性能。我发现它使用起来很乏味,因为它需要大量的样板代码,但它在各种环境中都得到了很好的支持。C99标准是最新版本,添加了一些漂亮的功能,并且得到了编译器的良好支持。在此过程中,我将提供必要的C和C++编程背景知识,以便初学者和高级用户都可以跟进。安装要使用C99进行开发,您需要一个编译器。我通常使用Clang,但GCC是另一个有效的开源编译器。对于线性拟合,我选择使用GNU科学库。对于绘图,我找不到任何合理的库,所以该程序依赖于外部程序:Gnuplot。该示例还使用BerkeleySoftwareDistribution(BSD)中定义的动态数据结构来存储数据。在Fedora中安装很容易:sudodninstallclanggnuplotgslgsl-devel代码注释在C99中,注释的格式是将//放在行的开头,该行的其余部分将被解释器丢弃。此外,/*和*/之间的任何内容都将被丢弃。//这是注释,会被解释器忽略/*这也被忽略*/必要的库库由两部分组成:头文件,包含函数描述,源文件包含函数定义头文件文件包含在源文件中,库文件的源文件被链接到可执行文件中。所以这个例子需要的头文件是://Input/OutputFunctions#include//StandardLibrary#include//Stringmanipulationfunctions#include//BSDqueue#include//GSLscientificfunction#include#includemain函数在C语言中,程序必须位于主函数main中()里面有一个特殊的函数:intmain(void){...}这和之前教程介绍的Python不同,它会运行所有在源文件中找到的代码。定义变量在C中,变量必须在使用前声明,并且必须与类型相关联。每当你想使用一个变量时,你必须决定你想在其中存储什么样的数据。您还可以指定该变量是否打算用作常量值,这不是必需的,但编译器可以从该信息中获益。以下内容来自存储库中的fitting_C99.c程序:constchar*input_file_name="anscombe.csv";constchar*delimiter="\t";constunsignedintskip_header=3;constunsignedintcolumn_x=0;constunsignedintcolumn_y=1;constchar*output_file_name="fit_C99.csv";constunsignedintN=100;C中的数组不是动态的,因为数组的长度必须提前确定(即编译前):intdata_array[1024];由于您通常不知道有多少个数据点,所以使用单向链表。这是一个可以无限增长的动态数据结构。幸运的是,BSD提供了链表。下面是一个定义示例:这个例子定义了一个由结构化值组成的data_point结构化值列表,同时包含x和y值。语法相当复杂,但很直观,如果不详细描述就太冗长了。打印输出要在终端打印,可以使用printf()函数,这个函数类似于Octave的printf()函数(第一篇介绍):printf("####Anscombe'sfirstsetwithC99####\n");printf()函数不会自动在打印字符串的末尾添加换行符,因此您必须添加换行符。第一个参数是一个字符串,它可以包含传递给函数的其他参数的格式信息,例如:printf("Slope:%f\n",slope);读取数据现在困难的部分来了……有一些用C语言解析CSV文件的库,但似乎没有一个稳定或流行到足以包含在Fedora软件包存储库中。我决定自己编写这部分,而不是为本教程添加依赖项。同样,讨论这些细节太啰嗦了,所以我只解释大概的思路。为简洁起见,将忽略源代码中的某些行,但您可以在存储库中找到完整的示例代码。首先,打开输入文件:FILE*input_file=fopen(input_file_name,"r");然后逐行读取文件,直到发生错误或文件结束:while(!ferror(input_file)&&!feof(input_file)){size_tbuffer_size=0;char*buffer=NULL;getline(&buffer,&buffer_size,input_file);...}getline()函数是添加到POSIX.1-2008标准中的一个很好的函数。它可以读取文件中的整行并负责分配必要的内存。然后使用strtok()函数将每一行拆分为字符标记。遍历字符并选择所需的列:char*token=strtok(buffer,delimiter);while(token!=NULL){doublevalue;sscanf(token,"%lf",&value);if(column==column_x){x=value;}elseif(column==column_y){y=value;}column+=1;token=strtok(NULL,delimiter);}最后选择x和y值时,插入新的数据点进入链表中间:structdata_point*datum=malloc(sizeof(structdata_point));datum->x=x;datum->y=y;SLIST_INSERT_HEAD(&head,datum,entries);malloc()函数动态分配(保留)新数据点一些持久内存。拟合数据GSL线性拟合函数gslfitlinear()期望它的输入是一个简单的数组。因此,由于您不知道要创建的数组的大小,因此您必须手动分配它们的内存:constsize_tentries_number=row-skip_header-1;double*x=malloc(sizeof(double)*entries_number);double*y=malloc(sizeof(double)*entries_number);然后,遍历链表将相关数据保存到数组中:y[i]=current_y;i+=1;}现在你已经完成了链表,请清理它。始终释放已手动分配的内存以防止内存泄漏。内存泄漏是坏的、坏的、坏的(三个重要的词)。每次内存未释放时,花园侏儒都找不到自己的头:while(!SLIST_EMPTY(&head)){structdata_point*datum=SLIST_FIRST(&head);SLIST_REMOVE_HEAD(&head,entries);free(datum);}终于,终于!您可以拟合数据:gsl_fit_linear(x,1,y,1,entries_number,&intercept,&slope,&cov00,&cov01,&cov11,&chi_squared);constdoubler_value=gsl_stats_correlation(x,1,y,1,entries_number);printf("Slope:%f\n",slope);printf("截距:%f\n",截距);printf("相关系数:%f\n",r_value);绘图你必须使用外部程序绘图。因此将拟合数据保存到外部文件:constdoublestep_x=((max_x+1)-(min_x-1))/N;for(unsignedinti=0;i#include#includeextern"C"{#include#include}由于GSL库是用C写的,所以必须包含这个特例通知编译器。定义变量C++比C支持更多的数据类型(类),例如,字符串类型比C对应的类型具有更多的功能。相应地更新变量的定义:conststd::stringinput_file_name("anscombe.csv");对于像字符串这样的结构化对象,您可以定义不带=号的变量。要打印输出,您可以使用printf()函数,但cout对象更为惯用。使用运算符<<指示要使用cout打印的字符串(或对象):std::cout<<"####Anscombe'sfirstsetwithC++11####"<x;std::vectory;//Addinganelementtoxandy:x。emplace_back(值);y.emplace_back(值);拟合数据要在C++中拟合,您不必遍历列表,因为向量保证有连续的内存。您可以将指向向量缓冲区的指针直接传递给拟合函数:gsl_fit_linear(x.data(),1,y.data(),1,entries_number,&intercept,&slope,&cov00,&cov01,&cov11,&chi_squared);constdoubler_value=gsl_stats_correlation(x.data(),1,y.data(),1,entries_number);std::cout<<"坡度:"<<坡度<