在数据科学中使用 C 和 C++
数据科学

> 
> 让我们使用 C99 和 C++11 完成常见的数据科学任务。
> 
> 
> 


![](/data/attachment/album/202003/02/101924x4ptbiuple52gtwo.jpg)


虽然 [Python](https://opensource.com/article/18/9/top-3-python-libraries-data-science) 和 [R](https://opensource.com/article/19/5/learn-python-r-data-science) 之类的语言在数据科学中越来越受欢迎，但是 C 和 C++ 对于高效的数据科学来说是一个不错的选择。在本文中，我们将使用 [C99](https://en.wikipedia.org/wiki/C99) 和 [C++11](https://en.wikipedia.org/wiki/C%2B%2B11) 编写一个程序，该程序使用 [Anscombe 的四重奏](https://en.wikipedia.org/wiki/Anscombe%27s_quartet)数据集，下面将对其进行解释。


我在一篇涉及 [Python 和 GNU Octave](/article-11943-1.html) 的文章中写了我不断学习编程语言的动机，值得大家回顾。这里所有的程序都需要在[命令行](https://en.wikipedia.org/wiki/Command-line_interface)上运行，而不是在[图形用户界面（GUI）](https://en.wikipedia.org/wiki/Graphical_user_interface)上运行。完整的示例可在 [polyglot\_fit 存储库](https://gitlab.com/cristiano.fontana/polyglot_fit)中找到。


### 编程任务


你将在本系列中编写的程序：


* 从 [CSV 文件](https://en.wikipedia.org/wiki/Comma-separated_values)中读取数据
* 用直线插值数据（即 `f(x)=m ⋅ x + q`）
* 将结果绘制到图像文件


这是许多数据科学家遇到的普遍情况。示例数据是 [Anscombe 的四重奏](https://en.wikipedia.org/wiki/Anscombe%27s_quartet)的第一组，如下表所示。这是一组人工构建的数据，当拟合直线时可以提供相同的结果，但是它们的曲线非常不同。数据文件是一个文本文件，其中的制表符用作列分隔符，前几行作为标题。该任务将仅使用第一组（即前两列）。


![](/data/attachment/album/202002/29/122805h3yrs1dkrgysssxk.png)


### C 语言的方式


[C](https://en.wikipedia.org/wiki/C_%28programming_language%29) 语言是通用编程语言，是当今使用最广泛的语言之一（依据 [TIOBE 指数](https://www.tiobe.com/tiobe-index/)、[RedMonk 编程语言排名](https://redmonk.com/sogrady/2019/07/18/language-rankings-6-19/)、[编程语言流行度指数](http://pypl.github.io/PYPL.html)和 [GitHub Octoverse 状态](https://octoverse.github.com/) 得来）。这是一种相当古老的语言（大约诞生在 1973 年），并且用它编写了许多成功的程序（例如 Linux 内核和 Git 仅是其中的两个例子）。它也是最接近计算机内部运行机制的语言之一，因为它直接用于操作内存。它是一种[编译语言](https://en.wikipedia.org/wiki/Compiled_language)；因此，源代码必须由[编译器](https://en.wikipedia.org/wiki/Compiler)转换为[机器代码](https://en.wikipedia.org/wiki/Machine_code)。它的[标准库](https://en.wikipedia.org/wiki/C_standard_library)很小，功能也不多，因此人们开发了其它库来提供缺少的功能。


我最常在[数字运算](https://en.wiktionary.org/wiki/number-crunching)中使用该语言，主要是因为其性能。我觉得使用起来很繁琐，因为它需要很多[样板代码](https://en.wikipedia.org/wiki/Boilerplate_code)，但是它在各种环境中都得到了很好的支持。C99 标准是最新版本，增加了一些漂亮的功能，并且得到了编译器的良好支持。


我将一路介绍 C 和 C++ 编程的必要背景，以便初学者和高级用户都可以继续学习。


#### 安装


要使用 C99 进行开发，你需要一个编译器。我通常使用 [Clang](https://clang.llvm.org/)，不过 [GCC](https://gcc.gnu.org/) 是另一个有效的开源编译器。对于线性拟合，我选择使用 [GNU 科学库](https://www.gnu.org/software/gsl/)。对于绘图，我找不到任何明智的库，因此该程序依赖于外部程序：[Gnuplot](http://www.gnuplot.info/)。该示例还使用动态数据结构来存储数据，该结构在[伯克利软件分发版（BSD）](https://en.wikipedia.org/wiki/Berkeley_Software_Distribution)中定义。


在 [Fedora](https://getfedora.org/) 中安装很容易：



```
sudo dnf install clang gnuplot gsl gsl-devel
```

#### 代码注释


在 C99 中，[注释](https://en.wikipedia.org/wiki/Comment_(computer_programming))的格式是在行的开头放置 `//`，行的其它部分将被解释器丢弃。另外，`/*` 和 `*/` 之间的任何内容也将被丢弃。



```
// 这是一个注释，会被解释器忽略
/* 这也被忽略 */
```

#### 必要的库


库由两部分组成：


* [头文件](https://en.wikipedia.org/wiki/Include_directive)，其中包含函数说明
* 包含函数定义的源文件


头文件包含在源文件中，而库文件的源文件则[链接](https://en.wikipedia.org/wiki/Linker_%28computing%29)到可执行文件。因此，此示例所需的头文件是：



```
// 输入/输出功能
#include <stdio.h>
// 标准库
#include <stdlib.h>
// 字符串操作功能
#include <string.h>
// BSD 队列
#include <sys/queue.h>
// GSL 科学功能
#include <gsl/gsl_fit.h>
#include <gsl/gsl_statistics_double.h>
```

#### 主函数


在 C 语言中，程序必须位于称为主函数 [main()](https://en.wikipedia.org/wiki/Entry_point#C_and_C++) 的特殊函数内：



```
int main(void) {
    ...
}
```

这与上一教程中介绍的 Python 不同，后者将运行在源文件中找到的所有代码。


#### 定义变量


在 C 语言中，变量必须在使用前声明，并且必须与类型关联。每当你要使用变量时，都必须决定要在其中存储哪种数据。你也可以指定是否打算将变量用作常量值，这不是必需的，但是编译器可以从此信息中受益。 以下来自存储库中的 [fitting\_C99.c 程序](https://gitlab.com/cristiano.fontana/polyglot_fit/-/blob/master/fitting_C99.c)：



```
const char *input_file_name = "anscombe.csv";
const char *delimiter = "\t";
const unsigned int skip_header = 3;
const unsigned int column_x = 0;
const unsigned int column_y = 1;
const char *output_file_name = "fit_C99.csv";
const unsigned int N = 100;
```

C 语言中的数组不是动态的，从某种意义上说，数组的长度必须事先确定（即，在编译之前）：



```
int data_array[1024];
```

由于你通常不知道文件中有多少个数据点，因此请使用[单链列表](https://en.wikipedia.org/wiki/Linked_list#Singly_linked_list)。这是一个动态数据结构，可以无限增长。幸运的是，BSD [提供了链表](http://man7.org/linux/man-pages/man3/queue.3.html)。这是一个示例定义：



```
struct data_point {
    double x;
    double y;

    SLIST_ENTRY(data_point) entries;
};

SLIST_HEAD(data_list, data_point) head = SLIST_HEAD_INITIALIZER(head);
SLIST_INIT(&head);
```

该示例定义了一个由结构化值组成的 `data_point` 列表，该结构化值同时包含 `x` 值和 `y` 值。语法相当复杂，但是很直观，详细描述它就会太冗长了。


#### 打印输出


要在终端上打印，可以使用 [printf()](https://en.wikipedia.org/wiki/Printf_format_string) 函数，其功能类似于 Octave 的 `printf()` 函数（在第一篇文章中介绍）：



```
printf("#### Anscombe's first set with C99 ####\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_t buffer_size = 0;
    char *buffer = NULL;
   
    getline(&buffer, &buffer_size, input_file);

    ...
}
```

[getline()](http://man7.org/linux/man-pages/man3/getline.3.html) 函数是 [POSIX.1-2008 标准](https://en.wikipedia.org/wiki/POSIX)新增的一个不错的函数。它可以读取文件中的整行，并负责分配必要的内存。然后使用 [strtok()](http://man7.org/linux/man-pages/man3/strtok.3.html) 函数将每一行分成<ruby> <a href="https://en.wikipedia.org/wiki/Lexical_analysis#Token">  字元 </a> <rt>  token </rt></ruby>。遍历字元，选择所需的列：



```
char *token = strtok(buffer, delimiter);

while (token != NULL)
{
    double value;
    sscanf(token, "%lf", &value);

    if (column == column_x) {
        x = value;
    } else if (column == column_y) {
        y = value;
    }

    column += 1;
    token = strtok(NULL, delimiter);
}
```

最后，当选择了 `x` 和 `y` 值时，将新数据点插入链表中：



```
struct data_point *datum = malloc(sizeof(struct data_point));
datum->x = x;
datum->y = y;

SLIST_INSERT_HEAD(&head, datum, entries);
```

[malloc()](http://man7.org/linux/man-pages/man3/malloc.3.html) 函数为新数据点动态分配（保留）一些持久性内存。


#### 拟合数据


GSL 线性拟合函数 [gslfitlinear()](https://www.gnu.org/software/gsl/doc/html/lls.html) 期望其输入为简单数组。因此，由于你将不知道要创建的数组的大小，因此必须手动分配它们的内存：



```
const size_t entries_number = row - skip_header - 1;

double *x = malloc(sizeof(double) * entries_number);
double *y = malloc(sizeof(double) * entries_number);
```

然后，遍历链表以将相关数据保存到数组：



```
SLIST_FOREACH(datum, &head, entries) {
    const double current_x = datum->x;
    const double current_y = datum->y;

    x[i] = current_x;
    y[i] = current_y;

    i += 1;
}
```

现在你已经处理完了链表，请清理它。要**总是**释放已手动分配的内存，以防止[内存泄漏](https://en.wikipedia.org/wiki/Memory_leak)。内存泄漏是糟糕的、糟糕的、糟糕的（重要的话说三遍）。每次内存没有释放时，花园侏儒都会找不到自己的头：



```
while (!SLIST_EMPTY(&head)) {
    struct data_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);
const double r_value = gsl_stats_correlation(x, 1, y, 1, entries_number);

printf("Slope: %f\n", slope);
printf("Intercept: %f\n", intercept);
printf("Correlation coefficient: %f\n", r_value);
```

#### 绘图


你必须使用外部程序进行绘图。因此，将拟合数据保存到外部文件：



```
const double step_x = ((max_x + 1) - (min_x - 1)) / N;

for (unsigned int i = 0; i < N; i += 1) {
    const double current_x = (min_x - 1) + step_x * i;
    const double current_y = intercept + slope * current_x;

    fprintf(output_file, "%f\t%f\n", current_x, current_y);
}
```

用于绘制两个文件的 Gnuplot 命令是：



```
plot 'fit_C99.csv' using 1:2 with lines title 'Fit', 'anscombe.csv' using 1:2 with points pointtype 7 title 'Data'
```

#### 结果


在运行程序之前，你必须编译它：



```
clang -std=c99 -I/usr/include/ fitting_C99.c -L/usr/lib/ -L/usr/lib64/ -lgsl -lgslcblas -o fitting_C99
```

这个命令告诉编译器使用 C99 标准、读取 `fitting_C99.c` 文件、加载 `gsl` 和 `gslcblas` 库、并将结果保存到 `fitting_C99`。命令行上的结果输出为：



```
#### Anscombe's first set with C99 ####
Slope: 0.500091
Intercept: 3.000091
Correlation coefficient: 0.816421
```

这是用 Gnuplot 生成的结果图像：


![Plot and fit of the dataset obtained with C99](/data/attachment/album/202003/02/101945buc2qchc6mm2l7v2.png "Plot and fit of the dataset obtained with C99")


### C++11 方式


[C++](https://en.wikipedia.org/wiki/C%2B%2B) 语言是一种通用编程语言，也是当今使用的最受欢迎的语言之一。它是作为 [C 的继承人](http://www.cplusplus.com/info/history/)创建的（诞生于 1983 年），重点是[面向对象程序设计（OOP）](https://en.wikipedia.org/wiki/Object-oriented_programming)。C++ 通常被视为 C 的超集，因此 C 程序应该能够使用 C++ 编译器进行编译。这并非完全正确，因为在某些极端情况下它们的行为有所不同。 根据我的经验，C++ 与 C 相比需要更少的样板代码，但是如果要进行面向对象开发，语法会更困难。C++11 标准是最新版本，增加了一些漂亮的功能，并且基本上得到了编译器的支持。


由于 C++ 在很大程度上与 C 兼容，因此我将仅强调两者之间的区别。我在本部分中没有涵盖的任何部分，则意味着它与 C 中的相同。


#### 安装


这个 C++ 示例的依赖项与 C 示例相同。 在 Fedora 上，运行：



```
sudo dnf install clang gnuplot gsl gsl-devel
```

#### 必要的库


库的工作方式与 C 语言相同，但是 `include` 指令略有不同：



```
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>

extern "C" {
#include <gsl/gsl_fit.h>
#include <gsl/gsl_statistics_double.h>
}
```

由于 GSL 库是用 C 编写的，因此你必须将这个特殊情况告知编译器。


#### 定义变量


与 C 语言相比，C++ 支持更多的数据类型（类），例如，与其 C 语言版本相比，`string` 类型具有更多的功能。相应地更新变量的定义：



```
const std::string input_file_name("anscombe.csv");
```

对于字符串之类的结构化对象，你可以定义变量而无需使用 `=` 符号。


#### 打印输出


你可以使用 `printf()` 函数，但是 `cout` 对象更惯用。使用运算符 `<<` 来指示要使用 `cout` 打印的字符串（或对象）：



```
std::cout << "#### Anscombe's first set with C++11 ####" << std::endl;

...

std::cout << "Slope: " << slope << std::endl;
std::cout << "Intercept: " << intercept << std::endl;
std::cout << "Correlation coefficient: " << r_value << std::endl;
```

#### 读取数据


该方案与以前相同。将打开文件并逐行读取文件，但语法不同：



```
std::ifstream input_file(input_file_name);

while (input_file.good()) {
    std::string line;

    getline(input_file, line);

    ...
}
```

使用与 C99 示例相同的功能提取行字元。代替使用标准的 C 数组，而是使用两个[向量](https://en.wikipedia.org/wiki/Sequence_container_%28C%2B%2B%29#Vector)。向量是 [C++ 标准库](https://en.wikipedia.org/wiki/C%2B%2B_Standard_Library)中对 C 数组的扩展，它允许动态管理内存而无需显式调用 `malloc()`：



```
std::vector<double> x;
std::vector<double> y;

// Adding an element to x and y:
x.emplace_back(value);
y.emplace_back(value);
```

#### 拟合数据


要在 C++ 中拟合，你不必遍历列表，因为向量可以保证具有连续的内存。你可以将向量缓冲区的指针直接传递给拟合函数：



```
gsl_fit_linear(x.data(), 1, y.data(), 1, entries_number,
               &intercept, &slope,
               &cov00, &cov01, &cov11, &chi_squared);
const double r_value = gsl_stats_correlation(x.data(), 1, y.data(), 1, entries_number);

std::cout << "Slope: " << slope << std::endl;
std::cout << "Intercept: " << intercept << std::endl;
std::cout << "Correlation coefficient: " << r_value << std::endl;
```

#### 绘图


使用与以前相同的方法进行绘图。 写入文件：



```
const double step_x = ((max_x + 1) - (min_x - 1)) / N;

for (unsigned int i = 0; i < N; i += 1) {
    const double current_x = (min_x - 1) + step_x * i;
    const double current_y = intercept + slope * current_x;

    output_file << current_x << "\t" << current_y << std::endl;
}

output_file.close();
```

然后使用 Gnuplot 进行绘图。


#### 结果


在运行程序之前，必须使用类似的命令对其进行编译：



```
clang++ -std=c++11 -I/usr/include/ fitting_Cpp11.cpp -L/usr/lib/ -L/usr/lib64/ -lgsl -lgslcblas -o fitting_Cpp11
```

命令行上的结果输出为：



```
#### Anscombe's first set with C++11 ####
Slope: 0.500091
Intercept: 3.00009
Correlation coefficient: 0.816421
```

这就是用 Gnuplot 生成的结果图像：


![Plot and fit of the dataset obtained with C++11](/data/attachment/album/202003/02/101946ayg7m27mxv504bns.png "Plot and fit of the dataset obtained with C++11")


### 结论


本文提供了用 C99 和 C++11 编写的数据拟合和绘图任务的示例。由于 C++ 在很大程度上与 C 兼容，因此本文利用了它们的相似性来编写了第二个示例。在某些方面，C++ 更易于使用，因为它部分减轻了显式管理内存的负担。但是其语法更加复杂，因为它引入了为 OOP 编写类的可能性。但是，仍然可以用 C 使用 OOP 方法编写软件。由于 OOP 是一种编程风格，因此可以在任何语言中使用。在 C 中有一些很好的 OOP 示例，例如 [GObject](https://en.wikipedia.org/wiki/GObject) 和 [Jansson](http://www.digip.org/jansson/)库。


对于数字运算，我更喜欢在 C99 中进行，因为它的语法更简单并且得到了广泛的支持。直到最近，C++11 还没有得到广泛的支持，我倾向于避免使用先前版本中的粗糙不足之处。对于更复杂的软件，C++ 可能是一个不错的选择。


你是否也将 C 或 C++ 用于数据科学？在评论中分享你的经验。




---


via: <https://opensource.com/article/20/2/c-data-science>


作者：[Cristiano L. Fontana](https://opensource.com/users/cristianofontana) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
While languages like [Python](https://opensource.com/article/18/9/top-3-python-libraries-data-science) and [R](https://opensource.com/article/19/5/learn-python-r-data-science) are increasingly popular for data science, C and C++ can be a strong choice for efficient and effective data science. In this article, we will use [C99](https://en.wikipedia.org/wiki/C99) and [C++11](https://en.wikipedia.org/wiki/C%2B%2B11) to write a program that uses the [Anscombe’s quartet](https://en.wikipedia.org/wiki/Anscombe%27s_quartet) dataset, which I'll explain about next.

I wrote about my motivation for continually learning languages in an article covering [Python and GNU Octave](https://opensource.com/article/20/2/python-gnu-octave-data-science), which is worth reviewing. All of the programs are meant to be run on the [command line](https://en.wikipedia.org/wiki/Command-line_interface), not with a [graphical user interface](https://en.wikipedia.org/wiki/Graphical_user_interface) (GUI). The full examples are available in the [polyglot_fit repository](https://gitlab.com/cristiano.fontana/polyglot_fit).

## The programming task

The program you will write in this series:

- Reads data from a
[CSV file](https://en.wikipedia.org/wiki/Comma-separated_values) - Interpolates the data with a straight line (i.e.,
*f(x)=m ⋅ x + q*) - Plots the result to an image file

This is a common situation that many data scientists have encountered. The example data is the first set of [Anscombe's quartet](https://en.wikipedia.org/wiki/Anscombe%27s_quartet), shown in the table below. This is a set of artificially constructed data that gives the same results when fitted with a straight line, but their plots are very different. The data file is a text file with tabs as column separators and a few lines as a header. This task will use only the first set (i.e., the first two columns).

I | II | III | IV | ||||
---|---|---|---|---|---|---|---|

x | y | x | y | x | y | x | y |

10.0 | 8.04 | 10.0 | 9.14 | 10.0 | 7.46 | 8.0 | 6.58 |

8.0 | 6.95 | 8.0 | 8.14 | 8.0 | 6.77 | 8.0 | 5.76 |

13.0 | 7.58 | 13.0 | 8.74 | 13.0 | 12.74 | 8.0 | 7.71 |

9.0 | 8.81 | 9.0 | 8.77 | 9.0 | 7.11 | 8.0 | 8.84 |

11.0 | 8.33 | 11.0 | 9.26 | 11.0 | 7.81 | 8.0 | 8.47 |

14.0 | 9.96 | 14.0 | 8.10 | 14.0 | 8.84 | 8.0 | 7.04 |

6.0 | 7.24 | 6.0 | 6.13 | 6.0 | 6.08 | 8.0 | 5.25 |

4.0 | 4.26 | 4.0 | 3.10 | 4.0 | 5.39 | 19.0 | 12.50 |

12.0 | 10.84 | 12.0 | 9.13 | 12.0 | 8.15 | 8.0 | 5.56 |

7.0 | 4.82 | 7.0 | 7.26 | 7.0 | 6.42 | 8.0 | 7.91 |

5.0 | 5.68 | 5.0 | 4.74 | 5.0 | 5.73 | 8.0 | 6.89 |


## The C way

[C](https://en.wikipedia.org/wiki/C_%28programming_language%29) is a general-purpose programming language that is among the most popular languages in use today (according to data from the [TIOBE Index](https://www.tiobe.com/tiobe-index/), [RedMonk Programming Language Rankings](https://redmonk.com/sogrady/2019/07/18/language-rankings-6-19/), [Popularity of Programming Language Index](http://pypl.github.io/PYPL.html), and [State of the Octoverse of GitHub](https://octoverse.github.com/)). It is a quite old language (circa 1973), and many successful programs were written in it (e.g., the Linux kernel and Git to name just two). It is also one of the closest languages to the inner workings of the computer, as it is used to manipulate memory directly. It is a [compiled language](https://en.wikipedia.org/wiki/Compiled_language); therefore, the source code has to be translated by a [compiler](https://en.wikipedia.org/wiki/Compiler) into [machine code](https://en.wikipedia.org/wiki/Machine_code). Its [standard library](https://en.wikipedia.org/wiki/C_standard_library) is small and light on features, so other libraries have been developed to provide missing functionalities.

It is the language I use the most for [number crunching](https://en.wiktionary.org/wiki/number-crunching), mostly because of its performance. I find it rather tedious to use, as it needs a lot of [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code), but it is well supported in various environments. The C99 standard is a recent revision that adds some nifty features and is well supported by compilers.

I will cover the necessary background of C and C++ programming along the way so both beginners and advanced users can follow along.

### Installation

To develop with C99, you need a compiler. I normally use [Clang](https://clang.llvm.org/), but [GCC](https://gcc.gnu.org/) is another valid open source compiler. For linear fitting, I chose to use the [GNU Scientific Library](https://www.gnu.org/software/gsl/). For plotting, I could not find any sensible library, and therefore this program relies on an external program: [Gnuplot](http://www.gnuplot.info/). The example also uses a dynamic data structure to store data, which is defined in the [Berkeley Software Distribution](https://en.wikipedia.org/wiki/Berkeley_Software_Distribution) (BSD).

Installing in [Fedora](https://getfedora.org/) is as easy as running:

`sudo dnf install clang gnuplot gsl gsl-devel`


### Commenting code

In C99, [comments](https://en.wikipedia.org/wiki/Comment_(computer_programming)) are formatted by putting **//** at the beginning of the line, and the rest of the line will be discarded by the interpreter. Alternatively, anything between **/*** and ***/** is discarded, as well.

```
// This is a comment ignored by the interpreter.
/* Also this is ignored */
```


### Necessary libraries

Libraries are composed of two parts:

- A
[header file](https://en.wikipedia.org/wiki/Include_directive)that contains a description of the functions - A source file that contains the functions' definitions

Header files are included in the source, while the libraries' sources are [linked](https://en.wikipedia.org/wiki/Linker_%28computing%29) against the executable. Therefore, the header files needed for this example are:

```
// Input/Output utilities
#include <stdio.h>
// The standard library
#include <stdlib.h>
// String manipulation utilities
#include <string.h>
// BSD queue
#include <sys/queue.h>
// GSL scientific utilities
#include <gsl/gsl_fit.h>
#include <gsl/gsl_statistics_double.h>
```


### Main function

In C, the program must be inside a special function called [main()](https://en.wikipedia.org/wiki/Entry_point#C_and_C++):

```
int main(void) {
...
}
```


This differs from Python, as covered in the last tutorial, which will run whatever code it finds in the source files.

### Defining variables

In C, variables have to be declared before they are used, and they have to be associated with a type. Whenever you want to use a variable, you have to decide what kind of data to store in it. You can also specify if you intend to use a variable as a constant value, which is not necessary, but the compiler can benefit from this information. From the [fitting_C99.c program](https://gitlab.com/cristiano.fontana/polyglot_fit/-/blob/master/fitting_C99.c) in the repository:

```
const char *input_file_name = "anscombe.csv";
const char *delimiter = "\t";
const unsigned int skip_header = 3;
const unsigned int column_x = 0;
const unsigned int column_y = 1;
const char *output_file_name = "fit_C99.csv";
const unsigned int N = 100;
```


Arrays in C are not dynamic, in the sense that their length has to be decided in advance (i.e., before compilation):

`int data_array[1024];`


Since you normally do not know how many data points are in a file, use a [singly linked list](https://en.wikipedia.org/wiki/Linked_list#Singly_linked_list). This is a dynamic data structure that can grow indefinitely. Luckily, the BSD [provides linked lists](http://man7.org/linux/man-pages/man3/queue.3.html). Here is an example definition:

```
struct data_point {
double x;
double y;
SLIST_ENTRY(data_point) entries;
};
SLIST_HEAD(data_list, data_point) head = SLIST_HEAD_INITIALIZER(head);
SLIST_INIT(&head);
```


This example defines a **data_point** list comprised of structured values that contain both an **x** value and a **y** value. The syntax is rather complicated but intuitive, and describing it in detail would be too wordy.

### Printing output

To print on the terminal, you can use the [ printf()](https://en.wikipedia.org/wiki/Printf_format_string) function, which works like Octave's

**printf()**function (described in the first article):

`printf("#### Anscombe's first set with C99 ####\n");`


The **printf()** function does not automatically add a newline at the end of the printed string, so you have to add it. The first argument is a string that can contain format information for the other arguments that can be passed to the function, such as:

`printf("Slope: %f\n", slope);`


### Reading data

Now comes the hard part… There are some libraries for CSV file parsing in C, but none seemed stable or popular enough to be in the Fedora packages repository. Instead of adding a dependency for this tutorial, I decided to write this part on my own. Again, going into details would be too wordy, so I will only explain the general idea. Some lines in the source will be ignored for the sake of brevity, but you can find the complete example in the repository.

First, open the input file:

`FILE* input_file = fopen(input_file_name, "r");`


Then read the file line-by-line until there is an error or the file ends:

```
while (!ferror(input_file) && !feof(input_file)) {
size_t buffer_size = 0;
char *buffer = NULL;
getline(&buffer, &buffer_size, input_file);
...
}
```


The [ getline()](http://man7.org/linux/man-pages/man3/getline.3.html) function is a nice recent addition from the

[POSIX.1-2008 standard](https://en.wikipedia.org/wiki/POSIX). It can read a whole line in a file and take care of allocating the necessary memory. Each line is then split into

[tokens](https://en.wikipedia.org/wiki/Lexical_analysis#Token)with the

[function. Looping over the token, select the columns that you want:](http://man7.org/linux/man-pages/man3/strtok.3.html)

**strtok()**```
char *token = strtok(buffer, delimiter);
while (token != NULL)
{
double value;
sscanf(token, "%lf", &value);
if (column == column_x) {
x = value;
} else if (column == column_y) {
y = value;
}
column += 1;
token = strtok(NULL, delimiter);
}
```


Finally, when the **x** and **y** values are selected, insert the new data point in the linked list:

```
struct data_point *datum = malloc(sizeof(struct data_point));
datum->x = x;
datum->y = y;
SLIST_INSERT_HEAD(&head, datum, entries);
```


The [ malloc()](http://man7.org/linux/man-pages/man3/malloc.3.html) function dynamically allocates (reserves) some persistent memory for the new data point.

### Fitting data

The GSL linear fitting function [ gsl_fit_linear()](https://www.gnu.org/software/gsl/doc/html/lls.html) expects simple arrays for its input. Therefore, since you won't know in advance the size of the arrays you create, you must manually allocate their memory:

```
const size_t entries_number = row - skip_header - 1;
double *x = malloc(sizeof(double) * entries_number);
double *y = malloc(sizeof(double) * entries_number);
```


Then, loop over the linked list to save the relevant data to the arrays:

```
SLIST_FOREACH(datum, &head, entries) {
const double current_x = datum->x;
const double current_y = datum->y;
x[i] = current_x;
y[i] = current_y;
i += 1;
}
```


Now that you are done with the linked list, clean it up. *Always* release the memory that has been manually allocated to prevent a [memory leak](https://en.wikipedia.org/wiki/Memory_leak). Memory leaks are bad, bad, bad. Every time memory is not released, a garden gnome loses its head:

```
while (!SLIST_EMPTY(&head)) {
struct data_point *datum = SLIST_FIRST(&head);
SLIST_REMOVE_HEAD(&head, entries);
free(datum);
}
```


Finally, finally(!), you can fit your data:

```
gsl_fit_linear(x, 1, y, 1, entries_number,
&intercept, &slope,
&cov00, &cov01, &cov11, &chi_squared);
const double r_value = gsl_stats_correlation(x, 1, y, 1, entries_number);
printf("Slope: %f\n", slope);
printf("Intercept: %f\n", intercept);
printf("Correlation coefficient: %f\n", r_value);
```


### Plotting

You must use an external program for the plotting. Therefore, save the fitting function to an external file:

```
const double step_x = ((max_x + 1) - (min_x - 1)) / N;
for (unsigned int i = 0; i < N; i += 1) {
const double current_x = (min_x - 1) + step_x * i;
const double current_y = intercept + slope * current_x;
fprintf(output_file, "%f\t%f\n", current_x, current_y);
}
```


The Gnuplot command for plotting both files is:

`plot 'fit_C99.csv' using 1:2 with lines title 'Fit', 'anscombe.csv' using 1:2 with points pointtype 7 title 'Data'`


### Results

Before running the program, you must compile it:

`clang -std=c99 -I/usr/include/ fitting_C99.c -L/usr/lib/ -L/usr/lib64/ -lgsl -lgslcblas -o fitting_C99`


This command tells the compiler to use the C99 standard, read the **fitting_C99.c** file, load the libraries **gsl** and **gslcblas**, and save the result to **fitting_C99**. The resulting output on the command line is:

```
#### Anscombe's first set with C99 ####
Slope: 0.500091
Intercept: 3.000091
Correlation coefficient: 0.816421
```


Here is the resulting image generated with Gnuplot.

![Plot and fit of the dataset obtained with C99 Plot and fit of the dataset obtained with C99](https://opensource.com/sites/default/files/uploads/fit_c99.png)

## The C++11 way

[C++](https://en.wikipedia.org/wiki/C%2B%2B) is a general-purpose programming language that is also among the most popular languages in use today. It was created as a [successor of C](http://www.cplusplus.com/info/history/) (in 1983) with an emphasis on [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming) (OOP). C++ is commonly regarded as a superset of C, so a C program should be able to be compiled with a C++ compiler. This is not exactly true, as there are some corner cases where they behave differently. In my experience, C++ needs less boilerplate than C, but the syntax is more difficult if you want to develop objects. The C++11 standard is a recent revision that adds some nifty features and is more or less supported by compilers.

Since C++ is largely compatible with C, I will just highlight the differences between the two. If I do not cover a section in this part, it means that it is the same as in C.

### Installation

The dependencies for the C++ example are the same as the C example. On Fedora, run:

`sudo dnf install clang gnuplot gsl gsl-devel`


### Necessary libraries

Libraries work in the same way as in C, but the **include** directives are slightly different:

```
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
extern "C" {
#include <gsl/gsl_fit.h>
#include <gsl/gsl_statistics_double.h>
}
```


Since the GSL libraries are written in C, you must inform the compiler about this peculiarity.

### Defining variables

C++ supports more data types (classes) than C, such as a **string** type that has many more features than its C counterpart. Update the definition of the variables accordingly:

`const std::string input_file_name("anscombe.csv");`


For structured objects like strings, you can define the variable without using the **=** sign.

### Printing output

You can use the **printf()** function, but the **cout** object is more idiomatic. Use the operator **<<** to indicate the string (or objects) that you want to print with **cout**:

```
std::cout << "#### Anscombe's first set with C++11 ####" << std::endl;
...
std::cout << "Slope: " << slope << std::endl;
std::cout << "Intercept: " << intercept << std::endl;
std::cout << "Correlation coefficient: " << r_value << std::endl;
```


### Reading data

The scheme is the same as before. The file is opened and read line-by-line, but with a different syntax:

```
std::ifstream input_file(input_file_name);
while (input_file.good()) {
std::string line;
getline(input_file, line);
...
}
```


The line tokens are extracted with the same function as in the C99 example. Instead of using standard C arrays, use two [vectors](https://en.wikipedia.org/wiki/Sequence_container_%28C%2B%2B%29#Vector). Vectors are an extension of C arrays in the [C++ standard library](https://en.wikipedia.org/wiki/C%2B%2B_Standard_Library) that allows dynamic management of memory without explicitly calling **malloc()**:

```
std::vector<double> x;
std::vector<double> y;
// Adding an element to x and y:
x.emplace_back(value);
y.emplace_back(value);
```


### Fitting data

For fitting in C++, you do not have to loop over the list, as vectors are guaranteed to have contiguous memory. You can directly pass to the fitting function the pointers to the vectors buffers:

```
gsl_fit_linear(x.data(), 1, y.data(), 1, entries_number,
&intercept, &slope,
&cov00, &cov01, &cov11, &chi_squared);
const double r_value = gsl_stats_correlation(x.data(), 1, y.data(), 1, entries_number);
std::cout << "Slope: " << slope << std::endl;
std::cout << "Intercept: " << intercept << std::endl;
std::cout << "Correlation coefficient: " << r_value << std::endl;
```


### Plotting

Plotting is done with the same approach as before. Write to a file:

```
const double step_x = ((max_x + 1) - (min_x - 1)) / N;
for (unsigned int i = 0; i < N; i += 1) {
const double current_x = (min_x - 1) + step_x * i;
const double current_y = intercept + slope * current_x;
output_file << current_x << "\t" << current_y << std::endl;
}
output_file.close();
```


And then use Gnuplot for the plotting.

### Results

Before running the program, it must be compiled with a similar command:

`clang++ -std=c++11 -I/usr/include/ fitting_Cpp11.cpp -L/usr/lib/ -L/usr/lib64/ -lgsl -lgslcblas -o fitting_Cpp11`


The resulting output on the command line is:

```
#### Anscombe's first set with C++11 ####
Slope: 0.500091
Intercept: 3.00009
Correlation coefficient: 0.816421
```


And this is the resulting image generated with Gnuplot.

![Plot and fit of the dataset obtained with C++11 Plot and fit of the dataset obtained with C++11](https://opensource.com/sites/default/files/uploads/fit_cpp11.png)

## Conclusion


This article provides examples for a data fitting and plotting task in C99 and C++11. Since C++ is largely compatible with C, this article exploited their similarities for writing the second example. In some aspects, C++ is easier to use because it partially relieves the burden of explicitly managing memory. But the syntax is more complex because it introduces the possibility of writing classes for OOP. However, it is still possible to write software in C with the OOP approach. Since OOP is a style of programming, it can be used in any language. There are some great examples of OOP in C, such as the [GObject](https://en.wikipedia.org/wiki/GObject) and [Jansson](http://www.digip.org/jansson/) libraries.

For number crunching, I prefer working in C99 due to its simpler syntax and widespread support. Until recently, C++11 was not as widely supported, and I tended to avoid the rough edges in the previous versions. For more complex software, C++ could be a good choice.

Do you use C or C++ for data science as well? Share your experiences in the comments.

## 14 Comments