瞭解如何使用 Python 和 GNU Octave 完成一項常見的數據科學任務。-- Cristiano L. Fontana(作者)
數據科學是跨越編程語言的知識領域。有些語言以解決這一領域的問題而聞名,而另一些則鮮為人知。這篇文章將幫助你熟悉用一些流行的語言完成數據科學的工作。
選擇 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 安裝它們是很容易的:
<code>sudo dnf install python3 python3-numpy python3-scipy python3-matplotlib/<code>
代碼註釋
在 Python中, 註釋 是通過在行首添加一個 # 來實現的,該行的其餘部分將被解釋器丟棄:
<code># 這是被解釋器忽略的註釋。/<code>
fitting_python.py 示例使用註釋在源代碼中插入許可證信息,第一行是 特殊註釋 ,它允許該腳本在命令行上執行:
<code>#!/usr/bin/env python3/<code>
這一行通知命令行解釋器,該腳本需要由程序 python3 執行。
需要的庫
在 Python 中,庫和模塊可以作為一個對象導入(如示例中的第一行),其中包含庫的所有函數和成員。可以通過使用 as 方式用自定義標籤重命名它們:
<code>import numpy as npfrom scipy import statsimport matplotlib.pyplot as plt/<code>
你也可以決定只導入一個子模塊(如第二行和第三行)。語法有兩個(基本上)等效的方式:import module.submodule 和 from module import submodule。
定義變量
Python 的變量是在第一次賦值時被聲明的:
<code>input_file_name = "anscombe.csv"delimiter = "\\t"skip_header = 3column_x = 0column_y = 1/<code>
變量類型由分配給變量的值推斷。沒有具有常量值的變量,除非它們在模塊中聲明並且只能被讀取。習慣上,不應被修改的變量應該用大寫字母命名。
打印輸出
通過命令行運行程序意味著輸出只能打印在終端上。Python 有 print() 函數,默認情況下,該函數打印其參數,並在輸出的末尾添加一個換行符:
<code>print("#### Anscombe's first set with Python ####")/<code>
在 Python 中,可以將 print() 函數與 字符串類 的 格式化能力 相結合。字符串具有format 方法,可用於向字符串本身添加一些格式化文本。例如,可以添加格式化的浮點數,例如:
<code>print("Slope: {:f}".format(slope))/<code>
讀取數據
使用 NumPy 和函數 genfromtxt() 讀取 CSV 文件非常容易,該函數生成 NumPy 數組 :
<code>data = np.genfromtxt(input_file_name, delimiter = delimiter, skip_header = skip_header)/<code>
在 Python 中,一個函數可以有數量可變的參數,你可以通過指定所需的參數來傳遞一個參數的子集。數組是非常強大的矩陣狀對象,可以很容易地分割成更小的數組:
<code>x = data[:, column_x]y = data[:, column_y]/<code>
冒號選擇整個範圍,也可以用來選擇子範圍。例如,要選擇數組的前兩行,可以使用:
<code>first_two_rows = data[0:1, :]/<code>
擬合數據
SciPy 提供了方便的數據擬合功能,例如 linregress() 功能。該函數提供了一些與擬合相關的重要值,如斜率、截距和兩個數據集的相關係數:
<code>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))/<code>
因為 linregress() 提供了幾條信息,所以結果可以同時保存到幾個變量中。
繪圖
Matplotlib 庫僅僅繪製數據點,因此,你應該定義要繪製的點的座標。已經定義了 x 和 y 數組,所以你可以直接繪製它們,但是你還需要代表直線的數據點。
<code>fit_x = np.linspace(x.min() - 1, x.max() + 1, 100)/<code>
linspace() 函數可以方便地在兩個值之間生成一組等距值。利用強大的 NumPy 數組可以輕鬆計算縱座標,該數組可以像普通數值變量一樣在公式中使用:
<code>fit_y = slope * fit_x + intercept/<code>
該公式在數組中逐元素應用;因此,結果在初始數組中具有相同數量的條目。
要繪圖,首先,定義一個包含所有圖形的 圖形對象 :
<code>fig_width = 7 #inchfig_height = fig_width / 16 * 9 #inchfig_dpi = 100fig = plt.figure(figsize = (fig_width, fig_height), dpi = fig_dpi)/<code>
一個圖形可以畫幾個圖;在 Matplotlib 中,這些圖被稱為 軸 。本示例定義一個單軸對象來繪製數據點:
<code>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')/<code>
將該圖保存到 PNG 圖形文件 中,有:
<code>fig.savefig('fit_python.png')/<code>
如果要顯示(而不是保存)該繪圖,請調用:
<code>plt.show()/<code>
此示例引用了繪圖部分中使用的所有對象:它定義了對象 fig 和對象 ax。這在技術上是不必要的,因為 plt 對象可以直接用於繪製數據集。《 Matplotlib 教程 》展示了這樣一個接口:
<code>plt.plot(fit_x, fit_y)/<code>
坦率地說,我不喜歡這種方法,因為它隱藏了各種對象之間發生的重要交互。不幸的是,有時 官方的例子 有點令人困惑,因為他們傾向於使用不同的方法。在這個簡單的例子中,引用圖形對象是不必要的,但是在更復雜的例子中(例如在圖形用戶界面中嵌入圖形時),引用圖形對象就變得很重要了。
結果
命令行輸入:
<code>#### Anscombe's first set with Python ####Slope: 0.500091Intercept: 3.000091Correlation coefficient: 0.816421/<code>
這是 Matplotlib 產生的圖像:
GNU Octave 方式
GNU Octave 語言主要用於數值計算。它提供了一個簡單的操作向量和矩陣的語法,並且有一些強大的繪圖工具。這是一種像 Python 一樣的解釋語言。由於 Octave 的語法 幾乎兼容 MATLAB ,它經常被描述為一個替代 MATLAB 的免費方案。Octave 沒有被列為最流行的編程語言,而 MATLAB 則是,所以 Octave 在某種意義上是相當流行的。MATLAB 早於 NumPy,我覺得它是受到了前者的啟發。當你看這個例子時,你會看到相似之處。
安裝
fitting_octave.m 的例子只需要基本的 Octave 包,在 Fedora 中安裝相當簡單:
<code>sudo dnf install octave/<code>
代碼註釋
在 Octave 中,你可以用百分比符號(%)為代碼添加註釋,如果不需要與 MATLAB 兼容,你也可以使用 #。使用 # 的選項允許你編寫像 Python 示例一樣的特殊註釋行,以便直接在命令行上執行腳本。
必要的庫
本例中使用的所有內容都包含在基本包中,因此你不需要加載任何新的庫。如果你需要一個庫, 語法 是 pkg load module。該命令將模塊的功能添加到可用功能列表中。在這方面,Python 具有更大的靈活性。
定義變量
變量的定義與 Python 的語法基本相同:
<code>input_file_name = "anscombe.csv";delimiter = "\\t";skip_header = 3;column_x = 1;column_y = 2;/<code>
請注意,行尾有一個分號;這不是必需的,但是它會抑制該行結果的輸出。如果沒有分號,解釋器將打印表達式的結果:
<code>octave:1> input_file_name = "anscombe.csv"input_file_name = anscombe.csvoctave:2> sqrt(2)ans = 1.4142/<code>
打印輸出結果
強大的函數 printf() 是用來在終端上打印的。與 Python 不同,printf() 函數不會自動在打印字符串的末尾添加換行,因此你必須添加它。第一個參數是一個字符串,可以包含要傳遞給函數的其他參數的格式信息,例如:
<code>printf("Slope: %f\\n", slope);/<code>
在 Python 中,格式是內置在字符串本身中的,但是在 Octave 中,它是特定於 printf() 函數。
讀取數據
dlmread() 函數可以讀取類似 CSV 文件的文本內容:
<code>data = dlmread(input_file_name, delimiter, skip_header, 0);/<code>
結果是一個 矩陣 對象,這是 Octave 中的基本數據類型之一。矩陣可以用類似於 Python 的語法進行切片:
<code>x = data(:, column_x);y = data(:, column_y);/<code>
根本的區別是索引從 1 開始,而不是從 0 開始。因此,在該示例中,x 列是第一列。
擬合數據
要用直線擬合數據,可以使用 polyfit() 函數。它用一個多項式擬合輸入數據,所以你只需要使用一階多項式:
<code>p = polyfit(x, y, 1);slope = p(1);intercept = p(2);/<code>
結果是具有多項式係數的矩陣;因此,它選擇前兩個索引。要確定相關係數,請使用 corr() 函數:
<code>r_value = corr(x, y);/<code>
最後,使用 printf() 函數打印結果:
<code>printf("Slope: %f\\n", slope);printf("Intercept: %f\\n", intercept);printf("Correlation coefficient: %f\\n", r_value);/<code>
繪圖
與 Matplotlib 示例一樣,首先需要創建一個表示擬合直線的數據集:
<code>fit_x = linspace(min(x) - 1, max(x) + 1, 100);fit_y = slope * fit_x + intercept;/<code>
與 NumPy 的相似性也很明顯,因為它使用了 linspace() 函數,其行為就像 Python 的等效版本一樣。
同樣,與 Matplotlib 一樣,首先創建一個 圖 對象,然後創建一個 軸 對象來保存這些圖:
<code>fig_width = 7; %inchfig_height = fig_width / 16 * 9; %inchfig_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);/<code>
要設置軸對象的屬性,請使用 set() 函數。然而,該接口相當混亂,因為該函數需要一個逗號分隔的屬性和值對列表。這些對只是代表屬性名的一個字符串和代表該屬性值的第二個對象的連續。還有其他設置各種屬性的函數:
<code>xlim(ax, [min(x) - 1, max(x) + 1]);ylim(ax, [min(y) - 1, max(y) + 1]);xlabel(ax, 'x');ylabel(ax, 'y');/<code>
繪圖是用 plot() 功能實現的。默認行為是每次調用都會重置座標軸,因此需要使用函數 hold() 。
<code>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");/<code>
此外,還可以在 plot() 函數中添加屬性和值對。 legend 必須單獨創建,標籤應手動聲明:
<code>lg = legend(ax, "Fit", "Data");set(lg, "location", "northwest");/<code>
最後,將輸出保存到 PNG 圖像:
<code>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);/<code>
令人困惑的是,在這種情況下,選項被作為一個字符串傳遞,帶有屬性名和值。因為在 Octave 字符串中沒有 Python 的格式化工具,所以必須使用 sprintf() 函數。它的行為就像 printf() 函數,但是它的結果不是打印出來的,而是作為字符串返回的。
在這個例子中,就像在 Python 中一樣,圖形對象很明顯被引用以保持它們之間的交互。如果說 Python 在這方面的文檔有點混亂,那麼 Octave 的文檔 就更糟糕了。我發現的大多數例子都不關心引用對象;相反,它們依賴於繪圖命令作用於當前活動圖形。全局 根圖形對象 跟蹤現有的圖形和軸。
結果
命令行上的結果輸出是:
<code>#### Anscombe's first set with Octave ####Slope: 0.500091Intercept: 3.000091Correlation coefficient: 0.816421/<code>
它顯示了用 Octave 生成的結果圖像。
接下來
Python 和 GNU Octave 都可以繪製出相同的信息,儘管它們的實現方式不同。如果你想探索其他語言來完成類似的任務,我強烈建議你看看 Rosetta Code 。這是一個了不起的資源,可以看到如何用多種語言解決同樣的問題。
你喜歡用什麼語言繪製數據?在評論中分享你的想法。
via: https://opensource.com/article/20/2/python-gnu-octave-data-science
作者: Cristiano L. Fontana 選題: lujun9972 譯者: heguangzhi 校對: wxy
本文由 LCTT 原創編譯, Linux中國 榮譽推出
閱讀更多 Linux中國 的文章