返回文章列表

主成分與KMeans演算法應用於股票資料分析

本文探討主成分分析(PCA)和 K-Means 分群演算法在股票市場資料分析中的應用。文章首先闡述 PCA 的原理,包括主成分權重、計算過程和解讀方法,並以 S&P 500 股票資料為例,使用 R 語言和 Python 程式碼示範如何進行 PCA

資料科學 機器學習

主成分分析(PCA)是一種降維技術,透過線性組合將原始變數轉換為新的不相關變數,稱為主成分。第一主成分捕捉最大變異,後續主成分依次捕捉剩餘變異。在股票市場分析中,PCA 可用於識別影響股票價格的主要因素。實務上,透過碎石圖和載荷圖分析主成分的解釋變異比例和權重,可以理解資料的底層結構。選擇主成分數量通常根據解釋變異的閾值或交叉驗證。對應分析則適用於類別資料,用於識別類別間的關聯。K-Means 分群演算法則將資料分成 K 個群組,目標是最小化群內平方和。在股票分析中,K-Means 可用於將股票收益分成不同的群組,例如高收益、低收益等。實際應用中,需要對連續變數進行正規化處理,避免尺度影響分群結果。透過分析每個群組的大小和中心點,可以理解不同群組的特性。

主成分分析(Principal Components Analysis, PCA)的原理與實踐

主成分的權重與意義

在進行主成分分析時,第一主成分的權重可能全部為負,但這並不影響主成分的本質。事實上,反轉所有權重的符號並不會改變主成分所代表的資訊。例如,使用 0.747 和 0.665 作為第一主成分的權重,與使用其負值是等價的,就像由原點和 (1,1) 定義的無限直線與由原點和 (-1,-1) 定義的直線相同。

計算主成分

從兩個變數擴充套件到多個變數的過程非常直接。對於第一主成分,只需將額外的預測變數納入線性組閤中,並賦予最佳化權重,以最大化所有預測變數之間的協變異數。主成分分析是一種經典的統計方法,依賴於資料的相關矩陣或協變異數矩陣,且計算速度快,不需要迭代。如前所述,主成分分析僅適用於數值變數,而不適用於類別變數。

整個過程可以描述如下:

  1. 在建立第一主成分時,PCA 找到能夠最大化解釋總變異數百分比的預測變數線性組合。
  2. 這個線性組合成為第一個新的預測變數 $Z_1$。
  3. PCA 重複這個過程,使用相同的變數但不同的權重,建立第二個新的預測變數 $Z_2$。權重的選擇使得 $Z_1$ 和 $Z_2$ 之間不相關。
  4. 這個過程持續進行,直到產生與原始變數 $X_i$ 相同數量的新變數或主成分 $Z_i$。
  5. 選擇保留足夠的主成分,以解釋大部分的變異數。
  6. 最終步驟是將原始資料轉換為新的主成分得分,透過將權重應用於原始值。這些新的得分可以用作簡化的預測變數集。

解讀主成分

主成分的性質往往能夠揭示資料的結構。有幾種標準的視覺化方法可以幫助理解主成分。其中一種方法是使用碎石圖(screeplot)來視覺化主成分的相對重要性。以下是一個使用 R 語言對 S&P 500 頂級公司股票進行主成分分析的例子:

syms <- c('AAPL', 'MSFT', 'CSCO', 'INTC', 'CVX', 'XOM', 'SLB', 'COP', 'JPM', 'WFC', 'USB', 'AXP', 'WMT', 'TGT', 'HD', 'COST')
top_sp <- sp500_px[row.names(sp500_px)>='2005-01-01', syms]
sp_pca <- princomp(top_sp)
screeplot(sp_pca)

碎石圖分析

碎石圖能夠顯示各主成分的解釋變異數比例。如圖 7-2 所示,第一主成分的變異數通常很大,但其他頂級主成分也具有顯著的重要性。

主成分載荷圖

繪製頂級主成分的權重(載荷)也非常有用。以下是使用 Python 進行同樣視覺化的程式碼:

loadings = pd.DataFrame(sp_pca.components_[0:5, :], columns=top_sp.columns)
maxPC = 1.01 * np.max(np.max(np.abs(loadings.loc[0:5, :])))
f, axes = plt.subplots(5, 1, figsize=(5, 5), sharex=True)
for i, ax in enumerate(axes):
    pc_loadings = loadings.loc[i, :]
    colors = ['C0' if l > 0 else 'C1' for l in pc_loadings]
    ax.axhline(color='#888888')
    pc_loadings.plot.bar(ax=ax, color=colors)
    ax.set_ylabel(f'PC{i+1}')
    ax.set_ylim(-maxPC, maxPC)

載荷分析

如圖 7-3 所示,前五個主成分的載荷揭示了資料的重要結構。第一主成分的載荷具有相同的符號,這是因為所有欄位分享一個共同因素(在本例中是整體股票市場趨勢)。第二主成分捕捉了能源股票與其他股票之間的價格變化對比。第三主成分主要對比了 Apple 和 CostCo 的股價變化。

程式碼詳細解說

R 碎石圖程式碼解說

syms <- c('AAPL', 'MSFT', 'CSCO', 'INTC', 'CVX', 'XOM', 'SLB', 'COP', 'JPM', 'WFC', 'USB', 'AXP', 'WMT', 'TGT', 'HD', 'COST')
top_sp <- sp500_px[row.names(sp500_px)>='2005-01-01', syms]
sp_pca <- princomp(top_sp)
screeplot(sp_pca)

內容解密:

  1. 選取特定股票符號:程式碼首先定義了一個包含特定 S&P 500 公司股票程式碼的向量 syms
  2. 篩選資料:從 sp500_px 資料集中選取自 2005 年 1 月 1 日以來的資料,並限制在 syms 中的股票程式碼。
  3. 進行主成分分析:使用 princomp 函式對篩選後的資料進行主成分分析。
  4. 繪製碎石圖:利用 screeplot 函式視覺化主成分的相對重要性。

Python 載荷圖程式碼解說

loadings = pd.DataFrame(sp_pca.components_[0:5, :], columns=top_sp.columns)
maxPC = 1.01 * np.max(np.max(np.abs(loadings.loc[0:5, :])))
f, axes = plt.subplots(5, 1, figsize=(5, 5), sharex=True)
for i, ax in enumerate(axes):
    pc_loadings = loadings.loc[i, :]
    colors = ['C0' if l > 0 else 'C1' for l in pc_loadings]
    ax.axhline(color='#888888')
    pc_loadings.plot.bar(ax=ax, color=colors)
    ax.set_ylabel(f'PC{i+1}')
    ax.set_ylim(-maxPC, maxPC)

內容解密:

  1. 轉換載荷資料:將 PCA 物件中的前五個主成分載荷轉換為 DataFrame。
  2. 計算最大載荷值:找到載荷中的絕對最大值,用於設定 y 軸範圍。
  3. 建立子圖:利用 plt.subplots 建立五個垂直排列的子圖。
  4. 繪製每個主成分的載荷條形圖:遍歷每個子圖,繪製對應的主成分載荷,並根據載荷值的正負設定顏色。
  5. 設定圖表屬性:為每個子圖新增標籤和調整 y 軸範圍,以確保視覺化的一致性和可讀性。

如何選擇主成分數量?

在進行資料降維時,必須決定保留多少個主成分。最常見的方法是採用特定的經驗法則,選擇能夠解釋大部分變異的主成分。透過碎石圖(screeplot),例如圖7-2,可以直觀地進行選擇。另外,也可以選擇累積變異超過某個閾值(如80%)的前幾個主成分。此外,檢查主成分的載荷(loadings),以確定是否具有直觀的解釋,也是非常重要的。交叉驗證(cross-validation)提供了一種更正式的方法來選擇顯著的主成分數量。

對應分析(Correspondence Analysis)

主成分分析(PCA)無法直接應用於類別資料,但有一種相關的技術稱為對應分析,其目標是識別類別之間的關聯。對應分析與PCA在矩陣代數層面有相似之處,主要用於低維度類別資料的圖形分析,而不是像PCA那樣作為大資料降維的預處理步驟。

輸入資料可以視為一個表格,行代表一個變數,列代表另一個變數,儲存格代表記錄計數。輸出結果(經過一些矩陣代數運算後)是一個雙標圖(biplot),即一個散點圖,其軸經過縮放(並標示出該維度解釋的變異百分比)。軸上的單位與原始資料的直觀意義並不直接相關,該散點圖的主要價值在於圖形化地展示彼此相關的變數(透過在圖上的接近程度)。例如,在圖7-4中,家庭任務根據是否由夫妻共同完成(垂直軸)以及由妻子還是丈夫承擔主要責任(水平軸)進行排列。

