使用 Python 和 GNU Octave 繪製數據
數據科學是跨越編程語言的知識領域。有些語言以解決這一領域的問題而聞名,而另一些則鮮為人知。這篇文章將幫助你熟悉用一些流行的語言完成數據科學的工作。
選擇 Python 和 GNU Octave 做數據科學工作
我經常嘗試學習一種新的編程語言。為什麼?這既有對舊方式的厭倦,也有對新方式的好奇。當我開始學習編程時,我唯一知道的語言是 C 語言。那些年的編程生涯既艱難又危險,因為我必須手動分配內存、管理指針、並記得釋放內存。
後來一個朋友建議我試試 Python,現在我的編程生活變得輕鬆多了。雖然程序運行變得慢多了,但我不必通過編寫分析軟體來受苦了。然而,我很快就意識到每種語言都有比其它語言更適合自己的應用場景。後來我學習了一些其它語言,每種語言都給我帶來了一些新的啟發。發現新的編程風格讓我可以將一些解決方案移植到其他語言中,這樣一切都變得有趣多了。
為了對一種新的編程語言(及其文檔)有所了解,我總是從編寫一些執行我熟悉的任務的示常式序開始。為此,我將解釋如何用 Python 和 GNU Octave 編寫一個程序來完成一個你可以歸類為數據科學的特殊任務。如果你已經熟悉其中一種語言,從它開始,然後通過其他語言尋找相似之處和不同之處。這篇文章並不是對編程語言的詳盡比較,只是一個小小的展示。
所有的程序都應該在命令行上運行,而不是用圖形用戶界面(GUI)。完整的例子可以在 polyglot_fit 存儲庫中找到。
編程任務
你將在本系列中編寫的程序:
- 從 CSV 文件中讀取數據
- 用直線插入數據(例如
f(x)=m ⋅ x + q
) - 將結果生成圖像文件
這是許多數據科學家遇到的常見情況。示例數據是 Anscombe 的四重奏的第一組,如下表所示。這是一組人工構建的數據,當用直線擬合時會給出相同的結果,但是它們的曲線非常不同。數據文件是一個文本文件,以製表符作為列分隔符,開頭幾行作為標題。此任務將僅使用第一組(即前兩列)。
Python 方式
Python 是一種通用編程語言,是當今最流行的語言之一(依據 TIOBE 指數、RedMonk 編程語言排名、編程語言流行指數、GitHub Octoverse 狀態和其他來源的調查結果)。它是一種解釋型語言;因此,源代碼由執行該指令的程序讀取和評估。它有一個全面的標準庫並且總體上非常好用(我對這最後一句話沒有證據;這只是我的拙見)。
安裝
要使用 Python 開發,你需要解釋器和一些庫。最低要求是:
- NumPy 用於簡化數組和矩陣的操作
- SciPy 用於數據科學
- Matplotlib 用於繪圖
在 Fedora 安裝它們是很容易的:
sudo dnf install python3 python3-numpy python3-scipy python3-matplotlib
代碼注釋
在 Python中,注釋是通過在行首添加一個 #
來實現的,該行的其餘部分將被解釋器丟棄:
# 這是被解釋器忽略的注釋。
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 ####")
在 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 圖形文件中,有:
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 產生的圖像:
![Plot and fit of the dataset obtained with Python](/data/attachment/album/202002/29/114814z7mefpy1np1ppy4n.png "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: %fn", 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: %fn", slope);
printf("Intercept: %fn", intercept);
printf("Correlation coefficient: %fn", 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 生成的結果圖像。
![Plot and fit of the dataset obtained with Octave](/data/attachment/album/202002/29/114815do6fh2xfac6w2r2g.png "Plot and fit of the dataset obtained with Octave")
接下來
Python 和 GNU Octave 都可以繪製出相同的信息,儘管它們的實現方式不同。如果你想探索其他語言來完成類似的任務,我強烈建議你看看 Rosetta Code。這是一個了不起的資源,可以看到如何用多種語言解決同樣的問題。
你喜歡用什麼語言繪製數據?在評論中分享你的想法。
via: https://opensource.com/article/20/2/python-gnu-octave-data-science
作者:Cristiano L. Fontana 選題:lujun9972 譯者:heguangzhi 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive