在機器學習的世界中,理論與實踐之間總存在著一道鴻溝。許多專業人士在面對日常工作時,需要的不僅是深厚的理論基礎,更需要能夠立即解決問題的實用技巧。這篇文章的目標正是提供機器學習實踐者一套實用工具,幫助解決在建立模型過程中遇到的各種挑戰。

我將採用任務導向的方法,提供近200個自包含的解決方案,這些方案可以直接複製貼上並執行,解決資料科學家或機器學習工程師在建立模型時最常遇到的問題。

解決實際機器學習問題的方法

想像一個場景:你手上有一個JSON檔案,包含1,000個類別和數值特徵,這些特徵存在缺失資料,目標向量是類別不平衡的,而你希望建立一個可解釋的模型。這時,你可能需要以下步驟:

  1. 載入JSON檔案
  2. 標準化特徵
  3. 編碼特徵字典
  4. 處理缺失的類別值
  5. 使用主成分析減少特徵
  6. 透過隨機搜尋選擇最佳模型
  7. 訓練隨機森林分類別
  8. 在隨機森林中選擇隨機特徵

透過這篇文章,我希望讀者能夠:

  • 直接複製程式碼確認它能與提供的資料集一起正常運作
  • 理解程式碼後的技術原理,並瞭解哪些引數需要重點考慮
  • 將這些方案整合到自己的應用中

適合的讀者群

這篇文章並非機器學習的入門。如果你對機器學習的基本概念不熟悉,建議先學習相關基礎知識。這篇文章是為那些已經熟悉機器學習理論和概念,但需要快速參考來解決日常挑戰的實踐者準備的。

文章假設讀者熟悉Python程式語言和套件管理。如果你不熟悉交叉驗證、隨機森林或梯度下降等概念,建議先閱讀專門的入門教材,然後再回來學習這些實用解決方案。

機器學習術語簡介

機器學習領域融合了電腦科學、統計學和數學等多個領域的技術,因此在討論中使用的術語有很大的變化:

  • 觀測值:我們觀察層級中的單一單位,例如一個人、一筆銷售或一條記錄
  • 學習演算法:用於學習模型最佳引數的演算法,如線性迴歸、樸素貝葉斯或決策樹
  • 模型:學習演算法訓練的輸出,我們使用這些模型進行預測
  • 引數:透過訓練學習的模型權重或係數
  • 超引數:需要在訓練前設定的學習演算法設定
  • 效能:用於評估模型的指標
  • 損失:透過訓練最大化或最小化的指標
  • 訓練:使用梯度下降等數值方法將學習演算法應用於資料
  • 擬合:使用分析方法將學習演算法應用於資料
  • 資料:觀測值的集合

向量、矩陣與陣列基礎

NumPy是Python機器學習技術堆積積疊基礎。它能夠高效地操作機器學習中常用的資料結構:向量、矩陣和張量。在接下來的內容中,我們將介紹在機器學習工作流程中最常用的NumPy操作。

建立向量

在機器學習中,向量是最基本的資料結構之一。使用NumPy建立一維陣列可以輕鬆實作向量的表示:

# 載入函式庫import numpy as np

# 建立一個行向量
vector_row = np.array([1, 2, 3])

# 建立一個列向量
vector_column = np.array([[1],
                         [2],
                         [3]])

這段程式碼示瞭如何使用NumPy建立向量。NumPy的主要資料結構是多維陣列,而向量實際上就是一維陣列。這些陣列可以水平表示(行)或垂直表示(列)。行向量是一個簡單的一維陣列,而列向量則是一個每個元素都包含在子陣列中的二維陣列。這種表示方式與線性代數中的向量概念一致,為後續的矩陣運算奠定基礎。

建立矩陣

矩陣是二維資料結構,在機器學習中用於表示多種資料關係:

# 載入函式庫import numpy as np

# 建立矩陣
matrix = np.array([[1, 2],
                   [1, 2],
                   [1, 2]])

這裡我們使用NumPy的二維陣列建立了一個矩陣。這個矩陣包含三行兩列(一列是1,另一列是2)。

值得注意的是,NumPy實際上有一個專門的矩陣資料結構:

matrix_object = np.mat([[1, 2],
                        [1, 2],
                        [1, 2]])

但這種矩陣資料結構並不推薦使用,原因有二:首先,陣列是NumPy的事實標準資料結構;其次,絕大多數NumPy操作回傳的是陣列,而非矩陣物件。因此在實際應用中,我們通常使用多維陣列來表示矩陣。

建立稀疏矩陣

在處理大量資料時,我們經常會遇到大部分元素為零的情況,這時稀疏矩陣就能派上用場:

# 載入函式庫
import numpy as np
from scipy import sparse

# 建立矩陣
matrix = np.array([[0, 0],
                   [0, 1],
                   [3, 0]])

# 建立壓縮稀疏行(CSR)矩陣
matrix_sparse = sparse.csr_matrix(matrix)

在機器學習中,我們經常遇到資料量巨大但大多數元素為零的情況。例如,想像一個矩陣,其中列是Netflix上的每部電影,行是每個Netflix使用者值是使用者看特定電影的次數。這個矩陣將有成千上萬的列和數百萬的行!然而,由於大多數使用者會觀看大多數電影,絕大多數元素將為零。

稀疏矩陣只儲存非零元素,並假設所有其他值為零,這導致了顯著的計算節省。在我們的解決方案中,我們建立了一個包含兩個非零值的NumPy陣列,然後將其轉換為稀疏矩陣。如果我們檢視稀疏矩陣,可以看到只儲存了非零值:

# 檢視稀疏矩陣
print(matrix_sparse)
# 輸出:
# (1, 1) 1
# (2, 0) 3

在壓縮稀疏行(CSR)矩陣中,(1, 1)和(2, 0)表示非零值1和3的(從零開始索引的)索引。例如,元素1位於第二行第二列。

我們可以透過建立一個更大的矩陣(有更多零元素)然後將其與原始稀疏矩陣進行比較,來看出稀疏矩陣的優勢:

# 建立更大的矩陣
matrix_large = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
                         [0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
                         [3, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

# 建立壓縮稀疏行(CSR)矩陣
matrix_large_sparse = sparse.csr_matrix(matrix_large)

# 檢視原始稀疏矩陣
print(matrix_sparse)
# 輸出:
# (1, 1) 1
# (2, 0) 3

# 檢視更大的稀疏矩陣
print(matrix_large_sparse)
# 輸出:
# (1, 1) 1
# (2, 0) 3

我們可以看到,儘管在更大的矩陣中增加了更多的零元素,但稀疏矩陣的表示方式保持不變,只儲存非零元素的位置和值。這就是稀疏矩陣的效率所在:無論矩陣多大,只要非零元素數量有限,儲存空間和計算效率都能保持在一個合理的範圍內。

在處理大規模真實世界資料時,這種效率差異可能會非常顯著,特別是在處理文字資料(如詞袋模型)、推薦系統或網路分析等領域,這些領域通常涉及非常大但非常稀疏的資料集。

稀疏矩陣的使用不僅節省了儲存空間,還能顯著提高計算效率。例如,在矩陣乘法操作中,如果使用稀疏矩陣表示,可以跳過所有涉及零元素的乘法運算,大減少計算量。

在實際應用中,當面對大型資料集時,評估其稀疏性並選擇合適的稀疏表示方法是提高模型效率的重要步驟。SciPy提供了多種稀疏矩陣格式,如CSR、CSC(壓縮稀疏列)、COO(坐標)等,可以根據具體操作需求選擇最合適的格式。

機器學習中,向量和矩陣操作是基礎中的基礎。掌握了這些基本操作,我們才能更好地理解和實作各種機器學習演算法。在實際應用中,有效地使用NumPy和SciPy等函式庫的功能可以大提高程式碼效率和可讀性,使我們能夠專注於解決實際問題,而不是陷入低層次的實作細節。

在後續的文章中,我將繼續介紹更多機器學習實踐中的實用技巧,從資料預處理到模型評估,為讀者提供一套完整的機器學習工具箱。

從教學機器學習的角度來看:為瞭解機器學習但需要查詢如何實作特定任務的個人提供資源。

本文的目標是教您如何使用機器學習完成工作。每個秘訣都會逐步帶您完成特定的機器學習任務或操作。這本文並不是要教導您機器學習領域的基礎理論,雖然我也會在需要時提供每個演算法背後的直覺的簡短概述。

具體而言,本文的食譜設計簡短與切中重點。文中沒有太多理論、圖片,也沒有冗長的說明。目的是提供您機器學習操作的參考,而不是教您機器學習本身。如果您想要學習機器學習的理論,這本文並不適合您。反之,我建議您參考數百本有關這個主題的教科書、部落格和大型開放線上課程 (MOOC)。

我在全文中使用了 scikit-learn 和 pandas 函式庫。如果這些函式庫中有特定的實作,我也嘗試使用。我相信使用通用和標準化的函式庫總比重新發明好。對於神經網路,我使用了流行的 Keras 函式庫。

本文中的大部分秘訣都涉及監督式機器學習,也就是使用標記訓練資料學習模型的過程,此模型可用於預測新資料的標記。然而,本文也有非監督式學習的秘訣,例如聚類別 (clustering) 和降維 (dimensionality reduction)。

雖然本文並非設計為學習 Python 用於資料科學的全面資源,但我已嘗試包含對從初學者到經驗豐富的實務人員等各種資料科學家都有用的配方。本文中的配方可輕鬆適應您的特定需求。

這本文的物件 本文的物件為想要查詢如何執行特定任務的資料科學家和機器學習從業人員。我假設您對這個領域有基本的瞭解,特別是您熟悉常見的機器學習模型 (例如:線性迴歸、邏輯迴歸、支援向量機、神經網路) 和概念 (例如:交叉驗證、L1/L2 正則化、監督學習)。如果您沒有這樣的瞭解,我建議您先閱讀一本有關機器學習的入門本文。

NumPy陣列與矩陣操作完全

在資料科學與科學計算領域中,NumPy是Python生態系統中不可或缺的核心函式庫為處理多維陣列的基礎工具,它提供了高效能的資料結構與運算功能,使複雜的數值運算變得直觀與高效。這篇文章將帶領讀者從基礎到進階,全面掌握NumPy的陣列與矩陣操作技巧。

善用稀疏矩陣提升效能

稀疏矩陣(Sparse Matrix)是一種特殊的矩陣形式,其中大部分元素為零。在處理大規模但稀疏的資料集時,使用稀疏矩陣可以顯著節省記憶體並提升計算效率。

讓我們看如何在Python中使用SciPy的稀疏矩陣功能:

# 載入必要的函式庫
import numpy as np
from scipy import sparse

# 建立一個包含大量零的矩陣
matrix = np.array([
    [0, 0, 0, 0, 0, 1],
    [0, 0, 2, 0, 0, 0],
    [0, 0, 0, 0, 0, 0],
    [0, 3, 0, 0, 0, 0]
])

# 將矩陣轉換為壓縮稀疏行格式(CSR)
matrix_sparse = sparse.csr_matrix(matrix)

# 顯示稀疏矩陣
print("稀疏矩陣的形狀:", matrix_sparse.shape)
print("稀疏矩陣的資料型態:", type(matrix_sparse))
print("稀疏矩陣的非零元素數量:", matrix_sparse.count_nonzero())

這段程式碼展示瞭如何將一個普通NumPy陣列轉換為SciPy的壓縮稀疏行格式(CSR)矩陣。CSR是一種常用的稀疏矩陣表示法,它只儲存非零元素及其位置資訊,大幅減少記憶體使用量。matrix_sparse.count_nonzero()方法可以計算矩陣中非零元素的數量,幫助我們瞭解矩陣的稀疏程度。

稀疏矩陣的一個重要特性是,當我們嘗試新增值為零的元素時,它不會改變矩陣的大小或結構:

# 嘗試將某個位置的值設為0
matrix_sparse[0, 0] = 0
print("設定零值後的非零元素數量:", matrix_sparse.count_nonzero())

# 將非零值設為零
matrix_sparse[1, 2] = 0
print("將非零元素設為零後的數量:", matrix_sparse.count_nonzero())

這段程式碼展示了稀疏矩陣的一個關鍵特性。當我們將一個已經是零的元素再次設為零時,稀疏矩陣的結構不會改變,非零元素數量保持不變。而當我們將一個非零元素設為零時,非零元素的數量會減少。這種行為對於需要頻繁更新大型稀疏資料結構的應用來說非常重要。

值得注意的是,SciPy提供了多種稀疏矩陣格式,如壓縮稀疏行(CSR)、壓縮稀疏列(CSC)、座標列表(COO)和字典鍵值(DOK)等。每種格式都有其特定的優勢和使用場景:

  • CSR/CSC: 適合矩陣算術運算和行/列切片
  • COO: 適合快速構建矩陣
  • DOK: 適合逐個元素的增量構建
  • LIL: 適合按行修改矩陣

選擇哪種稀疏矩陣格式應取決於具體的應用場景和操作需求。

陣列元素選擇的藝術

在資料分析中,能夠靈活地選擇陣列中的特定元素或子集是基礎技能。NumPy提供了強大而直觀的索引和切片功能。

# 載入函式庫
import numpy as np

# 建立一維向量
vector = np.array([1, 2, 3, 4, 5, 6])

# 建立矩陣
matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

# 選擇向量的第三個元素
print("向量的第三個元素:", vector[2])

# 選擇矩陣的第二行第二列
print("矩陣的第二行第二列:", matrix[1, 1])

這段程式碼展示了NumPy中基本的元素選擇操作。在Python中,索引是從0開始的,所以vector[2]實際上選擇的是向量中的第三個元素,而matrix[1, 1]選擇的是矩陣中第二行第二列的元素。

NumPy提供了多種靈活的方式來選擇陣列中的元素或子陣列:

# 選擇向量的所有元素
print("向量的所有元素:", vector[:])

# 選擇向量中直到第三個元素(包含)的所有元素
print("向量的前三個元素:", vector[:3])

# 選擇向量中第三個元素之後的所有元素
print("向量的第四個元素開始:", vector[3:])

# 選擇向量的最後一個元素
print("向量的最後一個元素:", vector[-1])

# 選擇矩陣的前兩行和所有列
print("矩陣的前兩行:\n", matrix[:2, :])

# 選擇矩陣的所有行和第二列
print("矩陣的第二列:\n", matrix[:, 1:2])

這段程式碼展示了NumPy強大的切片功能。切片語法[start:stop:step]允許我們選擇陣列的子集,其中start是起始索引,stop是結束索引(不包含),step是步長。

特別注意以下幾點:

  • vector[:]選擇向量的所有元素
  • vector[:3]選擇索引0到2的元素
  • vector[3:]選擇索引3到結束的元素
  • vector[-1]使用負索引從末尾選擇元素
  • matrix[:2, :]選擇前兩行的所有列
  • matrix[:, 1:2]選擇所有行的第二列,結果是一個二維陣列

在實際分析中,這些技巧可以幫助我們快速提取和操作感興趣的資料子集。

深入瞭解矩陣的基本屬性

在處理矩陣時,瞭解其基本屬性如形狀、大小和維度是十分重要的,這些訊息不僅幫助我們理解資料結構,也是進行後續操作的基礎。

# 載入函式庫
import numpy as np

# 建立矩陣
matrix = np.array([[1, 2, 3, 4],
                   [5, 6, 7, 8],
                   [9, 10, 11, 12]])

# 檢視行數和列數
print("矩陣形狀(行x列):", matrix.shape)

# 檢視元素總數
print("矩陣元素數量:", matrix.size)

# 檢視維度數
print("矩陣維度:", matrix.ndim)

這段程式碼展示瞭如何取得NumPy陣列的基本屬性:

  • matrix.shape: 回傳一個元組,表示陣列的維度。在這個例子中,結果是(3, 4),表示這是一個3行4列的矩陣。
  • matrix.size: 回傳陣列中元素的總數,即行數乘以列數,在這裡是12。
  • matrix.ndim: 回傳陣列的維度數,在這個例子中是2,表示這是一個二維陣列。

雖然這些操作看似基礎,但在實際開發中,它們是確保程式正確性的重要工具。當我處理複雜的資料轉換或計算時,經常會檢查這些屬性作為除錯和驗證的手段。例如,在進行矩陣乘法前,確認兩個矩陣的形狀是否相容是避免錯誤的關鍵步驟。

對陣列元素應用函式

在資料分析中,我們經常需要對陣列的每個元素應用相同的操作。NumPy提供了向量化的方法來高效地實作這一點。

# 載入函式庫
import numpy as np

# 建立矩陣
matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

# 建立一個將數字加100的函式
add_100 = lambda i: i + 100

# 建立向量化函式
vectorized_add_100 = np.vectorize(add_100)

# 對矩陣中所有元素應用函式
result = vectorized_add_100(matrix)
print("應用函式後的結果:\n", result)

這段程式碼展示瞭如何使用NumPy的vectorize函式將一個普通的Python函式轉換為可以應用於陣列每個元素的向量化函式。在這個例子中,我們建立了一個簡單的Lambda函式add_100,它將輸入值加上100,然後使用np.vectorize將其轉換為可以應用於整個陣列的函式。

值得注意的是,vectorize實際上是在Python層面上對每個元素進行迴圈處理,並不會帶來效能提升。它的主要優勢是提供了一個便捷的介面,讓我們可以將普通函式應用於陣列。

對於簡單的算術運算,NumPy的廣播(broadcasting)功能提供了更高效的解決方案:

# 使用廣播直接對所有元素加100
result_broadcast = matrix + 100
print("使用廣播的結果:\n", result_broadcast)

這段程式碼展示了NumPy的廣播功能,它允許不同形狀的陣列之間進行算術運算。在這個例子中,標量值100被"廣播"到與矩陣相同的形狀,然後進行元素級的加法運算。這種方法比使用vectorize更加高效,因為它是在底層C語言實作的。

在實際應用中,當需要對陣列元素進行簡單的算術運算時,直接使用NumPy的廣播功能通常是更好的選擇。而當需要應用複雜的自定義函式時,vectorize則提供了一個便捷的解決方案。

計算陣列的統計值

在資料分析中,計算統計值如最大值、最小值、平均值、方差和標準差是常見的操作。NumPy提供了高效的函式來完成這些任務。

# 載入函式庫
import numpy as np

# 建立矩陣
matrix = np.array([[1, 2, 3],
                   [4, 5, 6],
                   [7, 8, 9]])

# 找出最大值和最小值
print("矩陣的最大值:", np.max(matrix))
print("矩陣的最小值:", np.min(matrix))

# 計算平均值
print("矩陣的平均值:", np.mean(matrix))

# 計算方差
print("矩陣的方差:", np.var(matrix))

# 計算標準差
print("矩陣的標準差:", np.std(matrix))

這段程式碼展示瞭如何使用NumPy的統計函式運算陣列的各種統計值:

  • np.max(matrix): 回傳矩陣中的最大值
  • np.min(matrix): 回傳矩陣中的最小值
  • np.mean(matrix): 計算矩陣所有元素的平均值
  • np.var(matrix): 計算矩陣元素的方差
  • np.std(matrix): 計算矩陣元素的標準差

這些函式預設會計算整個陣列的統計值,但我們也可以透過指定axis引數來沿特定軸計算統計值:

# 計算每列的最大值
print("每列的最大值:", np.max(matrix, axis=0))

# 計算每行的最大值
print("每行的最大值:", np.max(matrix, axis=1))

# 計

## NumPy 線上性代數運算中的關鍵應用

線性代數是資料科學和機器學習的根本 NumPy 則是 Python 中處理線性代數運算的核心工具在建立機器學習模型的過程中我們經常需要進行矩陣操作計算特徵值執行向量點積等操作這些看似抽象的數學概念實際上是許多演算法背後的關鍵機制

在這篇文章中我將帶你探索 NumPy 中一系列實用的線性代數功能從基本的矩陣運算到進階的特徵值分析並展示如何將這些工具應用於實際問題

### 矩陣行列式計算與應用

行列式是方陣的一個標量特性在許多數學和物理應用中都扮演著重要角色它可以告訴我們矩陣是否可逆並在解線性方程組計算體積變化率等方面有重要應用

使用 NumPy 計算矩陣行列式非常直觀

```python
# 載入 NumPy 函式庫
import numpy as np

# 建立一個矩陣
matrix = np.array([[1, 2, 3],
                   [2, 4, 6],
                   [3, 8, 9]])

# 計算矩陣的行列式
determinant = np.linalg.det(matrix)
print(determinant)
# 輸出: 0.0

上面的程式碼建立了一個 3x3 的矩陣,然後使用 np.linalg.det() 函式運算其行列式。結果為 0,這表示該矩陣是奇異矩陣(不可逆)。在實際應用中,這可能意味著矩陣所代表的線性變換會將空間「壓扁」至更低的維度,或者表示線性方程組沒有唯一解。

當我在設計機器學習系統時,經常利用行列式來檢查協方差矩陣的條件,確保數值計算的穩定性。行列式接近零的矩陣在求逆時會導致數值不穩定,這是許多學習演算法中需要特別注意的問題。

提取矩陣對角線元素

矩陣的對角線元素在統計和機器學習中有特殊意義。例如,在協方差矩陣中,對角線元素代表各變數的方差。NumPy 提供了簡便的方法來提取這些元素:

# 載入 NumPy 函式庫
import numpy as np

# 建立矩陣
matrix = np.array([[1, 2, 3],
                   [2, 4, 6],
                   [3, 8, 9]])

# 提取對角線元素
diag_elements = matrix.diagonal()
print(diag_elements)
# 輸出: array([1, 4, 9])

除了提取主對角線,我們還可以取得偏離主對角線的元素:

# 提取主對角線上方一條對角線
upper_diag = matrix.diagonal(offset=1)
print(upper_diag)
# 輸出: array([2, 6])

# 提取主對角線下方一條對角線
lower_diag = matrix.diagonal(offset=-1)
print(lower_diag)
# 輸出: array([2, 8])

這個功能在處理特殊結構的矩陣時特別有用。例如,在處理三對角矩陣(只有主對角線及其相鄰的兩條對角線有非零元素的矩陣)時,我們可以僅提取這三條對角線來進行高效運算。

我在最佳化型稀疏矩陣計算時,經常使用這個技巧來減少記憶體用並提高處理速度。對於某些特定結構的問題,僅使用對角線元素就能得到問題的解或良好的近似解。

計算矩陣的跡

矩陣的跡是指主對角線元素的總和,這在多個機器學習演算法中都是一個重要的指標:

# 載入 NumPy 函式庫
import numpy as np

# 建立矩陣
matrix = np.array([[1, 2, 3],
                   [2, 4, 6],
                   [3, 8, 9]])

# 計算矩陣的跡
trace = matrix.trace()
print(trace)
# 輸出: 14

我們也可以透過先提取對角線元素,然後求和來達到同樣的效果:

# 提取對角線元素並求和
trace_alt = sum(matrix.diagonal())
print(trace_alt)
# 輸出: 14

矩陣的跡在機器學習中有多種應用。例如,在主成分分析(PCA)中,我們尋找的是使投影資料方差最大的方向,而總方差等於協方差矩陣的跡。此外,在最佳化些目標函式時,矩陣的跡也常作為正則化項。

在處理高維資料時,我曾使用矩陣的跡來快速評估特徵提取方法的效果,因為它提供了資料分散程度的簡單度量。矩陣跡的計算複雜度遠低於完整的特徵值分解,因此在需要快速評估的場景中特別有用。

計算特徵值與特徵向量

特徵值和特徵向量是理解矩陣行為的關鍵,在降維、主成分分析、譜聚類別眾多機器學習技術中扮演核心角色:

# 載入 NumPy 函式庫
import numpy as np

# 建立矩陣
matrix = np.array([[1, -1, 3],
                   [1, 1, 6],
                   [3, 8, 9]])

# 計算特徵值和特徵向量
eigenvalues, eigenvectors = np.linalg.eig(matrix)

# 檢視特徵值
print(eigenvalues)
# 輸出: array([ 13.55075847,  0.74003145, -3.29078992])

# 檢視特徵向量
print(eigenvectors)
# 輸出: array([[-0.17622017, -0.96677403, -0.53373322],
#              [-0.435951  ,  0.2053623 , -0.64324848],
#              [-0.88254925,  0.15223105,  0.54896288]])

特徵值和特徵向量揭示了矩陣作為線性變換的本質特性。直觀地理解,特徵向量是經過矩陣變換後方向不變的向量,而對應的特徵值則表示該方向上的伸縮比例。

在上面的例子中,矩陣有三個特徵值:約為13.55、0.74和-3.29。正的特徵值表示對應方向上的拉伸,負的特徵值表示有反向的拉伸(即翻轉),而接近零的特徵值則表示該方向接近被「壓扁」。

在實際應用中,我經常使用特徵值分解來理解資料的內在結構。例如,在處理影像資料時,特徵值可以幫助確定保留多少主成分以平衡訊息保留和計算效率。較大的特徵值對應的特徵向量通常包含資料集中最重要的模式。

計算向量點積

向量的點積是機器學習中最基本也最常用的運算之一,它測量了兩個向量的相似度和它們之間的角度:

# 載入 NumPy 函式庫
import numpy as np

# 建立兩個向量
vector_a = np.array([1, 2, 3])
vector_b = np.array([4, 5, 6])

# 計算點積
dot_product = np.dot(vector_a, vector_b)
print(dot_product)
# 輸出: 32

在 Python 3.5+ 中,我們還可以使用更簡潔的 @ 運算元:

# 使用 @ 運算元計算點積
dot_product_alt = vector_a @ vector_b
print(dot_product_alt)
# 輸出: 32

向量點積的數學定義是對應元素相乘後求和:1×4 + 2×5 + 3×6 = 4 + 10 + 18 = 32。

點積在機器學習中有廣泛應用。例如,在餘弦相似度計算中,我們使用標準化向量的點積來衡量文字、使用者好等的相似程度。線上性迴歸中,預測值是特徵向量與權重向量的點積。在神經網路中,每個神經元的輸出本質上是輸入向量與權重向量的點積再加上偏置項。

我在開發推薦系統時,大量使用點積計算來發現使用者物品之間的相似性模式。點積計算效率高與易於理解,是處理高維向量資料的基礎工具。

矩陣加減法

矩陣的加減法是元素級操作,在資料預處理、特徵工程等領域有廣泛應用:

# 載入 NumPy 函式庫
import numpy as np

# 建立矩陣
matrix_a = np.array([[1, 1, 1],
                     [1, 1, 1],
                     [1, 1, 2]])

# 建立矩陣
matrix_b = np.array([[1, 3, 1],
                     [1, 3, 1],
                     [1, 3, 8]])

# 矩陣相加
matrix_sum = np.add(matrix_a, matrix_b)
print(matrix_sum)
# 輸出: array([[ 2, 4, 2],
#              [ 2, 4, 2],
#              [ 2, 4, 10]])

# 矩陣相減
matrix_diff = np.subtract(matrix_a, matrix_b)
print(matrix_diff)
# 輸出: array([[ 0, -2,  0],
#              [ 0, -2,  0],
#              [ 0, -2, -6]])

我們也可以直接使用 +- 運算元:

# 使用 + 運算元進行矩陣加法
matrix_sum_alt = matrix_a + matrix_b
print(matrix_sum_alt)
# 輸出: array([[ 2, 4, 2],
#              [ 2, 4, 2],
#              [ 2, 4, 10]])

矩陣加減法需要兩個矩陣具有相同的形狀,運算結果是對應位置元素的加減。這種操作在資料標準化、影像處理和特徵工程中非常常見。

在開發電腦視覺應用時,我經常使用矩陣加減法進行背景減除、影像增強等操作。例如,將一張影像減去平均背景可以突出異常區域;將兩個時間點的感測器資料相減可以檢測出變化模式。

矩陣乘法

矩陣乘法是線性代數中最核心的操作之一,在機器學習的幾乎每個角落都能看到它的身影:

# 載入 NumPy 函式庫
import numpy as np

# 建立矩陣
matrix_a = np.array([[1, 1],
                     [1, 2]])

# 建立矩陣
matrix_b = np.array([[1, 3],
                     [1, 2]])

# 矩陣乘法
matrix_product = np.dot(matrix_a, matrix_b)
print(matrix_product)
# 輸出: array([[2, 5],
#              [3, 7]])

在 Python 3.5+ 中,我們可以使用 @ 運算元:

# 使用 @ 運算元進行矩陣乘法
matrix_product_alt = matrix_a @ matrix_b
print(matrix_product_alt)
# 輸出: array([[2, 5],
#              [3, 7]])

如果想進行元素級別的乘法,我們可以使用 * 運算元:

# 元素級矩陣乘法
element_wise_product = matrix_a * matrix_b
print(element_wise_product)
# 輸出: array([[1, 3],
#              [1, 4]])

矩陣乘法(點積)與元素級乘法是兩種完全不同的操作。矩陣乘法要求第一個矩陣的列數等於第二個矩陣的行數,結果矩陣的形狀為(第一個矩

機器學習資料載入:從範例到實戰的完整

在機器學習專案開發過程中,資料載入是第一個也是最基礎的步驟。無論是研究新演算法還是建立預測模型,擁有適合的資料集都是專案成功的關鍵。在這篇文章中,我將分享多種載入資料的方法,從使用scikit-learn內建的範例資料集,到從各種格式檔案匯入真實資料,甚至是如何生成模擬資料進行演算法測試。

善用scikit-learn的範例資料集加速學習

當我們想要快速測試機器學習演算法或是學習新概念時,不必總是從頭開始準備資料。scikit-learn提供了許多經典的範例資料集,讓我們能夠立即開始實驗。

以下是載入scikit-learn內建資料集的方法:

# 載入scikit-learn的資料集模組
from sklearn import datasets

# 載入手寫數字資料集
digits = datasets.load_digits()

# 建立特徵矩陣
features = digits.data

# 建立目標向量
target = digits.target

# 檢視第一筆觀察值
print(features[0])

上面的程式碼示範瞭如何載入scikit-learn中的手寫數字資料集(MNIST的簡化版)。datasets.load_digits()函式會回傳一個類別字典的物件,其中包含資料集的特徵矩陣(.data屬性)和對應的標籤向量(.target屬性)。特徵矩陣中的每一列代表一張8x8畫素的手寫數字影像,被壓縮成64個元素的一維陣列,每個元素代表一個畫素的灰度值。

scikit-learn提供了多個適合不同機器學習任務的範例資料集,我特別推薦以下幾個:

  1. load_boston - 波士頓房價資料集,包含503筆觀察值,適合迴歸演算法學習
  2. load_iris - 鳶尾花資料集,包含150筆觀察值,適合分類別法學習
  3. load_digits - 手寫數字資料集,包含1,797筆觀察值,適合影像分類別習

這些資料集經常被稱為「玩具資料集」(toy dataset),因為它們比實際世界的資料集小得多與更加乾淨。但正是這些特性讓它們成為學習和測試演算法的絕佳選擇。

客製化模擬資料集的生成技巧

有時候,即使是現成的範例資料集也無法滿足特定的測試需求。此時,生成模擬資料就成為一個極佳的選擇。scikit-learn提供了多種生成模擬資料的方法,能夠針對不同類別的機器學習問題客製化資料特性。

生成迴歸問題的模擬資料

當我需要測試迴歸演算法時,make_regression函式是我的首選:

# 載入函式庫
from sklearn.datasets import make_regression

# 生成特徵矩陣、目標向量和真實係數
features, target, coefficients = make_regression(
    n_samples=100,    # 樣本數量
    n_features=3,     # 特徵數量
    n_informative=3,  # 有資訊量的特徵數
    n_targets=1,      # 目標變數量
    noise=0.0,        # 噪聲水平
    coef=True,        # 是否回傳真實係數
    random_state=1    # 隨機種子
)

# 檢視特徵矩陣和目標向量
print('特徵矩陣\n', features[:3])
print('目標向量\n', target[:3])

這段程式碼使用make_regression生成適合線性迴歸問題的模擬資料。它產生了100筆樣本,每筆有3個特徵。n_informative=3表示所有3個特徵都用於生成目標值,沒有冗餘特徵。noise=0.0設定了零噪聲,意味著生成的資料會完全符合線性關係。coef=True讓函式額外回傳用於生成資料的真實係數,這對於評估模型學習效果特別有用。

生成分類別題的模擬資料

分類別題是機器學習中的另一大類別務,make_classification函式提供了生成分類別料的強大功能:

# 載入函式庫
from sklearn.datasets import make_classification

# 生成特徵矩陣和目標向量
features, target = make_classification(
    n_samples=100,     # 樣本數量
    n_features=3,      # 特徵數量
    n_informative=3,   # 有資訊量的特徵數
    n_redundant=0,     # 冗餘特徵數
    n_classes=2,       # 類別數量
    weights=[.25, .75], # 各類別權重
    random_state=1     # 隨機種子
)

# 檢視特徵矩陣和目標向量
print('特徵矩陣\n', features[:3])
print('目標向量\n', target[:3])

這段程式碼使用make_classification生成一個二分類別料集。重要的引數包括n_classes=2指定了兩個類別,而weights=[.25, .75]設定了類別不平衡,其中25%的樣本屬於第一類別75%屬於第二類別這種不平衡類別的設定對於測試分類別在處理不平衡資料時的表現特別有用。

生成聚類別題的模擬資料

對於聚類別演算法,make_blobs函式能夠生成具有明顯群聚結構的資料:

# 載入函式庫
from sklearn.datasets import make_blobs

# 生成特徵矩陣和目標向量
features, target = make_blobs(
    n_samples=100,    # 樣本數量
    n_features=2,     # 特徵數量
    centers=3,        # 中心點(群集)數量
    cluster_std=0.5,  # 群集標準差
    shuffle=True,     # 是否打亂資料
    random_state=1    # 隨機種子
)

# 檢視特徵矩陣和目標向量
print('特徵矩陣\n', features[:3])
print('目標向量\n', target[:3])

make_blobs函式建立了具有明顯分離群集的資料點。這裡我設定了3個群集中心(centers=3),每個群集的標準差為0.5(cluster_std=0.5),這會讓群集相對緊湊。較小的標準差值會產生更加分離的群集,而較大的值則會讓群集更加重疊,增加聚類別度。

使用matplotlib可以直觀地檢視生成的聚類別料:

# 載入視覺化函式庫
import matplotlib.pyplot as plt

# 繪製散點圖
plt.scatter(features[:,0], features[:,1], c=target)
plt.title('使用make_blobs生成的聚類別料')
plt.xlabel('特徵1')
plt.ylabel('特徵2')
plt.show()

從CSV檔案載入真實資料

雖然模擬資料對學習和測試演算法很有用,但在實際應用中,我們通常需要處理來自CSV、Excel或資料函式庫實資料。以下介紹如何使用pandas載入各種格式的資料。

CSV(逗號分隔值)檔案是資料科學中最常見的資料格式之一。pandas提供了強大的read_csv函式來處理各種CSV格式:

# 載入函式庫
import pandas as pd

# 建立URL
url = 'https://tinyurl.com/simulated_data'

# 載入資料集
dataframe = pd.read_csv(url)

# 檢視前兩行
print(dataframe.head(2))

這段程式碼使用pandas的read_csv函式從URL載入CSV檔案。pd.read_csv()是處理CSV檔案最常用的方法,它能夠自動識別CSV的基本結構,預設情況下會將第一行視為標題行,並使用逗號作為分隔符號。

read_csv函式有超過30個引數,可以處理各種格式的CSV檔案。例如:

  • sep引數可以指定不同的分隔符號,如sep='\t'用於分隔符號為tab的檔案
  • header引數指定標題行的位置,如果沒有標題行,可以設定header=None
  • encoding引數處理不同編碼的檔案,如encoding='utf-8'encoding='big5'(處理繁體中檔案案時常用)

在載入資料之前,建議先檢視一下檔案的內容結構,這有助於確定需要設定哪些引數。

從Excel檔案匯入資料的技巧

Excel檔案在商業環境中非常普遍,pandas提供了read_excel函式來處理Excel檔案:

# 載入函式庫
import pandas as pd

# 建立URL
url = 'https://tinyurl.com/simulated_excel'

# 載入資料
dataframe = pd.read_excel(url, sheet_name=0, header=1)

# 檢視前兩行
print(dataframe.head(2))

pd.read_excel()函式用於載入Excel檔案,與read_csv類別,但多了一些處理Excel特有格式的引數。其中最重要的是sheet_name引數,用於指定要載入的工作表。sheet_name可以是整數(表示工作表的索引,從0開始)或字串(表示工作表的名稱)。

如果需要載入多個工作表,可以傳遞一個列表,例如:sheet_name=[0, 1, 2, "銷售資料"]會回傳一個字典,包含第一、第二、第三個工作表和名為"銷售資料"的工作表。

header=1引數表示將第二行(索引為1)視為標題行,這在處理那些第一行是標題或說明的Excel檔案時特別有用。

處理JSON格式資料

JSON(JavaScript Object Notation)是一種輕量級的資料交換格式,特別適合網路應用和API。pandas提供了read_json函式來處理JSON檔案:

# 載入函式庫
import pandas as pd

# 建立URL
url = 'https://tinyurl.com/simulated_json'

# 載入資料
dataframe = pd.read_json(url, orient='columns')

# 檢視前兩行
print(dataframe.head(2))

pd.read_json()函式用於將JSON檔案轉換為pandas DataFrame。關鍵引數是orient,它指定了JSON資料的結構方式。

JSON檔案可以有多種不同的結構,orient引數可以接受以下幾種值:

  • 'split':JSON的格式為{"index": [index], "columns": [columns], "data": [values]}
  • 'records':JSON的格式為[{column: value}, ... ]
  • 'index':JSON的格式為{index: {column: value}}
  • 'columns':JSON的格式為{column: {index: value}}
  • 'values':JSON的格式只包含值:[[value, ...], ...]

有時候需要嘗試不同的orient值才能正確載入資料。對於半結構化的JSON資料,pandas還提供了json_normalize函式,可以幫助將其轉換為結構化的DataFrame。

資料載入的進階技巧與最佳實踐

在實際的機器學習專案中,資料載入通常不僅僅是簡單的一行程式碼以下是一些進階技巧和最佳實踐:

  1. 處理大型資料集:對於無法一次載入到記憶體的大型資料集,可以使用read_csvchunksize引數分批載入:

    for chunk in pd.read_csv('large_file.csv', chunksize=10000):
        # 處理每個資料區塊
        process_data(chunk)
    
  2. *資料類別最佳化:指定資料類別可以大幅提高記憶體效率:

    dtypes = {'id': 'int32', 'value': 'float32', 'category': 'category'}
    df = pd.read_csv('data.csv', dtype=dtypes)
    

3

SQL資料函式庫與Pandas資料處理技術

在現代資料科學與機器學習專案中,資料取得和預處理佔據了工作流程中的重要部分。無論是從資料函式庫資料,還是對資料進行整理和轉換,選擇正確的工具和方法都能大幅提高工作效率。今天,我將分享如何從SQL資料函式庫資料,並使用Python的Pandas函式庫資料整理和分析的實用技巧。

從SQL資料函式庫資料的關鍵技術

SQL是與資料函式庫的通用語言,在實際工作中,從資料函式庫資料可能是最常見的資料取得方式。Pandas提供了簡潔的方法來執行SQL查詢並將結果直接載入DataFrame。

以下是一個基本的SQL查詢範例:

# 載入必要的函式庫import pandas as pd
from sqlalchemy import create_engine

# 建立資料函式庫
database_connection = create_engine('sqlite:///sample.db')

# 執行SQL查詢並載入資料
dataframe = pd.read_sql_query('SELECT * FROM data', database_connection)

# 檢視前兩行資料
dataframe.head(2)

這段程式碼展示了從SQLite資料函式庫資料的基本流程:

  1. 首先使用SQLAlchemy的create_engine建立與資料函式庫接
  2. 接著使用Pandas的read_sql_query方法執行SQL查詢
  3. 查詢結果自動轉換為DataFrame格式
  4. 最後使用head(2)方法顯示前兩行資料,以便快速檢查結果

在實際專案中,這種方法極為常用,因為SQL能夠在資料提取階段就進行初步的資料過濾和轉換,減輕後續處理的負擔。例如,我們可以在SQL查詢中加入WHERE子句進行資料篩選,或使用JOIN連線多個資料表。

資料整理的核心:DataFrame的建立與操作

資料整理(Data Wrangling)是將原始資料轉換為可用於分析的格式的過程。在Python中,Pandas的DataFrame是處理表格式資料的最佳工具,它類別於電子試算表,具有行、列和索引。

建立DataFrame的多種方法

從頭開始建立DataFrame有多種方式,以下是一種常見的方法:

# 載入函式庫
import pandas as pd

# 建立空的DataFrame
dataframe = pd.DataFrame()

# 增加列
dataframe['Name'] = ['Jacky Jackson', 'Steven Stevenson']
dataframe['Age'] = [38, 25]
dataframe['Driver'] = [True, False]

# 顯示DataFrame
dataframe

我們也可以向現有的DataFrame增加新行:

# 建立新行
new_person = pd.Series(['Molly Mooney', 40, True], index=['Name','Age','Driver'])

# 增加行
dataframe.append(new_person, ignore_index=True)

第一個程式碼區塊展示瞭如何從零開始建立DataFrame:

  1. 首先建立一個空的DataFrame物件
  2. 然後逐一增加各列資料,每列包含一個列表的值
  3. 最終生成一個包含3列2行的DataFrame

第二個區塊展示瞭如何增加新行:

  1. 使用pd.Series建立一個新的行資料,並定義索引與現有DataFrame的列名對應
  2. 使用append方法將新行增加到DataFrame底部
  3. 引數ignore_index=True確保索引重新編號

在實際工作中,這種從頭建立DataFrame的方法並不常見。通常我們會從檔案、資料函式庫PI載入資料,然後進行操作。但瞭解這些基本操作有助於理解DataFrame的結構。

深入瞭解資料:探索性分析技巧

載入資料後,下一步通常是進行初步的探索性分析,瞭解資料的結構和特性。

資料預覽與摘要統計

# 載入資料
url = 'https://tinyurl.com/titanic-csv'
dataframe = pd.read_csv(url)

# 顯示前兩行
dataframe.head(2)

# 顯示資料維度
dataframe.shape

# 顯示統計摘要
dataframe.describe()

這段程式碼展示了資料探索的基本步驟:

  1. 使用head()方法檢視資料的前幾行,瞭解資料的基本結構
  2. 使用shape屬性檢視資料的維度(行數和列數)
  3. 使用describe()方法生成數值列的統計摘要,包括計數、平均值、標準差、最小值、最大值和分位數

在處理泰坦尼克號乘客資料時,我注意到describe()方法將Survived和SexCode列視為數值列,但實際上它們是型別特徵,其中的數字代表不同類別。這提醒我們,單純依賴統計摘要可能無法完全理解資料的本質,還需要結合領域知識進行解讀。

靈活導航DataFrame:選取與切片技術

在資料分析中,我們經常需要選取特定的行、列或數值。Pandas提供了多種選取資料的方法,最常用的是loc和iloc。

根據位置的選取

# 選取第一行
dataframe.iloc[0]

# 選取第2-4行
dataframe.iloc[1:4]

# 選取到第4行為止的所有行
dataframe.iloc[:4]

根據標籤的選取

# 設定索引
dataframe = dataframe.set_index(dataframe['Name'])

# 顯示特定行
dataframe.loc['Allen, Miss Elisabeth Walton']

這些程式碼範例展示了選取DataFrame資料的不同方式:

  1. iloc是根據位置的索引,類別Python的列表索引,從0開始

    • iloc[0]選取第一行
    • iloc[1:4]選取第二到第四行(不包括第五行)
    • iloc[:4]選取前四行
  2. loc是根據標籤的索引,使用行索引的實際值

    • 首先使用set_index將’Name’列設為索引
    • 然後使用loc[‘Allen, Miss Elisabeth Walton’]選取特定乘客的資料

實際工作中,根據需求靈活使用這兩種方法可以大大提高資料處理的效率。例如,當資料有意義的唯一識別符號時,使用loc更直觀;而在需要迴圈處理連續多行時,iloc更為方便。

從實戰角度看資料整理的重要性

在分析泰坦尼克號乘客資料時,我發現資料整理是一個不可或缺的步驟。例如,資料中同時存在Sex和SexCode兩列,它們包含相同的資訊但格式不同。在進行機器學習建模前,我們需要決定保留哪一列,避免特徵冗餘。

資料整理不僅僅是技術問題,更是一個需要領域知識和分析目標指導的過程。例如,在處理年齡資料時,我們需要考慮是否將其分組,如何處理缺失值,以及年齡與生存率之間可能存在的非線性關係。

高效資料處理的最佳實踐

根據我在多個資料科學專案中的經驗,以下是一些處理資料函式庫和資料整理的最佳實踐:

  1. 在資料函式庫進行初步過濾:當處理大型資料集時,盡量在SQL查詢中完成初步的資料過濾,而不是將所有資料載入記憶體後再處理。

  2. 瞭解資料結構:在進行任何操作前,使用head()、shape和describe()等方法瞭解資料的基本結構和特性。

  3. 保留原始資料:在進行資料轉換時,保留原始資料的副本,以便在需要時回溯。

  4. 記錄資料轉換步驟:使用註解或Markdown單元格記錄資料轉換的各個步驟,提高程式碼的可讀性和可重現性。

  5. 批次處理大型資料:對於無法一次載入記憶體的超大型資料集,考慮使用批次處理或流處理方法。

資料科學工作中,我們經常花費大量時間在資料取得和整理上。掌握SQL和Pandas等工具,能夠顯著提高這些步驟的效率,讓我們有更多時間專注於模型建立和結果分析。

資料整理與機器學習流程的整合

在完整的機器學習工作流程中,資料整理只是第一步,但它對後續步驟的影響至關重要。經過良好整理的資料可以:

  • 提高模型訓練的效率
  • 減少特徵工程的複雜性
  • 增強模型的可解釋性
  • 降低過擬合的風險

在泰坦尼克號生存預測模型中,經過適當的資料整理,我們可以清晰地看到乘客類別、性別和年齡如何影響生存機率,並將這些洞見轉化為有效的預測模型。

資料函式庫和資料整理是資料科學工作的基礎,熟練掌握這些技能,將使我們在資料驅動決策的時代中佔據優勢。透過持續學習和實踐,我們可以不斷提升自己處理各類別料挑戰的能力,為更複雜的分析和模型建立奠定堅實基礎。

資料科學之路漫長而充滿挑戰,但每一次成功的資料處理都會增強我們的技能和信心。希望本文分享的技巧能為你的資料科學旅程提供幫助,讓資料整理成為你強大的技術武器,而非繁瑣的障礙。

Pandas資料操作的關鍵技巧

在資料分析過程中,資料清理和預處理往往佔據了分析師80%的時間。Pandas作為Python資料處理的核心工具,提供了豐富與靈活的功能來處理各種資料操作需求。在這篇文章中,我將分享多個實用的技巧,幫助你提升資料處理的效率和品質。

理解loc與iloc的資料選擇差異

在使用Pandas時,理解loc和iloc的區別至關重要。這兩種方法雖然看似相似,但背後的邏輯完全不同:

  • loc - 根據標籤(label)進行選擇,根據索引名稱
  • iloc - 根據位置(position)進行選擇,根據數字位置

當我在處理複雜資料集時,發現這個區別尤為重要。例如,iloc[0]會回傳第一行資料,無論該行的索引標籤是什麼。相比之下,loc['index_name']則會選擇特定索引名稱的資料。

在日常資料清理工作中,熟練掌握這兩種方法能夠讓我們更加靈活地處理各種資料選擇需求。特別是當資料框的索引被重置或自定義時,瞭解這兩種方法的區別能避免許多常見錯誤。

根據條件篩選資料列

資料分析中最常見的操作之一就是根據特定條件選擇資料。Pandas提供了簡潔而強大的語法來實作這一點。

單條件篩選

假設我們要分析鐵達尼號的乘客資料,只想檢視女性乘客的資訊:

# 載入必要函式庫import pandas as pd

# 建立URL連結
url = 'https://tinyurl.com/titanic-csv'

# 載入資料
dataframe = pd.read_csv(url)

# 顯示前兩行性別為女性的資料
dataframe[dataframe['Sex'] == 'female'].head(2)

這段程式碼首先載入鐵達尼號的乘客資料集,然後使用條件表示式dataframe['Sex'] == 'female'建立一個布林遮罩(boolean mask),該遮罩會標記所有性別為女性的行。將這個遮罩套用到原始資料框上,就能篩選出所有符合條件的資料列。最後使用.head(2)只顯示前兩筆結果。

這種布林索引(boolean indexing)是Pandas最強大的功能之一,寫法直觀與執行效率高。

多條件篩選

當我們需要更複雜的篩選條件時,可以結合多個條件。例如,篩選出所有65歲或以上的女性乘客:

# 篩選資料列
dataframe[(dataframe['Sex'] == 'female') & (dataframe['Age'] >= 65)]

在這個例子中,我使用了兩個條件:性別為女性(dataframe['Sex'] == 'female')以及年齡大於等於65(dataframe['Age'] >= 65)。使用&運算元將兩個條件結合起來,表示兩個條件都必須滿足。注意每個條件都需要用括號括起來,這是為了確保運算順序正確。

在實務上,我常常需要處理更加複雜的條件組合。例如,使用|(或)運算元、~(非)運算元來構建更複雜的篩選邏輯。掌握這些操作能夠讓我們精確地定位到需要的資料子集。

替換資料值

在資料清理過程中,替換特定值是常見的需求。Pandas提供了replace方法來實作這一功能。

單一值替換

我們可以輕鬆地將特定欄位中的某個值替換成另一個值:

# 載入必要函式庫import pandas as pd

# 建立URL連結
url = 'https://tinyurl.com/titanic-csv'

# 載入資料
dataframe = pd.read_csv(url)

# 替換值,顯示前兩行
dataframe['Sex'].replace("female", "Woman").head(2)

這段程式碼將’Sex’欄位中所有的"female"替換為"Woman"。replace方法會回傳一個新的Series,而不會修改原始資料,除非我們將結果重新指定給原始欄位。這是Pandas中許多操作的共同特性 - 它們通常不會直接修改原始資料,而是回傳一個新的資料結構。

多值替換

如果需要同時替換多個值,可以傳入兩個列表:

# 將"female"和"male"分別替換為"Woman"和"Man"
dataframe['Sex'].replace(["female", "male"], ["Woman", "Man"]).head(5)

在這個例子中,第一個列表["female", "male"]包含我們要替換的原始值,第二個列表["Woman", "Man"]包含對應的新值。Pandas會按照列表中值的順序進行一一對應替換。這種批次替換的方式比逐個呼叫replace更高效。

整個資料框的替換

replace方法不僅可以應用於單個欄位,還可以應用於整個資料框:

# 替換值,顯示前兩行
dataframe.replace(1, "One").head(2)

這段程式碼會搜尋整個資料框中所有等於1的值,並將它們替換為字串"One"。這包括數值型欄位中的1以及可能存在的字串型"1"。這種全域替換在處理編碼值(如1代表"是",0代表"否")時非常有用。

使用正規表示式替換

replace還支援使用正規表示式進行更靈活的替換:

# 替換值,顯示前兩行
dataframe.replace(r"1st", "First", regex=True).head(2)

透過設定regex=True引數,我們可以使用正規表示式進行模式比對和替換。在這個例子中,所有包含"1st"的字串都會被替換為"First"。正規表示式替換在處理非結構化文字或需要複雜模式比對時特別有用。

在我的資料清理工作中,正規表示式替換是一個強大的工具,尤其是在處理格式不一致的文字資料時。例如,可以用它來標準化各種不同形式的日期格式或清理雜亂的文字資料。

重新命名資料欄位

清晰與一致的欄位命名對資料分析至關重要。Pandas提供了簡便的方法來重新命名資料框的欄位。

重新命名單個欄位

# 載入必要函式庫
import pandas as pd

# 建立URL連結
url = 'https://tinyurl.com/titanic-csv'

# 載入資料
dataframe = pd.read_csv(url)

# 重新命名欄位,顯示前兩行
dataframe.rename(columns={'PClass': 'Passenger Class'}).head(2)

rename方法接受一個字典作為columns引數,其中字典的鍵是原始欄位名,值是新的欄位名。這種方法的優點是隻修改指定的欄位,其他欄位名保持不變。

重新命名多個欄位

使用相同的方法,我們可以同時重新命名多個欄位:

# 重新命名多個欄位,顯示前兩行
dataframe.rename(columns={'PClass': 'Passenger Class', 'Sex': 'Gender'}).head(2)

只需在字典中加入更多的鍵值對,就可以一次性重新命名多個欄位。這比連續呼叫多次rename方法更加高效。與許多Pandas方法一樣,rename預設不會修改原始資料框,而是回傳一個新的資料框。如果要修改原始資料框,可以設定inplace=True引數。

建立欄位名稱字典的技巧

在處理大量欄位時,手動建立重新命名字典可能很繁瑣。這裡有一個技巧可以快速建立包含所有欄位的字典:

# 載入必要函式庫
import collections

# 建立字典
column_names = collections.defaultdict(str)

# 建立鍵
for name in dataframe.columns:
    column_names[name]

# 顯示字典
column_names

這段程式碼使用Python的collections.defaultdict建立一個特殊的字典,當存取不存在的鍵時,會自動建立該鍵並賦予預設值(在這裡是空字串)。透過遍歷資料框的所有欄位名稱,我們可以自動構建一個包含所有欄位的字典,然後可以根據需要填入新的欄位名。

在處理有數十甚至上百個欄位的大型資料集時,這個技巧特別有用。我可以先生成這個字典,然後只修改需要更改的欄位名稱,最後將完整字典傳遞給rename方法。

計算基本統計量

在探索性資料分析(EDA)階段,瞭解資料的基本統計特性是必不可少的。Pandas提供了簡單的方法來計算各種統計量。

計算常見統計量

# 載入必要函式庫
import pandas as pd

# 建立URL連結
url = 'https://tinyurl.com/titanic-csv'

# 載入資料
dataframe = pd.read_csv(url)

# 計算統計量
print('最大值:', dataframe['Age'].max())
print('最小值:', dataframe['Age'].min())
print('平均值:', dataframe['Age'].mean())
print('總和:', dataframe['Age'].sum())
print('計數:', dataframe['Age'].count())

這段程式碼計算了’Age’欄位的多個基本統計量:最大值、最小值、平均值、總和與非缺失值的計數。這些方法都是Pandas內建的,使用起來非常直觀。

除了範例中使用的統計方法外,Pandas還提供了許多其他統計方法,例如:

  • var() - 計算方差
  • std() - 計算標準差
  • kurt() - 計算峰度
  • skew() - 計算偏度
  • sem() - 計算平均值的標準誤
  • mode() - 計算眾數
  • median() - 計算中位數

整個資料框的統計量

這些統計方法不僅可以應用於單個欄位,還可以應用於整個資料框:

# 顯示計數
dataframe.count()

當應用於整個資料框時,count()方法會回傳每個欄位的非缺失值計數。這可以快速幫助我們瞭解資料集中每個欄位的完整性,識別出缺失值較多的欄位。

在實際分析中,我常常使用describe()方法來一次性取得多個數值型欄位的統計摘要,包括計數、平均值、標準差、最小值、四分位數和最大值。這提供了資料分佈的全面檢視。

查詢唯一值

在處理分類別料時,瞭解每個類別的唯一值及其分佈是非常重要的。

取得唯一值列表

# 載入必要函式庫
import pandas as pd

# 建立URL連結
url = 'https://tinyurl.com/titanic-csv'

# 載入資料
dataframe = pd.read_csv(url)

# 選擇唯一值
dataframe['Sex'].unique()

unique()方法回傳一個包含所有唯一值的NumPy陣列。這對於瞭解分類別數的可能取值非常有用。在這個例子中,‘Sex’欄位只有兩個唯一值:‘female’和’male’。

計算唯一值出現次數

# 顯示計數
dataframe['Sex'].value_counts()

value_counts()方法不僅回傳唯一值,還會計算每個值出現的次數,並按降序排列。這是探索分類別數分佈的強大工具。從結果可以看出,鐵達尼號資料集中男性乘客(851人)明顯多於女性乘

Pandas 高效資料處理:從基礎操作到進階技巧

在資料分析的世界中,資料前處理往往佔據了分析師80%的時間。無論你是資料科學家、分析師,還是機器學習工程師,掌握高效的資料整理技巧都至關重要。今天我將分享一些在實務上非常實用的 Pandas 資料處理技巧,這些方法能讓你的資料前處理工作更加順暢。

優雅地刪除DataFrame中的欄位

刪除欄位是日常資料處理中最常見的操作之一。在 Pandas 中,最佳的方式是使用 drop() 方法並指定 axis=1 引數(表示操作欄位軸):

# 載入必要的函式庫
import pandas as pd

# 建立資料來源的URL
url = 'https://tinyurl.com/titanic-csv'

# 載入資料
dataframe = pd.read_csv(url)

# 刪除特定欄位
dataframe.drop('Age', axis=1).head(2)

這段程式碼展示瞭如何刪除 DataFrame 中的單一欄位。drop() 方法接收欄位名稱作為第一個引數,而 axis=1 指定我們要操作的是欄位(而非列)。.head(2) 則顯示處理後的前兩筆資料,讓我們可以快速確認結果。注意這個操作並不會修改原始的 DataFrame,而是回傳一個新的 DataFrame。

若要一次刪除多個欄位,可以傳入欄位名稱的列表:

# 同時刪除多個欄位
dataframe.drop(['Age', 'Sex'], axis=1).head(2)

有時候我們會遇到欄位沒有名稱的情況,此時可以透過索引位置來刪除:

# 透過索引位置刪除欄位
dataframe.drop(dataframe.columns[1], axis=1).head(2)

不可變資料框的處理哲學

在我處理複雜資料專案時,發現了一個非常有價值的習慣:避免使用 Pandas 中的 inplace=True 引數。雖然這個引數允許直接修改原始 DataFrame,但這種做法在複雜的資料處理流程中可能會導致難以追蹤的問題。

我建議將 DataFrame 視為不可變物件,每次操作都產生一個新的 DataFrame:

# 建立新的 DataFrame 而非修改原始資料
dataframe_name_dropped = dataframe.drop(dataframe.columns[0], axis=1)

這種方法會建立一個名為 dataframe_name_dropped 的新 DataFrame,而不是直接修改 dataframe。這樣的處理方式雖然看起來需要更多程式碼,但在複雜的資料處理流程中,能讓每個步驟的輸入和輸出更加清晰,大幅降低除錯難度,也讓程式碼更容易維護和擴充套件。

有效刪除 DataFrame 中的列

刪除列(資料列)的最佳方式通常不是使用 drop(),而是透過布林條件篩選:

# 刪除性別為男性的列
dataframe[dataframe['Sex'] != 'male'].head(2)

這行程式碼建立了一個新的 DataFrame,只包含性別不是男性的資料列。dataframe['Sex'] != 'male' 會產生一個布林陣列,每個元素對應一列資料是否符合條件。將此布林陣列放在 DataFrame 的索引位置,等同於過濾出所有符合條件的列。

這種方法的強大之處在於它的靈活性。我們可以用它來刪除特定的單一列:

# 刪除特定名稱的列
dataframe[dataframe['Name'] != 'Allison, Miss Helen Loraine'].head(2)

甚至可以用來刪除特定索引的列:

# 刪除索引為0的列
dataframe[dataframe.index != 0].head(2)

處理重複資料列

重複資料可能會嚴重影響分析結果。Pandas 提供了 drop_duplicates() 方法來處理這個問題:

# 刪除完全重複的列
dataframe.drop_duplicates().head(2)

預設情況下,drop_duplicates() 會刪除所有欄位值完全相同的列。在實際應用中,常常只需要考慮特定欄位是否重複,這時可以使用 subset 引數:

# 只考慮 'Sex' 欄位的重複情況
dataframe.drop_duplicates(subset=['Sex'])

這段程式碼會保留每種性別的第一筆資料,刪除其餘相同性別的資料。如果想保留最後一筆而非第一筆,可以使用 keep 引數:

# 保留每種性別的最後一筆資料
dataframe.drop_duplicates(subset=['Sex'], keep='last')

Pandas 還提供 duplicated() 方法,它會回傳一個布林序列,標示每一列是否為重複資料。這在需要更複雜的重複資料處理邏輯時非常有用。

資料分組的強大功能

分組操作可能是 Pandas 中最強大的功能之一。它允許我們根據某些分享值將資料分組,然後對每個組別進行計算:

# 根據性別分組並計算平均值
dataframe.groupby('Sex').mean()

這段程式碼首先根據 ‘Sex’ 欄位的值將資料分成兩組(男性和女性),然後計算每組中所有數值欄位的平均值。輸出結果會顯示每個性別的平均年齡、存活率等統計資料。

初次接觸 groupby() 的人可能會對以下程式碼感到困惑:

# 僅分組但不進行操作
dataframe.groupby('Sex')

這樣的程式碼會回傳一個 GroupBy 物件,而非具體的結果。這是因為 groupby() 需要配合某種操作(如計算平均值、總和、計數等)才能產生有意義的結果。

例如,計算存活和未存活的乘客數量:

# 分組並計數
dataframe.groupby('Survived')['Name'].count()

這行程式碼先根據 ‘Survived’ 欄位將資料分組(0表示未存活,1表示存活),然後計算每組中 ‘Name’ 欄位的非空值數量(即乘客數量)。注意我們在 groupby() 後指定了 ['Name'],這是因為特定的統計操作只對某些資料類別有意義。

我們還可以進行多層分組:

# 根據性別和存活狀態分組,計算平均年齡
dataframe.groupby(['Sex', 'Survived'])['Age'].mean()

這段程式碼首先按性別分組,然後在每個性別組內再按存活狀態分組,最後計算每個子組的平均年齡。這種多層分組分析能揭示更複雜的資料模式,例如這裡可以看出在泰坦尼克號災難中,存活的男性平均年齡比未存活的男性年輕,而女性則相反。

資料處理的哲學思考

在處理大型資料集時,我發現效率和可讀性之間的平衡至關重要。雖然 Pandas 提供了 inplace=True 這樣的引數來直接修改原始資料,但這種做法在複雜專案中可能會導致難以追蹤的問題。

我的建議是:

  1. 將 DataFrame 視為不可變物件,每次操作產生新的 DataFrame
  2. 使用有意義的變數名稱,清楚表達每個 DataFrame 的用途
  3. 利用方法鏈(method chaining)來提高程式碼的可讀性
  4. 在處理大型資料集時,考慮使用 inplace=True 以節省記憶體,但要確保操作的獨立性

在資料清理過程中,我們常常需要做出取捨。例如,是否刪除含有缺失值的列?是否填補缺失值?這些決策應該根據對資料本質的理解,而非機械式地套用規則。

資料分組的進階應用

資料分組不僅限於計算簡單的統計量。我們可以對每個組別應用自定義函式:

# 自定義函式運算每組的存活率百分比
def survival_percentage(group):
    return group['Survived'].mean() * 100

# 應用到分組
dataframe.groupby('Sex').apply(survival_percentage)

還可以進行更複雜的操作,如分組後進行多種計算:

# 分組後計算多種統計量
dataframe.groupby('PClass').agg({
    'Age': ['mean', 'median', 'std'],
    'Survived': 'mean'
})

這段程式碼根據乘客等級(PClass)分組,然後對每組計算年齡的平均值、中位數和標準差,以及存活率。agg() 方法允許我們為不同欄位指定不同的聚合函式,這在需要複雜統計分析時非常有用。

資料處理的效能考量

在處理大型資料集時,效能是一個重要的考量因素。以下是一些提升 Pandas 操作效能的技巧:

  1. 使用適當的資料類別:例如,將類別變數設為 category 類別可以節省記憶體
  2. 避免迴圈:盡量使用向量化操作而非迴圈
  3. 使用 inplace=True 謹慎:雖然前面不建議使用,但在記憶體有限的情況下,這可能是必要的
  4. 分批處理:對於超大型資料集,考慮使用 read_csvchunksize 引數分批讀取處理

在資料科學專案中,我常發現初期投入更多時間在資料清理和前處理上,能大幅降低後續分析和建模階段的複雜度。一個經過良好處理的資料集,往往能讓分析工作事半功倍。

資料處理不僅是技術問題,更是一門藝術。它需要分析師對資料有深入的理解,能夠做出適當的取捨,並在確保資料完整性的同時提高處理效率。掌握了這些 Pandas 技巧,相信你能在資料分析的道路上走得更遠、更順暢。

時間序列資料的有效處理:resample 技巧

時間序列資料處理一直是資料分析中的重要環節。在處理大量時間戳記錄時,我們經常需要按照特定的時間區間來分組資料。Pandas 提供的 resample 函式正是解決此類別題的利器。

按時間區間分組資料

想像我們有一個包含銷售記錄的資料集,每條記錄都有一個時間戳和銷售金額。如何按週、月等時間單位來統計這些資料?

# 載入必要的函式庫
import pandas as pd
import numpy as np

# 建立日期範圍作為索引
time_index = pd.date_range('06/06/2017', periods=100000, freq='30S')

# 建立 DataFrame
dataframe = pd.DataFrame(index=time_index)

# 建立隨機銷售金額欄位
dataframe['Sale_Amount'] = np.random.randint(1, 10, 100000)

# 按週分組並計算每週總和
weekly_sum = dataframe.resample('W').sum()

這段程式碼示範瞭如何使用 resample 函式對時間序列資料進行分組。首先建立了一個包含 100,000 筆資料的 DataFrame,索引是從 2017 年 6 月 6 日開始,每 30 秒一筆的時間戳。每筆記錄都有一個 1 到 9 之間的隨機銷售金額。

resample('W') 指令會按週將資料分組,.sum() 則計算每週的總和。這裡的 ‘W’ 是週期別名 (frequency alias),代表按週分組。

靈活控制時間分組

resample 函式的強大之處在於它的靈活性。我們可以按照各種時間單位來分組資料:

# 按兩週分組,計算平均值
biweekly_mean = dataframe.resample('2W').mean()

# 按月分組,計算記錄數
monthly_count = dataframe.resample('M').count()

這裡展示了兩種不同的時間分組方式。'2W' 表示每兩週分組一次,而 'M' 表示按月分組。除了使用不同的時間單位外,我們還可以應用不同的聚合函式:.mean() 計算平均值,.count() 計算每組中的記錄數量。

在處理時序資料時,有時我們可能會注意到分組後的索引標籤看起來有些奇怪。這是因為 resample 預設使用時間區間的右邊界(最後一個標籤)作為標籤。我們可以透過 label 引數來調整這個行為:

# 按月分組,使用左邊界作為標籤
left_labeled = dataframe.resample('M', label='left').count()

這個例子改變了標籤的位置,使用 label='left' 引數讓 resample 使用時間區間的左邊界(第一個標籤)作為標籤。這在視覺化或報表呈現時可能會影響到讀者的理解,因此根據需求靈活調整是很重要的。

在實際專案中,我發現時間序列分析是許多商業智慧應用的基礎。例如,銷售趨勢分析、網站流量監控、IoT 感測器資料處理等,都需要高效的時間序列分組功能。

欄位迭代與函式應用技巧

迭代處理欄位元素

有時我們需要對 DataFrame 中的某一欄進行逐元素處理。雖然 Pandas 提供了向量化操作,但某些情況下迭代處理仍然是必要的。

# 載入資料
import pandas as pd
url = 'https://tinyurl.com/titanic-csv'
dataframe = pd.read_csv(url)

# 使用迴圈處理前兩個名字
for name in dataframe['Name'][0:2]:
    print(name.upper())

這段程式碼示範瞭如何使用 Python 的標準 for 迴圈來迭代 DataFrame 中 ‘Name’ 欄位的前兩個元素。每次迭代時,我們將名字轉換為大寫並列印出來。

除了使用 for 迴圈外,我們也可以利用 Python 的列表推導式 (list comprehension) 來實作相同功能:

# 使用列表推導式處理前兩個名字
[name.upper() for name in dataframe['Name'][0:2]]

列表推導式是 Python 中一種簡潔有力的語法,允許我們在一行程式碼中建立新的列表。這裡我們對 ‘Name’ 欄位的前兩個元素應用 upper() 函式,將結果收集為一個新的列表。

使用 apply 函式進行欄位操作

雖然迭代和列表推導式很方便,但在 Pandas 中,更為推薦的方式是使用 apply 函式,它通常比迭代更高效與更符合 Pandas 的設計哲學。

# 定義一個函式
def uppercase(x):
    return x.upper()

# 使用 apply 應用函式
uppercase_names = dataframe['Name'].apply(uppercase)[0:2]

apply 函式是 Pandas 中一個強大的工具,它允許我們將自定義函式應用到 Series 或 DataFrame 的每個元素上。在這個例子中,我們定義了一個將文字轉為大寫的函式 uppercase,然後使用 apply 將這個函式應用到 ‘Name’ 欄位的每個元素上。

在實際開發中,我經常使用 apply 來進行資料清洗和轉換。它的靈活性讓我們可以執行複雜的操作,如分離名字和姓氏、將字串轉換為數值、標準化資料格式等。

對分組資料應用函式

結合 groupbyapply,我們可以對分組後的資料應用自定義函式,這在進行複雜的分組分析時非常有用。

# 按性別分組,然後計算每組的計數
group_counts = dataframe.groupby('Sex').apply(lambda x: x.count())

這段程式碼首先按 ‘Sex’ 欄位將資料分組,然後對每個組應用一個 lambda 函式來計算組內每個欄位的計數。結果是一個包含每個性別組計數統計的 DataFrame。

這種組合使用 groupbyapply 的方式特別適合處理需要按類別執行複雜計算的場景。例如,我曾在一個專案中使用這種方法來計算不同客戶群的消費模式統計,或是分析不同地區的銷售表現。

DataFrame 的連線與合併操作

在資料分析過程中,我們經常需要將多個資料來源整合在一起。Pandas 提供了多種方式來連線和合併資料框,讓我們能夠靈活處理各種資料整合需求。

使用 concat 連線資料框

concat 函式用於簡單地將多個 DataFrame 垂直或水平堆積積在一起。

import pandas as pd

# 建立第一個 DataFrame
data_a = {'id': ['1', '2', '3'],
         'first': ['Alex', 'Amy', 'Allen'],
         'last': ['Anderson', 'Ackerman', 'Ali']}
dataframe_a = pd.DataFrame(data_a, columns=['id', 'first', 'last'])

# 建立第二個 DataFrame
data_b = {'id': ['4', '5', '6'],
         'first': ['Billy', 'Brian', 'Bran'],
         'last': ['Bonder', 'Black', 'Balwner']}
dataframe_b = pd.DataFrame(data_b, columns=['id', 'first', 'last'])

# 按行連線 DataFrame (垂直堆積積)
vertical_stack = pd.concat([dataframe_a, dataframe_b], axis=0)

這段程式碼示範瞭如何使用 concat 函式垂直堆積積兩個 DataFrame。我們建立了兩個具有相同結構的 DataFrame,然後使用 concat 函式將它們連線起來。引數 axis=0 表示按行連線(垂直堆積積)。

除了垂直堆積積,我們也可以水平堆積積 DataFrame:

# 按列連線 DataFrame (水平堆積積)
horizontal_stack = pd.concat([dataframe_a, dataframe_b], axis=1)

透過將 axis 引數設為 1,我們可以水平堆積積 DataFrame,即將第二個 DataFrame 的列增加到第一個 DataFrame 的右側。這在處理需要合併多個特徵集的情況下很有用。

除了 concat,我們也可以使用 append 方法來增加新行:

# 建立新行
row = pd.Series([10, 'Chris', 'Chillon'], index=['id', 'first', 'last'])

# 增加行
extended_df = dataframe_a.append(row, ignore_index=True)

append 方法允許我們將一個 Series(作為新行)增加到 DataFrame 的底部。引數 ignore_index=True 讓 Pandas 重新生成索引,確保索引連續。

使用 merge 合併資料框

當我們需要根據某些共同欄位將多個 DataFrame 結合在一起時,merge 函式提供了類別 SQL JOIN 的功能。

# 建立員薪水料
employee_data = {'employee_id': ['1', '2', '3', '4'],
                'name': ['Amy Jones', 'Allen Keys', 'Alice Bees', 'Tim Horton']}
dataframe_employees = pd.DataFrame(employee_data, columns=['employee_id', 'name'])

# 建立銷售資料
sales_data = {'employee_id': ['3', '4', '5', '6'],
             'total_sales': [23456, 2512, 2345, 1455]}
dataframe_sales = pd.DataFrame(sales_data, columns=['employee_id', 'total_sales'])

# 內部合併 (inner join)
inner_merged = pd.merge(dataframe_employees, dataframe_sales, on='employee_id')

這個例子示範瞭如何使用 merge 函式執行內部合併 (inner join)。我們有兩個 DataFrame:一個包含員薪水訊,另一個包含銷售資訊。透過在 employee_id 欄位上合併,我們得到一個新的 DataFrame,只包含兩個資料集中都存在的員工(即同時有員薪水訊和銷售資訊的員工)。

Pandas 的 merge 函式也支援其他類別的合併:

# 外部合併 (outer join)
outer_merged = pd.merge(dataframe_employees, dataframe_sales, on='employee_id', how='outer')

# 左合併 (left join)
left_merged = pd.merge(dataframe_employees, dataframe_sales, on='employee_id', how='left')
  • 外部合併 (outer join) 將保留兩個 DataFrame 中的所有記錄,對於只在一個 DataFrame 中存在的記錄,另一個 DataFrame 的欄位將填充 NaN。
  • 左合併 (left join) 將保留左側 DataFrame(這裡是 dataframe_employees)中的所有記錄,如果某個員工在銷售資料中沒有對應的記錄,則 total_sales 欄位將填充 NaN。

我們也可以指定要合併的欄位名稱,特別是當兩個 DataFrame 中的欄位名稱不同時:

# 指定合併欄位
specific_merged = pd.merge(dataframe_employees,
                         dataframe_sales,
                         left_on='employee_id',
                         right_on='employee_id')

這個例子使用 left_onright_on 引數明確指定了合併的欄位。雖然在這個例子中兩個 DataFrame 的欄位名稱相同,但如果欄位名稱不同,這種寫法就非常有用。

在實際應用中,我經常需要合併來自不同來源的資料。例如,將客戶資訊與訂單資料合併,或是將產品資訊與函式庫態合併。merge 函式的靈活性讓這類別作變得簡單而強大。

進階應用:時間序列分析與資料

資料整合與特徵工程:開發高品質機器學習輸入

在實際的資料科學專案中,我們很少能直接獲得理想狀態的資料集。通常,資料分散在不同的檔案和資料函式庫需要我們進行整合;而即使整合完成,數值特徵的尺度差異也可能嚴重影響機器學習模型的效能。本文將探討這兩個關鍵議題:如何合併不同來源的資料,以及如何對數值特徵進行適當的轉換處理。

合併分散資料:DataFrames的整合技術

在現實世界中,我們經常需要處理來自多個來源的資料。可能是不同的資料函式庫結果、不同的API回應,或是多個CSV檔案。pandas的merge函式提供了強大的資料整合能力,讓我們能輕鬆地將這些分散資料集合併成一個完整的分析資料集。

DataFrame合併的三大關鍵要素

當我們需要合併資料時,必須明確指定三個要素:

  1. 合併的兩個DataFrame:需要明確指定要合併的兩個資料框架
  2. 合併的依據欄位:指定用於比對兩個DataFrame的欄位名稱
  3. 合併的類別:決定如何處理兩個DataFrame中的資料比對和不比對情況

讓我們透過一個例項來理解這個概念:

import pandas as pd

# 建立員薪水料DataFrame
df_employees = pd.DataFrame({
    'employee_id': [1, 2, 3, 4, 5],
    'name': ['Anna', 'Bob', 'Charles', 'Daniel', 'Eva'],
    'department': ['Sales', 'IT', 'Marketing', 'HR', 'Finance']
})

# 建立銷售資料DataFrame
df_sales = pd.DataFrame({
    'employee_id': [1, 3, 5, 6],
    'sales_amount': [10000, 5000, 15000, 8000],
    'quarter': ['Q1', 'Q1', 'Q1', 'Q1']
})

# 合併兩個DataFrame
merged_data = pd.merge(df_employees, df_sales, on='employee_id')
print(merged_data)

這段程式碼建立了兩個DataFrame:一個包含員工基本資訊,另一個包含銷售額資訊。透過pd.merge函式,我們將這兩個DataFrame依據共同的employee_id欄位進行合併。由於沒有指定合併類別,預設使用inner join,只回傳在兩個DataFrame中都存在的員工記錄(即有銷售記錄的員工)。

四種合併類別及其應用場景

pandas的merge函式支援四種主要的合併類別,每種類別適用於不同的資料整合需求:

1. Inner Join (內部連線)

只回傳在兩個DataFrame中都有比對的資料列。這是預設的合併類別。

# 內部連線
inner_join = pd.merge(df_employees, df_sales, on='employee_id', how='inner')

當我們只關心同時存在於兩個資料集的記錄時,內部連線非常有用。例如,我們只想分析有銷售記錄的員薪水料。

2. Outer Join (外部連線)

回傳兩個DataFrame中的所有資料列。如果某個資料列在其中一個DataFrame中不存在,則對應的值會填充為NaN。

# 外部連線
outer_join = pd.merge(df_employees, df_sales, on='employee_id', how='outer')

外部連線適用於需要保留所有資訊的情況,例如我們想分析所有員工的資料,無論他們是否有銷售記錄。

3. Left Join (左連線)

回傳左側DataFrame的所有資料列,但只回傳右側DataFrame中能與左側比對的資料列。

# 左連線
left_join = pd.merge(df_employees, df_sales, on='employee_id', how='left')

左連線適用於需要保留主資料集所有記錄的情況,例如我們想分析所有員工,但只關心他們的銷售資料(如果有的話)。

4. Right Join (右連線)

回傳右側DataFrame的所有資料列,但只回傳左側DataFrame中能與右側比對的資料列。

# 右連線
right_join = pd.merge(df_employees, df_sales, on='employee_id', how='right')

右連線與左連線類別,但保留的是右側DataFrame的所有記錄。例如,我們想分析所有銷售記錄,但只關心有員薪水料的銷售。

處理不同名稱的合併欄位

有時候,我們需要合併的欄位在兩個DataFrame中有不同的名稱。這種情況下,我們可以使用left_onright_on引數:

# 員薪水料有employee_id,銷售資料有emp_id
df_sales_different = pd.DataFrame({
    'emp_id': [1, 3, 5, 6],  # 注意欄位名稱不同
    'sales_amount': [10000, 5000, 15000, 8000]
})

# 使用不同名稱的欄位合併
merged_different = pd.merge(df_employees, df_sales_different, 
                           left_on='employee_id', right_on='emp_id')

這段程式碼示範瞭如何處理兩個DataFrame中合併欄位名稱不同的情況。透過left_onright_on引數,分別指定左側DataFrame的employee_id和右側DataFrame的emp_id作為合併依據。這樣,即使欄位名稱不同,只要它們包含相同的識別值,依然可以成功合併資料。

數值資料處理:讓機器學習模型發揮最佳效能

在機器學習中,特徵的尺度差異可能對模型效能產生顯著影響。例如,一個特徵的範圍是0-1,而另一個特徵的範圍是0-1000,這會導致模型在訓練時偏向影響較大的特徵。因此,適當的數值特徵處理是機器學習前處理的關鍵步驟。

特徵重新縮放:統一特徵的值域範圍

特徵重新縮放是將數值特徵轉換到特定範圍的過程,通常是0-1或-1到1之間。scikit-learn提供了MinMaxScaler類別實作這一功能:

import numpy as np
from sklearn import preprocessing

# 建立包含不同尺度值的特徵
feature = np.array([[-500.5],
                    [-100.1],
                    [0],
                    [100.1],
                    [900.9]])

# 建立縮放器,設定目標範圍為0到1
minmax_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))

# 縮放特徵
scaled_feature = minmax_scaler.fit_transform(feature)

print(scaled_feature)

這段程式碼展示瞭如何使用scikit-learn的MinMaxScaler將特徵值縮放到0-1範圍。首先建立一個包含不同尺度值的特徵陣列,然後使用MinMaxScaler將其轉換。轉換後,原來範圍在-500.5到900.9的值被對映到0到1之間,保留了原始資料的相對關係,但統一了尺度。

最小-最大縮放的數學原理

最小-最大縮放使用特徵的最小值和最大值來重新調整值的範圍。具體計算公式為:

x_scaled = (x - min(x)) / (max(x) - min(x))

其中,x是原特徵向量,x_scaled是縮放後的特徵向量。這個轉換將所有值對映到0和1之間,其中最小值對映為0,最大值對映為1。

標準化:讓特徵符合標準常態分佈

另一種常用的特徵轉換方法是標準化(Standardization),它將特徵轉換為均值為0,標準差為1的分佈。這對許多機器學習演算法非常有效,特別是那些假設特徵符合常態分佈的演算法。

import numpy as np
from sklearn import preprocessing

# 建立特徵
x = np.array([[-1000.1],
              [-200.2],
              [500.5],
              [600.6],
              [9000.9]])

# 建立標準化轉換器
scaler = preprocessing.StandardScaler()

# 轉換特徵
standardized = scaler.fit_transform(x)

print(standardized)
print("平均值:", round(standardized.mean()))
print("標準差:", standardized.std())

這段程式碼展示瞭如何使用scikit-learn的StandardScaler將特徵進行標準化。轉換後的特徵均值接近0,標準差為1。標準化後的值表示原始值與特徵平均值的差距,以標準差為單位。例如,值為2表示原始值比平均值高出2個標準差。

標準化的數學原理

標準化使用特徵的均值和標準差來轉換資料。具體計算公式為:

x_standardized = (x - mean(x)) / std(x)

其中,mean(x)是特徵的平均值,std(x)是特徵的標準差。標準化後的值通常被稱為z分數(z-score),它表示原始值距離平均值多少個標準差。

處理離群值:穩健縮放技術

當資料存在顯著離群值時,標準縮放可能受到負面影響,因為離群值會大幅影響均值和標準差。在這種情況下,根據中位數和四分位距的穩健縮放(Robust Scaling)是更好的選擇:

import numpy as np
from sklearn import preprocessing

# 建立包含離群值的特徵
x = np.array([[-1000.1],
              [-200.2],
              [500.5],
              [600.6],
              [9000.9]])  # 9000.9是明顯的離群值

# 建立穩健縮放器
robust_scaler = preprocessing.RobustScaler()

# 轉換特徵
robust_scaled = robust_scaler.fit_transform(x)

print(robust_scaled)

這段程式碼示範瞭如何使用scikit-learn的RobustScaler處理含有離群值的特徵。穩健縮放使用中位數而非均值,使用四分位距而非標準差,因此對離群值不敏感。這使得轉換後的特徵分佈更能反映大多數資料點的實際分佈情況,而不受極端值影響。

特徵歸一化:觀測值的單位向量轉換

在某些機器學習演算法中,我們關心的不是特徵的絕對值,而是特徵的相對比例。這種情況下,將觀測值轉換為單位向量(unit norm)非常有用。scikit-learn提供了Normalizer類別實作這一功能:

import numpy as np
from sklearn import preprocessing

# 建立特徵矩陣,每行是一個觀測值
features = np.array([[1.0, 2.0, 3.0],
                     [4.0, 5.0, 6.0]])

# 建立歸一化轉換器
normalizer = preprocessing.Normalizer()

# 轉換特徵
normalized = normalizer.transform(features)

print(normalized)

歸一化後,每個觀測值(行向量)的歐幾裡得範數(L2範數)變為1。這在文字分析和聚類別應用中特別有用。

選擇適當的特徵轉換方法

不同的特徵轉換方法適用於不同的情境和演算法。以下是一些選擇:

  1. Min-Max縮放:當你需要特徵在固定範圍內,特別適用於神經網路等需要輸入值在特定範圍的演算法。

  2. 標準化:當你的演算法假設特徵符合常態分佈,如主成分分析(PCA)、線性迴歸等。這是大多數情況下的首選方法。

  3. 穩健縮放:當你的資料包含顯著離群值,與你不希望這些離群值影響轉換結果時。

  4. 歸一化:當你關注的是特徵間的相對比例,而非絕對值時,例如在文字分析和某些聚類別法中。

在實際應用中,我傾向於先嘗試標準化,除非有特定

特徵正規化:提高模型穩定性的關鍵技術

特徵工程是機器學習流程中不可或缺的一環,而正規化則是特徵工程中的重要技術。在處理數值資料時,適當的正規化方法可以顯著提升模型的穩定性和預測能力。不同於常見的特徵縮放方法,觀測值正規化(Normalization)提供了另一種思路。

觀測值正規化技術

大多數縮放方法(如最小-最大縮放和標準化)是針對特徵進行操作,但我們也可以對個別觀測值進行縮放。這種技術在處理等價特徵(如文字分類別每個詞或n片語是一個特徵)時特別有用。

Scikit-learn的Normalizer類別供了三種常用的規範選項,其中歐幾裡德範數(L2範數)是預設選項:

# 載入必要的函式庫import numpy as np
from sklearn.preprocessing import Normalizer

# 建立特徵矩陣
features = np.array([[0.5, 0.5],
                     [1.1, 3.4],
                     [1.5, 20.2],
                     [1.63, 34.4],
                     [10.9, 3.3]])

# 建立正規化器並設定為L2範數
normalizer = Normalizer(norm="l2")

# 轉換特徵矩陣
features_l2_norm = normalizer.transform(features)

# 顯示結果
print(features_l2_norm)

上述程式碼使用scikit-learn的Normalizer進行L2範數正規化。L2範數計算一個觀測值的歐幾裡德距離,然後將觀測值除以這個距離,使得觀測值的向量長度為1。這裡的特徵矩陣含有5個觀測值,每個觀測值有2個特徵。轉換後,每個觀測值的L2範數都會等於1,但保持了原始資料點間的相對關係。

我們也可以使用曼哈頓範數(L1範數)進行正規化:

# 使用L1範數轉換特徵矩陣
features_l1_norm = Normalizer(norm="l1").transform(features)

# 顯示結果
print(features_l1_norm)

# 檢驗第一個觀測值的總和
print("第一個觀測值的總和:", features_l1_norm[0, 0] + features_l1_norm[0, 1])

L1範數(也稱為曼哈頓範數或計程車範數)的計算方式是將觀測值的所有特徵絕對值相加。直觀理解,L2範數可視為兩點間的直線距離(如鳥飛行的路徑),而L1範數則像人在城市街道上的行走距離(先北走一個街區,再東走一個街區等)。

使用L1範數的一個實用特性是,正規化後的觀測值數值總和為1,這在某些應用場景中非常有用,例如在處理機率分佈時。如上例所示,第一個觀測值經L1範數正規化後的兩個特徵值總和為1。

正規化的選擇考量

在實際應用中,選擇哪種正規化方法取決於你的資料特性和模型需求:

  1. 當特徵間比例差異大時,L2範數能更好地保持資料的分佈特性
  2. 當需要稀疏結果(更多為零的值)時,L1範數是更好的選擇
  3. 當處理文字分類別影像識別等高維資料時,正規化可以減少不同長度檔案或影像尺寸的影響

多項式與互動特徵的生成

在機器學習中,有時原始特徵無法充分捕捉資料的複雜性,特別是當特徵與目標變數之間存在非線性關係時。這時,生成多項式特徵和互動特徵能顯著提升模型效能。

使用PolynomialFeatures生成複合特徵

Scikit-learn提供了PolynomialFeatures類別可以輕鬆生成多項式和互動特徵:

# 載入必要的函式庫
import numpy as np
from sklearn.preprocessing import PolynomialFeatures

# 建立特徵矩陣
features = np.array([[2, 3],
                     [2, 3],
                     [2, 3]])

# 建立多項式特徵生成器(包含二階多項式和互動項)
polynomial_interaction = PolynomialFeatures(degree=2, include_bias=False)

# 生成多項式特徵
poly_features = polynomial_interaction.fit_transform(features)
print(poly_features)

這段程式碼使用PolynomialFeatures將原始特徵轉換為包含多項式和互動特徵的新特徵集。degree=2引數設定生成最高二階多項式特徵,include_bias=False則表示不包含常數項(偏置項)。

對於原始特徵 [x₁, x₂],轉換後將得到 [x₁, x₂, x₁², x₁x₂, x₂²]。在這個例子中,原始值 [2, 3] 轉換為 [2, 3, 4, 6, 9],其中4是2²,6是2×3,9是3²。

如果我們只需要互動特徵而不需要高階多項式特徵,可以設定interaction_only=True

# 僅生成互動特徵
interaction = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
interaction_features = interaction.fit_transform(features)
print(interaction_features)

設定interaction_only=True後,只會生成互動特徵,不會生成單一特徵的高階項。對於原始特徵 [x₁, x₂],轉換後得到 [x₁, x₂, x₁x₂],即 [2, 3, 6]。這在特徵間存在相互影響但不需要考慮單一特徵非線性效應時非常有用。

多項式特徵的應用場景

多項式特徵在很多實際問題中都有重要應用:

  1. 非線性關係建模:當特徵與目標之間存在非線性關係時,多項式特徵能夠捕捉這種複雜性。例如,年齡對健康風險的影響可能不是恆定的,而是隨年齡增長而加速增加。

  2. 特徵互動效應:有時特徵的效果取決於其他特徵的值。以咖啡甜度預測為例,如果我們有兩個特徵:是否攪拌和是否加糖,個別特徵可能無法預測咖啡甜度,但結合起來才能:只有當咖啡加了糖並且被攪拌時,咖啡才會甜。

  3. 物理模型近似:在工程和物理應用中,許多關係是透過多項式函式描述的,使用多項式特徵可以幫助模型學習這些關係。

然而,使用多項式特徵時需要注意避免過度擬合,特別是當degree值較大時。實踐中,通常結合正則化方法(如Ridge或Lasso迴歸)來控制模型複雜度。

自定義特徵轉換

有時標準的特徵轉換可能無法滿足特定需求,這時自定義轉換就變得非常有用。Scikit-learn和Pandas都提供了靈活的方法來實作自定義轉換。

使用FunctionTransformer進行自定義轉換

Scikit-learn的FunctionTransformer允許我們將自定義函式應用於特徵:

# 載入必要的函式庫
import numpy as np
from sklearn.preprocessing import FunctionTransformer

# 建立特徵矩陣
features = np.array([[2, 3],
                     [2, 3],
                     [2, 3]])

# 定義一個簡單的函式
def add_ten(x):
    return x + 10

# 建立轉換器
ten_transformer = FunctionTransformer(add_ten)

# 轉換特徵矩陣
transformed_features = ten_transformer.transform(features)
print(transformed_features)

這段程式碼定義了一個簡單的函式add_ten,將輸入值加10。然後使用FunctionTransformer將此函式應用於特徵矩陣。這種方法的優勢是它能夠無縫整合到Scikit-learn的管道中,使整個特徵工程和模型訓練過程更加流暢。

使用Pandas的apply方法

對於使用Pandas DataFrame的情況,我們可以使用apply方法達到同樣的效果:

# 載入必要的函式庫
import pandas as pd

# 建立DataFrame
df = pd.DataFrame(features, columns=["feature_1", "feature_2"])

# 應用函式
transformed_df = df.apply(add_ten)
print(transformed_df)

Pandas的apply方法提供了一種更直觀的方式來應用自定義函式。這對於資料探索和前處理階段特別有用,因為它保持了DataFrame的結構和列名,使結果更易於理解和進一步處理。

自定義轉換的應用場景

自定義特徵轉換在許多情況下非常有價值:

  1. 數學轉換:例如,對特徵取自然對數、平方根或指數,以處理偏斜分佈或滿足模型假設。
  2. 域知識整合:根據專業知識建立特定領域的特徵,如金融領域的風險指標或醫學領域的健康評分。
  3. 特徵組合:將多個原始特徵組合成一個更有意義的衍生特徵。
  4. 類別變數處理:建立自定義編碼方案,超越標準的獨熱編碼或標籤編碼。

雖然範例中的函式很簡單,但實際應用中可以定義任意複雜的轉換函式。唯一的要求是函式能夠接受NumPy陣列或Pandas DataFrame並回傳相應的轉換結果。

異常值檢測技術

異常值可能嚴重影響機器學習模型的效能,因此識別和處理異常值是資料前處理的重要步驟。雖然異常值檢測更像是一門藝術而非科學,但仍有一些常用技術可以幫助我們識別極端觀測值。

使用橢圓包絡進行多變數異常值檢測

一種常見方法是假設資料呈正態分佈,並根據此假設在資料周圍"繪製"一個橢圓,將橢圓內的觀測值分類別內點(標記為1),將橢圓外的觀測值分類別異常值(標記為-1):

# 載入必要的函式庫
import numpy as np
from sklearn.covariance import EllipticEnvelope
from sklearn.datasets import make_blobs

# 建立模擬資料
features, _ = make_blobs(n_samples=10,
                         n_features=2,
                         centers=1,
                         random_state=1)

# 將第一個觀測值的值替換為極端值
features[0,0] = 10000
features[0,1] = 10000

# 建立檢測器
outlier_detector = EllipticEnvelope(contamination=.1)

# 擬合檢測器
outlier_detector.fit(features)

# 預測異常值
predictions = outlier_detector.predict(features)
print(predictions)

這段程式碼使用EllipticEnvelope檢測多變數異常值。首先生成模擬資料,然後將第一個觀測值設為極端值。contamination=.1引數指定預期的異常值比例為10%。檢測器擬合後,預測結果中-1表示異常值,1表示正常觀測值。

這種方法的主要限制是需要指定contamination引數,即異常值的比例—這通常是我們不知道的值。可以將contamination視為對資料清潔度的估計。如果預期資料異常值很少,可以設定較小的值;如果認為資料很可能有異常值,則可以設定較高的值。

使用四分位距檢測單變數異常值

離群值處理的藝術:從識別到對策

在處理數值資料時,離群值常常是讓資料科學家頭痛的問題。在我多年的實務經驗中發現,沒有任何一種離群值檢測技術可以稱為「最佳」,每種方法都有其特定的優點和侷限性。理想的做法是結合多種檢測技術,例如同時使用橢圓包絡法(Elliptic Envelope)和根據四分位距(IQR)的檢測,然後綜合分析結果。

離群值的處理策略

當面對離群值時,我們通常有三種處理策略可以考慮:

1. 移除離群值

最直接的方法是完全移除包含離群值的觀測值。這種方法適用於當離群值可能是錯誤資料或與分析目標無關時。

# 載入必要函式庫
import pandas as pd

# 建立範例資料
houses = pd.DataFrame()
houses['Price'] = [534433, 392333, 293222, 4322032]
houses['Bathrooms'] = [2, 3.5, 2, 116]
houses['Square_Feet'] = [1500, 2500, 1500, 48000]

# 過濾離群值
filtered_houses = houses[houses['Bathrooms'] < 20]
filtered_houses
__CODE_BLOCK_105__python
# 載入必要函式庫
import numpy as np

# 建立標記離群值的特徵
houses["Outlier"] = np.where(houses["Bathrooms"] < 20, 0, 1)
houses
__CODE_BLOCK_106__python
# 對特徵進行對數轉換
houses["Log_Of_Square_Feet"] = [np.log(x) for x in houses["Square_Feet"]]
houses
__CODE_BLOCK_107__python
# 載入必要函式庫
import numpy as np
from sklearn.preprocessing import Binarizer

# 建立特徵
age = np.array([[6],
                [12],
                [20],
                [36],
                [65]])

# 建立二元化器
binarizer = Binarizer(18)

# 轉換特徵
binarized_age = binarizer.fit_transform(age)
binarized_age
__CODE_BLOCK_108__python
# 分箱特徵
binned_age = np.digitize(age, bins=[20, 30, 64])
binned_age
__CODE_BLOCK_109__python
# 修改分箱行為
right_binned_age = np.digitize(age, bins=[20, 30, 64], right=True)
right_binned_age
__CODE_BLOCK_110__python
# 使用digitize進行二元化
binary_age = np.digitize(age, bins=[18])
binary_age
__CODE_BLOCK_111__python
# 載入函式庫
import pandas as pd
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans

# 建立模擬特徵矩陣
features, _ = make_blobs(n_samples=50,
                         n_features=2,
                         centers=3,
                         random_state=1)

# 建立DataFrame
dataframe = pd.DataFrame(features, columns=["feature_1", "feature_2"])

# 建立k-means聚類別
clusterer = KMeans(3, random_state=0)

# 訓練聚類別
clusterer.fit(features)

# 預測值
dataframe["group"] = clusterer.predict(features)

# 檢視前幾個觀測值
dataframe.head(5)
__CODE_BLOCK_112__python
# 載入函式庫
import numpy as np

# 建立特徵矩陣
features = np.array([[1.1, 11.1],
                     [2.2, 22.2],
                     [3.3, 33.3],
                     [4.4, 44.4],
                     [np.nan, 55]])

# 只保留不(~)含有缺失值的觀測值
complete_features = features[~np.isnan(features).any(axis=1)]
complete_features
__CODE_BLOCK_113__python
# 載入函式庫
import pandas as pd

# 載入資料
dataframe = pd.DataFrame(features, columns=["feature_1", "feature_2"])

# 移除含有缺失值的觀測值
clean_dataframe = dataframe.dropna()
clean_dataframe
__CODE_BLOCK_114__python
# 載入必要的函式庫
import numpy as np
from fancyimpute import KNN
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import make_blobs

# 建立模擬的特徵矩陣
features, _ = make_blobs(n_samples=1000,
                        n_features=2,
                        random_state=1)

# 標準化特徵
scaler = StandardScaler()
standardized_features = scaler.fit_transform(features)

# 將第一個特徵的第一個值替換為缺失值
true_value = standardized_features[0, 0]
standardized_features[0, 0] = np.nan

# 使用KNN預測特徵矩陣中的缺失值
features_knn_imputed = KNN(k=5, verbose=0).complete(standardized_features)

# 比較真實值和填補值
print("真實值:", true_value)
print("填補值:", features_knn_imputed[0, 0])
__CODE_BLOCK_115__python
# 載入函式庫
from sklearn.preprocessing import Imputer

# 建立填補器
mean_imputer = Imputer(strategy="mean", axis=0)

# 填補值
features_mean_imputed = mean_imputer.fit_transform(features)

# 比較真實值和填補值
print("真實值:", true_value)
print("填補值:", features_mean_imputed[0, 0])
__CODE_BLOCK_116__python
# 匯入函式庫
import numpy as np
from sklearn.preprocessing import LabelBinarizer

# 建立特徵
feature = np.array([["Texas"],
                    ["California"],
                    ["Texas"],
                    ["Delaware"],
                    ["Texas"]])

# 建立獨熱編碼器
one_hot = LabelBinarizer()

# 對特徵進行獨熱編碼
encoded_feature = one_hot.fit_transform(feature)
print(encoded_feature)
__CODE_BLOCK_117__python
# 檢視特徵類別
print(one_hot.classes_)
__CODE_BLOCK_118__python
# 反轉獨熱編碼
reversed_feature = one_hot.inverse_transform(encoded_feature)
print(reversed_feature)
__CODE_BLOCK_119__python
# 匯入函式庫import pandas as pd

# 從特徵建立虛擬變數
dummies = pd.get_dummies(feature[:, 0])
print(dummies)
__CODE_BLOCK_120__python
# 匯入函式庫
from sklearn.preprocessing import MultiLabelBinarizer

# 建立多類別特徵
multiclass_feature = [("Texas", "Florida"),
                      ("California", "Alabama"),
                      ("Texas", "Florida"),
                      ("Delaware", "Florida"),
                      ("Texas", "Alabama")]

# 建立多類別獨熱編碼器
one_hot_multiclass = MultiLabelBinarizer()

# 對多類別特徵進行獨熱編碼
encoded_multiclass = one_hot_multiclass.fit_transform(multiclass_feature)
print(encoded_multiclass)

# 檢視類別
print(one_hot_multiclass.classes_)
__CODE_BLOCK_121__python
# 載入必要的函式庫
import pandas as pd
from sklearn.preprocessing import OneHotEncoder

# 建立範例資料
df = pd.DataFrame({
    'State': ['Texas', 'California', 'Delaware', 'Texas', 'California']
})

# 使用pandas實作one-hot編碼
pd.get_dummies(df['State'])

# 或使用scikit-learn的OneHotEncoder
encoder = OneHotEncoder(sparse=False)
encoded_states = encoder.fit_transform(df[['State']])
__CODE_BLOCK_122__python
# 移除第一個one-hot編碼特徵
from sklearn.preprocessing import OneHotEncoder

encoder = OneHotEncoder(drop='first', sparse=False)
encoded_states_minus_one = encoder.fit_transform(df[['State']])
__CODE_BLOCK_123__python
# 載入函式庫
import pandas as pd

# 建立特徵
dataframe = pd.DataFrame({"Score": ["Low", "Low", "Medium", "Medium", "High"]})

# 建立對映字典
scale_mapper = {"Low": 1,
               "Medium": 2,
               "High": 3}

# 使用replace方法替換特徵值
dataframe["Score"].replace(scale_mapper)
__CODE_BLOCK_124__python
dataframe = pd.DataFrame({"Score": ["Low",
                                   "Low",
                                   "Medium",
                                   "Medium",
                                   "High",
                                   "Barely More Than Medium"]})

# 更精確的對映
scale_mapper = {"Low": 1,
               "Medium": 2,
               "Barely More Than Medium": 2.1,
               "High": 3}

dataframe["Score"].replace(scale_mapper)
__CODE_BLOCK_125__python
# 匯入函式庫
from sklearn.feature_extraction import DictVectorizer
import pandas as pd

# 建立字典
data_dict = [{"Red": 2, "Blue": 4},
            {"Red": 4, "Blue": 3},
            {"Red": 1, "Yellow": 2},
            {"Red": 2, "Yellow": 2}]

# 建立字典向量化器
dictvectorizer = DictVectorizer(sparse=False)

# 將字典轉換為特徵矩陣
features = dictvectorizer.fit_transform(data_dict)

# 檢視特徵矩陣
features
__CODE_BLOCK_126__python
# 取得特徵名稱
feature_names = dictvectorizer.get_feature_names()

# 建立DataFrame以更好地視覺化結果
pd.DataFrame(features, columns=feature_names)
__CODE_BLOCK_127__python
# 建立四個檔案的詞頻字典
doc_1_word_count = {"Red": 2, "Blue": 4}
doc_2_word_count = {"Red": 4, "Blue": 3}
doc_3_word_count = {"Red": 1, "Yellow": 2}
doc_4_word_count = {"Red": 2, "Yellow": 2}

# 建立列表
doc_word_counts = [doc_1_word_count,
                  doc_2_word_count,
                  doc_3_word_count,
                  doc_4_word_count]

# 將詞頻字典列表轉換為特徵矩陣
dictvectorizer.fit_transform(doc_word_counts)
__CODE_BLOCK_128__python
# 載入函式庫
import numpy as np
from sklearn.neighbors import KNeighborsClassifier

# 建立包含類別特徵的特徵矩陣
X = np.array([[0, 2.10, 1.45],
              [1, 1.18, 1.33],
              [0, 1.22, 1.27],
              [1, -0.21, -1.19]])

# 建立包含類別特徵缺失值的特徵矩陣
X_with_nan = np.array([[np.nan, 0.87, 1.31],
                       [np.nan, -0.67, -0.22]])

# 訓練KNN學習器
clf = KNeighborsClassifier(3, weights='distance')
trained_model = clf.fit(X[:,1:], X[:,0])

# 預測缺失值的類別
imputed_values = trained_model.predict(X_with_nan[:,1:])

# 將預測的類別列與其他特徵合併
X_with_imputed = np.hstack((imputed_values.reshape(-1,1), X_with_nan[:,1:]))

# 合併兩個特徵矩陣
np.vstack((X_with_imputed, X))
__CODE_BLOCK_129__python
from sklearn.preprocessing import Imputer

# 合併兩個特徵矩陣
X_complete = np.vstack((X_with_nan, X))
imputer = Imputer(strategy='most_frequent', axis=0)
imputer.fit_transform(X_complete)
__CODE_BLOCK_130__python
# 載入必要的函式庫
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# 載入鳶尾花資料
iris = load_iris()

# 建立特徵矩陣
features = iris.data

# 建立目標向量
target = iris.target

# 移除前40個觀測值
features = features[40:,:]
target = target[40:]

# 建立二元目標向量,指示是否為類別0
target = np.where((target == 0), 0, 1)

# 檢視不平衡的目標向量
print(target)
__CODE_BLOCK_131__python
# 方法1:明確指定權重
weights = {0: .9, 1: 0.1}
rf_classifier = RandomForestClassifier(class_weight=weights)

# 方法2:使用"balanced"自動計算權重
balanced_rf_classifier = RandomForestClassifier(class_weight="balanced")
__CODE_BLOCK_132__python
# 找出各類別的觀測值索引
i_class0 = np.where(target == 0)[0]
i_class1 = np.where(target == 1)[0]

# 計算各類別的觀測值數量
n_class0 = len(i_class0)
n_class1 = len(i_class1)

# 從類別1中隨機抽樣,數量等於類別0的觀測值數量
i_class1_downsampled = np.random.choice(i_class1, size=n_class0, replace=False)

# 合併類別0的目標向量與下取樣後的類別1目標向量
balanced_target = np.hstack((target[i_class0], target[i_class1_downsampled]))

# 合併類別0的特徵矩陣與下取樣後的類別1特徵矩陣
balanced_features = np.vstack((features[i_class0,:], features[i_class1_downsampled,:]))
__CODE_BLOCK_133__python
# 從類別0中有放回地隨機抽樣,數量等於類別1的觀測值數量
i_class0_upsampled = np.random.choice(i_class0, size=n_class1, replace=True)

# 合併上取樣後的類別0目標向量與類別1目標向量
balanced_target_up = np.concatenate((target[i_class0_upsampled], target[i_class1]))

# 合併上取樣後的類別0特徵矩陣與類別1特徵矩陣
balanced_features_up = np.vstack((features[i_class0_upsampled,:], features[i_class1,:]))
__CODE_BLOCK_134__python
# 建立文字資料
text_data = [" Interrobang. By Aishwarya Henriette ",
             "Parking And Going. By Karl Gautier",
             " Today Is The night. By Jarek Prakash "]

# 移除空白
strip_whitespace = [string.strip() for string in text_data]
print(strip_whitespace)
# ['Interrobang. By Aishwarya Henriette',
#  'Parking And Going. By Karl Gautier',
#  'Today Is The night. By Jarek Prakash']

# 移除句點
remove_periods = [string.replace(".", "") for string in strip_whitespace]
print(remove_periods)
# ['Interrobang By Aishwarya Henriette',
#  'Parking And Going By Karl Gautier',
#  'Today Is The night By Jarek Prakash']
__CODE_BLOCK_135__python
# 建立轉換函式
def capitalizer(string: str) -> str:
    return string.upper()

# 應用函式
[capitalizer(string) for string in remove_periods]
# ['INTERROBANG BY AISHWARYA HENRIETTE',
#  'PARKING AND GOING BY KARL GAUTIER',
#  'TODAY IS THE NIGHT BY JAREK PRAKASH']

這個例子展示瞭如何建立並應用自定義函式處理文字。capitalizer函式將輸入字串轉為全大寫,然後透過列表推導式應用到每個文字元素上。在實際專案中,通常會定義更複雜的函式,結合多個清理任務,例如移除特殊字元、標準化大小寫、處理縮寫等。

利用正規表示式進行強大的文字操作

當基本字串操作無法滿足需求時,正規表示式提供了更強大、更靈活的文書處理能力:

# 匯入正規表示式函式庫
import re

# 建立函式:將所有字母替換為X
def replace_letters_with_X(string: str) -> str:
    return re.sub(r"[a-zA-Z]", "X", string)

# 應用函式
[replace_letters_with_X(string) for string in remove_periods]
# ['XXXXXXXXXXX XX XXXXXXXXX XXXXXXXXX',
#  'XXXXXXX XXX XXXXX XX XXXX XXXXXXX',
#  'XXXXX XX XXX XXXXX XX XXXXX XXXXXXX']

這個例子展示了正規表示式的強大功能。re.sub()函式接受三個引數:要比對的模式、替換內容和源字串。在這裡,模式r"[a-zA-Z]"比對任何英文字母(大小寫皆可),然後將它們全部替換為字元’X’。正規表示式在處理複雜的文字模式時非常有用,例如提取電子郵件地址、驗證輸入格式或複雜的搜尋和替換操作。

在實際開發中,玄貓經常結合這些基本技術建立一個完整的文字清理管道,根據特定需求執行一系列轉換。重要的是要記住,文字清理應該服務於你的分析目標,而不是機械地應用每一種可能的轉換。

解析與清理HTML內容

使用Beautiful Soup提取HTML中的文字

在爬蟲和資料收集過程中,我們經常會遇到包含HTML標籤的文字資料。Beautiful Soup是一個功能強大的Python函式庫門用於解析HTML和XML檔案:

# 載入函式庫
from bs4 import BeautifulSoup

# 建立一些HTML程式碼html = """
<div class='full_name'><span style='font-weight:bold'>
Masego</span> Azra</div>"
"""

# 解析html
soup = BeautifulSoup(html, "lxml")

# 找出class為"full_name"的div,並顯示其文字內容
soup.find("div", { "class" : "full_name" }).text
# 'Masego Azra'

這段程式碼展示瞭如何使用Beautiful Soup從HTML中提取純文字。首先建立一個包含HTML標籤的字串,然後使用BeautifulSoup解析它(使用lxml解析器)。接著使用find()方法尋找符合條件的元素,這裡是查詢class為"full_name"的div元素。最後,.text屬性回傳該元素包含的所有文字內容,自動忽略所有HTML標籤。

Beautiful Soup的強大之處在於它能夠處理不完整或格式不正確的HTML,這在處理網頁爬取的資料時非常有用。它提供了豐富的選擇器和遍歷方法,可以精確定位並提取所需的內容。

在實務應用中,我常使用Beautiful Soup處理從網站爬取的產品描述或使用者論,清理這些文字後再進行情感分析或主題建模。這個步驟看似簡單,但對提升後續分析的準確性至關重要。

移除標點符號的高效方法

使用translate方法快速移除標點

處理文字時,移除標點符號是常見的預處理步驟。Python提供了一個高效的方法——translate()

# 載入函式庫
import unicodedata
import sys

# 建立文字
text_data = ['Hi!!!! I. Love. This. Song....',
             '10000% Agree!!!! #LoveIT',
             'Right?!?!']

# 建立標點符號字典
punctuation = dict.fromkeys(i for i in range(sys.maxunicode)
                           if unicodedata.category(chr(i)).startswith('P'))

# 對每個字串移除標點符號
[string.translate(punctuation) for string in text_data]
# ['Hi I Love This Song', '10000 Agree LoveIT', 'Right']

這段程式碼展示了一個高效移除標點符號的方法。首先建立一個字典,其中鍵是所有Unicode標點符號的程式碼,值為None。然後對每個字串應用translate()方法,將標點符號對映為None,實際上就是將它們移除。

translate()方法的速度非常快,因為它是在C層級實作的。這個解決方案雖然看起來有點複雜,但比其他替代方案(如正規表示式或迭代替換)快得多,特別是在處理大量文字時。

不過需要注意的是,標點符號通常包含訊息。例如,“Right?“和"Right!“表達了不同的語氣。因此,在決定是否移除標點符號時,應該考慮這些訊息對你的分析是否重要。在某些應用中,如情感分析,保留或專門處理感嘆號和問號可能會提高模型效能。

文字標記化技術

將文字分割為單詞和句子

標記化是將文字轉換為特徵的第一步,它將連續的文字分割成更小的單位(通常是單詞或句子):

# 載入函式庫
from nltk.tokenize import word_tokenize

# 建立文字
string = "The science of today is the technology of tomorrow"

# 標記化單詞
word_tokenize(string)
# ['The', 'science', 'of', 'today', 'is', 'the', 'technology', 'of', 'tomorrow']

我們也可以將文字分割成句子:

# 載入函式庫
from nltk.tokenize import sent_tokenize

# 建立文字
string = "The science of today is the technology of tomorrow. Tomorrow is today."

# 標記化句子
sent_tokenize(string)
# ['The science of today is the technology of tomorrow.', 'Tomorrow is today.']

這兩個例子展示瞭如何使用NLTK(Natural Language Toolkit)進行文字標記化。word_tokenize()函式將文字分割成單詞,而sent_tokenize()則將文字分割成句子。

標記化看似簡單,但實際上涉及多種複雜規則。例如,處理縮寫(如don’t)、處理標點符號、識別句子邊界等。NLTK的標記器已經處理了這些複雜情況,使我們能夠專注於後續的分析任務。

在實務中,標記化通常是文字分析管道中的第一步,為後續的特徵工程(如詞袋模型、TF-IDF向量化或詞嵌入)奠定基礎。選擇適當的標記化方法可能會顯著影響最終模型的效能。

移除停用詞最佳化字特徵

使用NLTK移除常見停用詞

停用詞是那些在文字中非常常見但訊息價值較低的詞,如"a”、“is”、“of”、“on"等。移除這些詞可以減少特徵空間並提高模型效率:

# 載入函式庫
from nltk.corpus import stopwords

# 第一次使用需要下載停用詞集
# import nltk
# nltk.download('stopwords')

# 建立單詞標記
tokenized_words = ['i',
                  'am',
                  'going',
                  'to',
                  'go',
                  'to',
                  'the',
                  'store',
                  'and',
                  'park']

# 載入停用詞
stop_words = stopwords.words('english')

# 移除停用詞
[word for word in tokenized_words if word not in stop_words]
# ['going', 'go', 'store', 'park']

這段程式碼展示瞭如何使用NLTK的停用詞列表移除標記化文字中的常見單詞。首先從NLTK的corpus模組載入英文停用詞,然後使用列表推導式保留不在停用詞列表中的單詞。

NLTK提供了多種語言的停用詞列表,不僅限於英文。值得注意的是,NLTK的停用詞列表假設所有單詞都是小寫的,所以在使用前最好將文字轉換為小寫。

在開發過程中,玄貓發現移除停用詞通常能提高文字分類別主題建模等任務的效能,但在情感分析中,某些停用詞(如否定詞"not”)可能包含重要訊息。因此,應根據具體任務決定是否以及如何處理停用詞。