R語言中有多個套件可用於對應分析,此處使用ca套件:

ca_analysis <- ca(housetasks)
plot(ca_analysis)

在Python中,可以使用prince套件,該套件使用scikit-learn API實作對應分析:

ca = prince.CA(n_components=2)
ca = ca.fit(housetasks)
ca.plot_coordinates(housetasks, figsize=(6, 6))

內容解密:

  1. prince.CA(n_components=2):建立一個對應分析物件,設定要保留的主成分數量為2。
  2. ca.fit(housetasks):將對應分析模型擬合到housetasks資料上。
  3. ca.plot_coordinates(housetasks, figsize=(6, 6)):繪製對應分析的座標圖,用於視覺化類別之間的關聯。

主成分分析重點

  • 主成分是預測變數的線性組合(僅適用於數值資料)。
  • 主成分的計算方式能夠最小化成分之間的相關性,減少冗餘。
  • 通常情況下,少數幾個主成分就能解釋大部分的變異。
  • 這少數的主成分可以用來取代原始的多個預測變數,從而降低維度。
  • 一種表面上類別似的技術——對應分析,適用於類別資料,但在大資料情境下並不常用。

K-Means分群法

分群是一種將資料分成不同群組的技術,每個群組中的記錄彼此相似。分群的目標是識別出有意義且重要的資料群組。這些群組可以直接使用、深入分析,或作為特徵或結果傳遞給預測性迴歸或分類別模型。K-means是第一個被開發的分群方法,由於其演算法相對簡單且能夠擴充套件到大型資料集,因此仍然被廣泛使用。

K-Means分群法的關鍵術語

  • 群集:一組相似的記錄。
  • 群集中心:群集中記錄的變數平均值向量。
  • K:群集的數量。

K-means透過最小化每個記錄到其所屬群集中心的平方距離之和,將資料分成K個群集。這被稱為群內平方和(within-cluster sum of squares)。K-means並不能保證每個群集的大小相同,但能夠找到最佳的分離群集。

正規化

通常需要對連續變數進行正規化(標準化),方法是減去平均值並除以標準差。否則,尺度較大的變數將主導分群過程。

簡單範例

考慮一個具有n筆記錄和兩個變數(x和y)的資料集。假設我們想要將資料分成K=4個群集。這意味著將每個記錄(x_i, y_i)分配給某個群集k。給定n_k筆記錄分配給群集k,群集中心(x_k, y_k)是該群集中所有點的平均值: x̄_k = (1/n_k) * ∑(i ∈ Cluster k) x_i ȳ_k = (1/n_k) * ∑(i ∈ Cluster k) y_i

內容解密:

  1. x̄_kȳ_k代表群集k的中心點,分別是x和y變數在該群集中的平均值。
  2. n_k是分配給群集k的記錄數量。
  3. 公式表示計算每個群集中心的方法,即將該群集中所有記錄的x和y值的總和除以該群集中的記錄數量。

群內平方和由以下公式給出: SS_k = ∑(i ∈ Cluster k) (x_i - x_k)^2 + (y_i - y_k)^2

K-means找到一種記錄分配方式,使得所有四個群集的群內平方和總和最小化: ∑(k=1 to 4) SS_k

內容解密:

  1. SS_k衡量了群集k內的緊密程度,即該群集中所有點到其中心的距離平方和。
  2. K-means的最佳化目標是最小化所有群集的SS_k總和,從而使得每個群集內的點盡可能接近其中心。

K-Means 分群演算法在股票市場資料分析的應用

K-Means 分群演算法是一種常見的無監督學習方法,用於將資料分成不同的群組,使得同一群組內的資料點盡可能相似,而不同群組之間的資料點則盡可能不同。在股票市場資料分析中,K-Means 可以用來辨識股票收益的自然群組,或將資料分成預先設定的群組。

K-Means 在股票收益分群的範例

假設我們想要將每日股票收益分成四個群組,可以使用 K-Means 分群演算法來實作。在 R 語言中,可以使用 kmeans 函式來執行 K-Means 分群:

df <- sp500_px[row.names(sp500_px)>='2011-01-01', c('XOM', 'CVX')]
km <- kmeans(df, centers=4)
df$cluster <- factor(km$cluster)
head(df)

