使用 Python 和 GNU Octave 绘制数据

了解如何使用 Python 和 GNU Octave 完成常见的数据科学任务。
125 位读者喜欢这篇文章。
Analytics: Charts and Graphs

JuralMin, CC0。由 Jason Baker 修改。

数据科学是一个跨越编程语言的知识领域。有些语言在解决该领域的问题方面广为人知,而另一些则鲜为人知。本文将帮助您熟悉使用一些流行的语言进行数据科学。

选择 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 进行开发,您需要解释器和一些库。最低要求是

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 规范使用自定义标签<0xC2><0xA0>重命名它们

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

您也可以决定仅导入一个子模块(如第二行和第三行所示)。语法有两个(或多或少)等效的选项:import module.submodulefrom 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 中字符串类格式化功能结合使用。字符串具有 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 库仅绘制数据点;因此,您应该定义要绘制的点。xy 数组已经定义,因此您可以直接绘制它们,但您还需要代表直线的数据点。

fit_x = np.linspace(x.min() - 1, x.max() + 1, 100)

linspace() 函数方便地生成一组介于两个值之间的等间隔值。纵坐标可以通过利用强大的 NumPy 数组轻松计算,NumPy 数组可以在公式中像普通数值变量一样使用

fit_y = slope * fit_x + intercept

该公式逐元素应用于数组;因此,结果在初始数组中具有相同数量的条目。

要创建绘图,首先定义一个 figure 对象,它将包含所有图形

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 生成的图像。

Plot and fit of the dataset obtained with Python

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 示例中相同的特殊注释行,以直接在命令行上执行脚本。

必要的库

本示例中使用的所有内容都包含在基本软件包中,因此您无需加载任何新库。如果您需要库,语法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);

结果是一个 矩阵 对象,它是 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 生成的结果图像。

Plot and fit of the dataset obtained with Octave

下一步

Python 和 GNU Octave 都可以绘制相同的信息,尽管它们实现的方式有所不同。如果您正在寻找其他语言来完成类似的任务,我强烈建议您查看 Rosetta Code。这是一个极好的资源,可以了解如何使用多种语言解决相同的问题。

您喜欢用哪种语言绘制数据?在评论中分享您的想法。

接下来阅读
User profile image.
Cristiano L. Fontana 曾是意大利帕多瓦大学“伽利略·伽利莱”物理与天文系的 исследователем,之后开始了新的体验。

2 条评论

好文章!我将把它作为我学习 Python 的起点。谢谢!

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.