在面對真實資料來源時,我們可能思考如何轉換為程式中可運行的資料,這個過程往往比動手寫程式要花更多的時間,因為人類可讀的資料與機器可讀的資料,往往有著極大的不同。

機器資料?人的資料?

試著以繪圖為任務,把玩NumPy之類的資料處理程式庫時,雖說我有具體目標,在功能龐大的API中,逐漸熟悉基礎觀念,然而,說實話,這終究只是停留在使用API的程度,與資料處理有一段距離。

這是因為以繪圖為任務時,多半涉及的是自動生成、自動衍生,也就是給幾個參數、產生一組資料,這些資料基本上就是由程式生成,因為要給程式中其他API使用,自然地會去符合API要求的格式;就算是讀入圖檔進行影像處理之類的任務,其資料來源本身通常也是某種圖檔格式,也就是多半已是機器可讀的形式,影像處理基本上是將之轉換為另一個機器可讀的格式。

虛構的資料來源多半也接觸不到實際的資料處理,例如,虛構一個CSV檔案,欄位為「數學,英文,物理」,各列資料為「90,99,100」的格式,這樣的資料太乾淨了,某些程度上是先射箭再畫靶的概念,腦袋中已經被程式庫的API給框住,然後設計出一個虛假但符合程式庫格式的資料罷了。

真實世界中的資料來源,不一定是專門為了給機器或程式庫判讀而設計,例如,以臺灣證券交易所的發行量加權股價指數歷史資料為例,下載的CSV檔案中第一列會有標題文字,日期欄位的內容會是"110/01/04",而指數部份會是"14,720.25"的格式。

這看來好像沒什麼,不過日期、指數等格式,其實都是給人看的資料,機器可不需要那些斜線、逗號之類的視覺輔助符號,如果以讀取後想要將資料視覺化,例如,以顯示指數線圖或K線圖之類的圖形為目標,也必須面對基本的資料清理問題。

純NumPy的方案

因為指數歷史資料是CSV檔案,第一個會想到Python內建的csv模組,然而,這免不了要使用for之類帶有迴圈概念的語法,若你使用NumPy,可能不想這麼做,而想要遵循陣列程式設計的典範,以便善用NumPy的一些特性。

numpy模組的loadtxt函式可以載入文字檔案,預設會以空白、換行來切出列行,並轉換為np.float64形態;然而,可以使用delimiter參數指定分隔符,並使用converters參數指定行(column)轉換器,透過這兩個參數,基本上,就能自行剖析每一列文字。

另一方式是透過numpy模組的fromregex函式,它可以指定規則表示式,用括號分組來捕捉資料,例如,regex=r'"(.+)","([\d,.]+)","(.+)","(.+)","(.+)",',底下程式片段,就能捕捉到指數歷史資料的各個欄位:

hist = np.fromregex('MI_5MINS_HIST.csv', regex,
encoding = 'Big5', dtype = np.str)

hist是NumPy陣列,因而想取得第n行,就能透過hist[:,n],但資料目前都是字串型態,若要在Matplotlib繪製線圖,須將"14,720.25"的數字格式剖析為浮點數,最簡單的方式是透過Python內建的locale 模組,設定區域化的數值格式,之後將locale.atof向量化,以便剖析NumPy陣列的元素,例如:

locale.setlocale(locale.LC_NUMERIC, 'zh_TW.UTF-8')
atof = np.frompyfunc(locale.atof, 1, 1) # 向量化

這麼一來,atof(hist[:,n])就可以將第n行的指數資料剖析為浮點數,然後用Matplotlib來繪製線圖了,若對完整程式實作有興趣,我們可參考〈NumPy載入文字檔〉〈Matplotlib軸的格式〉

透過Pandas的DataFrame

對於表格型態的資料,透過(基於NumPy的)Pandas處理會方便許多,畢竟Pandas的DataFrame型態,提供了許多(二維)NumPy陣列沒有的便捷方法,而且整合了Matplotlib的基本繪圖,很適合讀入CSV之類的資料並進行視覺化,而且Pandas提供了豐富的API,可以讀取各種格式的檔案,支援的檔案格式可以參考官方〈Input/output〉文件

然而,這並不表示現實生活的資料載入後,不用任何清理,例如,pandas模組的read_csv可以讀入CSV檔案傳回DataFrame,然而,若讀入方才的指數歷史資料CSV,最後一行會是NaN,這是因為來源CSV檔案每一列最後都有逗號,等於多一個欄位,而該欄位沒有資料,這時Pandas會自行補上NaN。

因此,初步的清理,可以是透過DataFrame的iloc[:,:-1]來去除該行,或者是透過read_csv的usecols參數,指定要使用哪些欄位;另外,可指定read_csv的index_col參數為'日期',DataFrame的plot方法繪圖時,索引行預設成為x軸,也就不用特別剖析日期。

然而,指數的部份,需要數值才能繪圖。若是DataFrame,可以透過applymap方法,指定方才的atof函式,來套用全部的元素(索引行元素不會參與),後續呼叫DataFrame的plot方法時,就會自動將全部的行以線圖來繪製。

既然股市的指數能以線圖方式展現,你可能會想,能否畫個K線圖(candlestick chart)?這需要Matplotlib的mplfinance,不過,mpl-finance已無維護,在2019年,Daniel Goldfarb接手該專案,folk並更名為mplfinance。

mplfinance會使用日期作為索引,來源DataFrame的索引行必須是DatetimeIndex實例,實作上可以透過read_csv的index_col參數指定索引行,雖然read_csv有個parse_date參數,不過因為指數歷史資料的CSV檔案中,日期使用的是民國年,而parse_date在剖析日期時必須使用西元年,因此必須改使用read_csv的date_parser參數,指定自訂的剖析器。

mplfinance預設接受的DataFrame,欄位名稱與順序上必須是'open'、'high'、'low'、'close',以及一個選擇性的'volume',這可以直接更改DataFrame的columns,或是呼叫mplfinance的plot時,透過columns參數指定自訂的欄位名稱,要注意的是,雖然'volume'是選擇性的,不過呼叫mplfinance的plot時自訂欄位名稱時,必須都列出來。若想看看完整程式實作的樣貌,我們可以參考〈Pandas輸入輸出〉

給人讀還是機器?

實際上,無論是NumPy、Pandas、Matplotlib或是mpl-finance,掌握基礎API的使用,都不算太難,然而,在面對真實資料來源時,就算只是要單純視覺化,如何轉換為程式中可運行的資料,這個思考過程,往往比動手寫程式前要花更多的時間。

這是因為,有許多資料來源,往往是以人類易於閱讀或理解的觀點來規畫或格式化,因而經常會有多重的訊息,例如欄位中除了數值,可能還有附註、括號或其他視覺上輔助的符號,才會增加程式上清理的麻煩。

從另一方面來說,如果產出的資料,本身就是打算給機器閱讀,或許就要對機器方面做些思考,而不是從人類的觀點來生成資料;當然,不見得會有完全適切的資料格式,試著多從現實來源的資料做練習,對清理方式做更多的思考,從中瞭解有哪些工具或策略可用,才是資料清理的不二法門吧!

專欄作者

熱門新聞

Advertisement