20 KiB
使用 Python 和 GNU Octave 绘制数据
了解如何使用 Python 和 GNU Octave 完成一项常见的数据科学任务。 !分析:图表和图形
数据科学是跨越编程语言的知识领域。有些人以解决这一领域的问题而闻名,而另一些人则鲜为人知。这篇文章将帮助你熟悉用一些流行语言做数据科学。
为数据科学选择 Python 和 GNU Octave
我经常尝试学习一种新的编程语言。为什么?这主要是对旧方式的厌倦和对新方式的好奇的结合。当我开始编程时,我唯一知道的语言是 C 语言。那些年的编程生涯既艰难又危险,因为我不得不手动分配内存,管理指针,并记得释放内存。
然后一个朋友建议我试试 Python,现在编程生活变得简单多了。虽然程序运行变得慢多了,但我不必通过编写分析软件来受苦了。然而,我很快就意识到每种语言都有比其他语言更适合自己应用场景。后来我学习了其他一些语言,每种语言都给我带来了一些新的启发。发现新的编程风格让我可以将一些解决方案移植到其他语言中,这样一切都变得有趣多了。
为了对一种新的编程语言(及其文档)有所了解,我总是从编写一些执行我熟悉的任务的示例程序开始。为此,我将解释如何用 Python 和 GNU Octave 编写一个程序来完成一个你可以归类为数据科学的特殊任务。如果你已经熟悉其中一种语言,从中开始,浏览其他语言,寻找相似之处和不同之处。这并不是对编程语言的详尽比较,只是一个小小的展示。
所有的程序都应该在命令行上运行,而不是用图形用户界面(GUI)。完整的例子可以在多语种知识库中找到。
编程任务
你将在本系列中编写的程序:
- 从CSV文件中读取数据
- 用直线插入数据(例如 f(x)=m ⋅ x + q)
- 将结果生成图像文件
这是许多数据科学家遇到的常见情况。示例数据是第一组Anscombe's quartet,如下表所示。这是一组人工构建的数据,当用直线拟合时会给出相同的结果,但是它们的曲线非常不同。数据文件是一个文本文件,以制表符作为列分隔,以几行作为标题。此任务将仅使用第一组(例如:前两列)。
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 index、RedMonk编程语言排名、编程语言流行指数、State of the Octoverse of GitHub和其他来源的调查结果可以看出)。这是一种解释的语言;因此,源代码由执行指令的程序读取和评估。它有一个全面的标准库并且总体上非常好用(我没有参考这最后一句话;这只是我的拙见)。
安装
要使用 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执行。
Required libraries
在 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 ####")`
在 Python 中,可以将print()函数与字符串类的格式化能力相结合。字符串具有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 数组可以轻松计算纵坐标,该数组可以像普通数值变量一样在公式中使用:
`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 image file中,有:
`fig.savefig('fit_python.png')`
如果要显示(而不是保存)绘图,请调用:
`plt.show()`
此示例引用了绘图部分中使用的所有对象:它定义了对象 fig 和对象 ax。这种技术细节是不必要的,因为 plt 对象可以直接用于绘制数据集。《Matplotlib 教程展示了这样一个界面:
`plt.plot(fit_x, fit_y)`
坦率地说,我不喜欢这种方法,因为它隐藏了各种对象之间发生的重要的的交互。不幸的是,有时官方的例子有点令人困惑,因为他们倾向于使用不同的方法。在这个简单的例子中,引用图形对象是不必要的,但是在更复杂的例子中(例如在图形用户界面中嵌入图形时),引用图形对象就变得很重要了。
结果
命令行输入:
#### 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 示例中编写相同的特殊注释行,以便直接在命令行上执行脚本。
必要的库
本例中使用的所有内容都包含在基本包中,因此你不需要加载任何新的库。如果你需要一个库,语法是 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 一样,首先创建一个图对象,然后创建一个轴对象来保存这些图:
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() 函数中添加属性和值对。legend必须单独创建,标签应手动声明:
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 代码。这是一个了不起的资源,可以看到如何用多种语言解决同样的问题。
你喜欢用什么语言绘制数据?在评论中分享你的想法。
via: https://opensource.com/article/20/2/python-gnu-octave-data-science
作者:Cristiano L. Fontana 选题:lujun9972 译者:heguangzhi 校对:校对者ID