数据科学是一个跨越编程语言的知识领域。有些语言因解决该领域的问题而闻名,而另一些语言则鲜为人知。本文将帮助您熟悉使用一些流行的语言进行数据科学。
选择 Python 和 GNU Octave 进行数据科学
我时不时地尝试学习一种新的编程语言。为什么?这主要是对旧方法感到厌倦和对新方法感到好奇的结合。当我开始编程时,我唯一知道的语言是 C。那些年生活艰苦而危险,因为我必须手动分配内存、管理指针并记住释放内存。
后来一位朋友建议我尝试 Python,生活变得轻松多了。程序运行速度变得慢得多,但我不必再痛苦地编写分析软件。然而,我很快意识到,每种语言都比其他语言更适合某些应用。后来我学习了一些其他语言,每种语言都带来了一些新的启示。发现新的编程风格让我可以将一些解决方案反向移植到其他语言,一切都变得更加有趣。
为了感受一种新的编程语言(及其文档),我总是从编写一些示例程序开始,这些程序执行我熟悉的任务。为此,我将解释如何在 Python 和 GNU Octave 中编写一个程序,用于您可以归类为数据科学的特定任务。如果您已经熟悉其中一种语言,请从该语言开始,然后浏览其他语言以查找相似之处和不同之处。本文并非旨在对这些语言进行详尽的比较,只是一个简单的展示。
所有程序都旨在在命令行上运行,而不是使用图形用户界面 (GUI)。完整的示例可在 polyglot_fit 存储库中找到。
编程任务
您将在此系列中编写的程序
- 从 CSV 文件读取数据
- 用直线插值数据(即,f(x)=m ⋅ x + q)
- 将结果绘制到图像文件
这是许多数据科学家都遇到过的常见情况。示例数据是第一组安斯库姆四重奏,如下表所示。这是一组人工构建的数据,当用直线拟合时会给出相同的结果,但它们的图非常不同。数据文件是一个文本文件,使用制表符作为列分隔符,并有几行作为标题。此任务将仅使用第一组(即前两列)。
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 |
Python 的方式
Python 是一种通用编程语言,是当今最流行的语言之一(TIOBE 指数、RedMonk 编程语言排名、编程语言流行度指数、GitHub Octoverse 状态和其他来源的调查结果证明了这一点)。它是一种解释型语言;因此,源代码由执行指令的程序读取和评估。它有一个全面的标准库,并且通常非常易于使用(我对最后一种说法没有参考;这只是我个人的拙见)。
安装
要使用 Python 进行开发,您需要解释器和一些库。最低要求是
- NumPy,用于方便的数组和矩阵操作
- SciPy,用于科学计算
- Matplotlib,用于绘图
在 Fedora 中安装它们很容易
sudo dnf install python3 python3-numpy python3-scipy python3-matplotlib
注释代码
在 Python 中,注释是通过在行首放置一个 # 来实现的,该行其余部分将被解释器丢弃
# This is a comment ignored by the interpreter.
fitting_python.py 示例使用注释在源代码中插入许可信息,第一行是一个 特殊注释,使脚本能够在命令行上执行
#! /usr/bin/env python3
此行通知命令行解释器,脚本需要由程序 python3 执行。
必需的库
库和模块可以在 Python 中作为对象导入(如示例中的第一行),其中包含库的所有函数和成员。有一个方便的选项可以使用 as 规范将它们重命名为自定义标签
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
您也可以决定仅导入一个子模块(如第二行和第三行)。语法有两个(或多或少)等效的选项:import module.submodule 和 from module import submodule。
定义变量
Python 的变量在第一次为其赋值时声明
input_file_name = "anscombe.csv"
delimiter = "\t"
skip_header = 3
column_x = 0
column_y = 1
变量类型由分配给变量的值推断。除非常量值在模块中声明且只能读取,否则不存在具有常量值的变量。按照惯例,不应修改的变量应以大写字母命名。
打印输出
通过命令行运行程序意味着输出仅打印在终端上。Python 具有 print() 函数,默认情况下,该函数打印其参数并在输出末尾添加换行符
print("#### Anscombe's first set with Python ####")
可以将 print() 函数与 Python string 类的 格式化功能结合使用。字符串具有 format 方法,可用于向字符串本身添加一些格式化的文本。例如,可以添加格式化的浮点数,例如
print("Slope: {:f}".format(slope))
读取数据
使用 NumPy 和函数 genfromtxt() 读取 CSV 文件非常容易,该函数生成一个 NumPy 数组
data = np.genfromtxt(input_file_name, delimiter = delimiter, skip_header = skip_header)
在 Python 中,函数可以具有可变数量的参数,您可以指定所需的参数来传递子集。数组是非常强大的类矩阵对象,可以轻松地切片成更小的数组
x = data[:, column_x]
y = data[:, column_y]
冒号选择整个范围,它们也可以用于选择子范围。例如,要选择数组的前两行,您可以使用
first_two_rows = data[0:1, :]
拟合数据
SciPy 提供了方便的数据拟合函数,例如 linregress() 函数。此函数提供了一些与拟合相关的重要值,例如斜率、截距和两个数据集的相关系数
slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
print("Slope: {:f}".format(slope))
print("Intercept: {:f}".format(intercept))
print("Correlation coefficient: {:f}".format(r_value))
由于 linregress() 提供了多条信息,因此可以将结果同时保存到多个变量中。
绘图
Matplotlib 库仅绘制数据点;因此,您应该定义要绘制的点。x 和 y 数组已经定义,因此您可以直接绘制它们,但您还需要表示直线的数据点。
fit_x = np.linspace(x.min() - 1, x.max() + 1, 100)
linspace() 函数方便地生成两个值之间的一组等间距值。纵坐标可以通过利用强大的 NumPy 数组轻松计算出来,NumPy 数组可以在公式中像普通数值变量一样使用
fit_y = slope * fit_x + intercept
该公式逐元素应用于数组;因此,结果与初始数组中的条目数相同。
要创建绘图,首先定义一个 图形对象,它将包含所有图形
fig_width = 7 #inch
fig_height = fig_width / 16 * 9 #inch
fig_dpi = 100
fig = plt.figure(figsize = (fig_width, fig_height), dpi = fig_dpi)
可以在一个图形上绘制多个绘图;在 Matplotlib 中,绘图称为轴。此示例定义了一个轴对象来绘制数据点
ax = fig.add_subplot(111)
ax.plot(fit_x, fit_y, label = "Fit", linestyle = '-')
ax.plot(x, y, label = "Data", marker = '.', linestyle = '')
ax.legend()
ax.set_xlim(min(x) - 1, max(x) + 1)
ax.set_ylim(min(y) - 1, max(y) + 1)
ax.set_xlabel('x')
ax.set_ylabel('y')
使用以下命令将图形保存到 PNG 图像文件
fig.savefig('fit_python.png')
如果要显示(而不是保存)绘图,请调用
plt.show()
此示例引用了绘图部分中使用的所有对象:它定义了对象 fig 和对象 ax。这种技术性不是必需的,因为 plt 对象可以直接用于绘制数据集。Matplotlib 教程显示了一个如下所示的界面
plt.plot(fit_x, fit_y)
坦率地说,我不喜欢这种方法,因为它隐藏了各种对象之间发生的非平凡的交互。不幸的是,有时 官方示例 有点令人困惑,因为它们倾向于使用不同的方法。在这个简单的示例中,引用图形对象不是必需的,但在更复杂的示例中(例如在 GUI 中嵌入绘图时)变得很重要。
结果
命令行上的输出是
#### Anscombe's first set with Python ####
Slope: 0.500091
Intercept: 3.000091
Correlation coefficient: 0.816421
这是 Matplotlib 生成的图像。