程式碼解密:

  1. df <- sp500_px[row.names(sp500_px)>='2011-01-01', c('XOM', 'CVX')]: 選擇從 2011 年 1 月 1 日開始的 ExxonMobil (XOM) 和 Chevron (CVX) 的股票收益資料。
  2. km <- kmeans(df, centers=4): 對選定的資料執行 K-Means 分群,將資料分成 4 個群組。
  3. df$cluster <- factor(km$cluster): 將分群結果新增到原始資料框架中。

在 Python 中,可以使用 scikit-learn 的 KMeans 類別來執行 K-Means 分群:

df = sp500_px.loc[sp500_px.index >= '2011-01-01', ['XOM', 'CVX']]
kmeans = KMeans(n_clusters=4).fit(df)
df['cluster'] = kmeans.labels_
df.head()

程式碼解密:

  1. df = sp500_px.loc[sp500_px.index >= '2011-01-01', ['XOM', 'CVX']]: 選擇從 2011 年 1 月 1 日開始的 ExxonMobil (XOM) 和 Chevron (CVX) 的股票收益資料。
  2. kmeans = KMeans(n_clusters=4).fit(df): 對選定的資料執行 K-Means 分群,將資料分成 4 個群組。
  3. df['cluster'] = kmeans.labels_: 將分群結果新增到原始資料框架中。

K-Means 分群結果的解讀

K-Means 分群演算法的輸出結果包括每個資料點的群組標籤和每個群組的中心點。這些資訊可以用來解讀分群結果。

在 R 中,可以使用以下程式碼來取得每個群組的大小和中心點:

km$size
centers <- data.frame(cluster=factor(1:4), km$centers)
centers

程式碼解密:

  1. km$size: 輸出每個群組的大小。
  2. centers <- data.frame(cluster=factor(1:4), km$centers): 將每個群組的中心點整理成資料框架。

在 Python 中,可以使用以下程式碼來取得每個群組的大小和中心點:

from collections import Counter
Counter(kmeans.labels_)
centers = pd.DataFrame(kmeans.cluster_centers_, columns=['XOM', 'CVX'])
centers

程式碼解密:

  1. Counter(kmeans.labels_): 輸出每個群組的大小。
  2. centers = pd.DataFrame(kmeans.cluster_centers_, columns=['XOM', 'CVX']): 將每個群組的中心點整理成資料框架。

K-Means 分群演算法的視覺化

K-Means 分群結果可以使用視覺化工具來呈現,例如散佈圖。

在 R 中,可以使用 ggplot2 套件來建立散佈圖:

ggplot(data=df, aes(x=XOM, y=CVX, color=cluster, shape=cluster)) +
  geom_point(alpha=.3) +
  geom_point(data=centers, aes(x=XOM, y=CVX), size=3, stroke=2)

程式碼解密:

  1. ggplot(data=df, aes(x=XOM, y=CVX, color=cluster, shape=cluster)): 建立散佈圖,x 軸為 XOM,y 軸為 CVX,並根據群組標籤進行著色和形狀區分。
  2. geom_point(alpha=.3): 新增資料點,透明度設為 0.3。
  3. geom_point(data=centers, aes(x=XOM, y=CVX), size=3, stroke=2): 新增群組中心點,大小設為 3,邊框寬度設為 2。

在 Python 中,可以使用 seaborn 套件來建立散佈圖:

fig, ax = plt.subplots(figsize=(4, 4))
ax = sns.scatterplot(x='XOM', y='CVX', hue='cluster', style='cluster', ax=ax, data=df)
ax.set_xlim(-3, 3)
ax.set_ylim(-3, 3)
centers.plot.scatter(x='XOM', y='CVX', ax=ax, s=50, color='black')

程式碼解密:

  1. sns.scatterplot(x='XOM', y='CVX', hue='cluster', style='cluster', ax=ax, data=df): 建立散佈圖,x 軸為 XOM,y 軸為 CVX,並根據群組標籤進行著色和形狀區分。
  2. ax.set_xlim(-3, 3)ax.set_ylim(-3, 3): 設定 x 軸和 y 軸的範圍。
  3. centers.plot.scatter(x='XOM', y='CVX', ax=ax, s=50, color='black'): 新增群組中心點,大小設為 50,顏色設為黑色。