GNU Octave 的方式
GNU Octave 语言主要用于数值计算。它为操作向量和矩阵提供了简单的语法,并具有一些强大的绘图功能。它是一种像 Python 一样的解释型语言。由于 Octave 的语法与 MATLAB 基本兼容,因此通常被描述为 MATLAB 的免费替代品。Octave 未列入最流行的编程语言之列,但 MATLAB 已列入,因此从某种意义上说,Octave 相当流行。MATLAB 早于 NumPy,我感觉它受到了前者的启发。当您浏览示例时,您会看到相似之处。
安装
fitting_octave.m 示例仅需要基本的 Octave 包,这使得在 Fedora 中安装非常简单
sudo dnf install octave
注释代码
在 Octave 中,您可以使用百分号 (%) 向代码添加注释,如果不需要 MATLAB 兼容性,也可以使用 #。使用 # 的选项允许您编写与 Python 示例中相同的特殊注释行,以直接在命令行上执行脚本。
必要的库
此示例中使用的所有内容都包含在基本包中,因此您无需加载任何新库。如果您需要库,语法为 syntax is pkg load module。此命令将模块的函数添加到可用函数列表中。在这方面,Python 具有更大的灵活性。
定义变量
变量的定义语法与 Python 非常相似
input_file_name = "anscombe.csv";
delimiter = "\t";
skip_header = 3;
column_x = 1;
column_y = 2;
请注意,行尾有一个分号;这不是必需的,但它会抑制行结果的输出。如果没有分号,解释器将打印表达式的结果
octave:1> input_file_name = "anscombe.csv"
input_file_name = anscombe.csv
octave:2> sqrt(2)
ans = 1.4142
打印输出
强大的函数 printf() 用于在终端上打印。与 Python 不同,printf() 函数不会在打印的字符串末尾自动添加换行符,因此您必须手动添加。第一个参数是一个字符串,可以包含要传递给函数的其他参数的格式信息,例如
printf("Slope: %f\n", slope);
在 Python 中,格式化内置在字符串本身中,但在 Octave 中,它特定于 printf() 函数。
读取数据
dlmread() 函数可以读取像 CSV 文件一样结构的文本文件
data = dlmread(input_file_name, delimiter, skip_header, 0);
结果是一个 matrix 对象,它是 Octave 中的基本数据类型之一。矩阵可以使用类似于 Python 的语法进行切片
x = data(:, column_x);
y = data(:, column_y);
根本的区别在于索引从 1 而不是 0 开始。因此,在示例中,
x 列是第一列。
拟合数据
要用直线拟合数据,可以使用 polyfit() 函数。它用多项式拟合输入数据,因此您只需要使用一阶多项式
p = polyfit(x, y, 1);
slope = p(1);
intercept = p(2);
结果是一个包含多项式系数的矩阵;因此,它选择前两个索引。要确定相关系数,请使用 corr() 函数
r_value = corr(x, y);
最后,使用 printf() 函数打印结果
printf("Slope: %f\n", slope);
printf("Intercept: %f\n", intercept);
printf("Correlation coefficient: %f\n", r_value);
绘图
与 Matplotlib 示例中一样,您首先需要创建一个表示拟合线的数据集
fit_x = linspace(min(x) - 1, max(x) + 1, 100);
fit_y = slope * fit_x + intercept;
与 NumPy 的相似之处在这里也很明显,因为它使用了 linspace() 函数,该函数的行为与 Python 的等效版本完全相同。
同样,与 Matplotlib 一样,首先创建一个 figure 对象,然后创建一个 axes 对象来保存绘图
fig_width = 7; %inch
fig_height = fig_width / 16 * 9; %inch
fig_dpi = 100;
fig = figure("units", "inches",
"position", [1, 1, fig_width, fig_height]);
ax = axes("parent", fig);
set(ax, "fontsize", 14);
set(ax, "linewidth", 2);
要设置轴对象的属性,请使用 set() 函数。但是,该界面相当令人困惑,因为该函数需要逗号分隔的属性和值对列表。这些对只是一连串表示属性名称的字符串和表示该属性值的第二个对象。还有其他函数可以设置各种属性
xlim(ax, [min(x) - 1, max(x) + 1]);
ylim(ax, [min(y) - 1, max(y) + 1]);
xlabel(ax, 'x');
ylabel(ax, 'y');
绘图是通过 plot() 函数实现的。默认行为是每次调用都会重置轴,因此您需要使用函数 hold()。
hold(ax, "on");
plot(ax, fit_x, fit_y,
"marker", "none",
"linestyle", "-",
"linewidth", 2);
plot(ax, x, y,
"marker", ".",
"markersize", 20,
"linestyle", "none");
hold(ax, "off");
此外,可以在 plot() 函数中添加属性和值对。图例必须单独创建,并且标签应手动声明
lg = legend(ax, "Fit", "Data");
set(lg, "location", "northwest");
最后,将输出保存到 PNG 图像
image_size = sprintf("-S%f,%f", fig_width * fig_dpi, fig_height * fig_dpi);
image_resolution = sprintf("-r%f,%f", fig_dpi);
print(fig, 'fit_octave.png',
'-dpng',
image_size,
image_resolution);
令人困惑的是,在这种情况下,选项作为单个字符串传递,其中包含属性名称和值。由于在 Octave 中,字符串没有 Python 的格式化功能,因此您必须使用 sprintf() 函数。它的行为与 printf() 函数完全相同,但它的结果不会打印,而是作为字符串返回。
在此示例中,与 Python 示例中一样,图形对象被引用以保持其交互的可见性。如果 Python 在这方面的文档有点令人困惑,那么 Octave 的文档 甚至更糟。我发现的大多数示例都不关心引用对象;相反,它们依赖于绘图命令作用于当前活动图形的事实。全局 根图形对象 跟踪现有图形和轴。
结果
命令行上的结果输出是
#### Anscombe's first set with Octave ####
Slope: 0.500091
Intercept: 3.000091
Correlation coefficient: 0.816421
这显示了使用 Octave 生成的图像。

下一步
Python 和 GNU Octave 都可以绘制相同的信息,尽管它们实现的方式有所不同。如果您正在寻找其他语言来完成类似的任务,我强烈建议您查看 Rosetta Code。这是一个极好的资源,可以了解如何在多种语言中解决相同的问题。
您喜欢用哪种语言绘制数据?请在评论中分享您的想法。
2 条评论