Python 在過去十年間已發展成為資料科學領域的主流程式語言。這並非偶然,而是因為 Python 具備幾項關鍵優勢:簡潔直觀的語法降低了學習門檻,豐富的科學計算函式庫生態系統提供了強大的工具支援,活躍的社群持續貢獻高品質的開源專案。對於希望進入資料科學領域的學習者而言,掌握 Python 的基礎知識是不可或缺的第一步。
本文將從 Python 的核心語法和資料結構開始,逐步深入到資料讀寫、蒙地卡羅模擬等應用場景,最後探討線性代數在資料科學中的基礎地位。線性代數不僅是機器學習演算法的數學基礎,也是理解資料轉換、降維分析等進階技術的關鍵。透過結合 NumPy 和 Pandas 等函式庫的實作範例,讀者將能夠建立從理論到實踐的完整知識體系。
Python 程式設計基礎概念
Python 的設計哲學強調程式碼的可讀性和簡潔性。這種設計理念體現在語言的各個層面,從縮排作為程式區塊的界定方式,到直觀的語法結構。理解這些基礎概念是有效運用 Python 進行資料科學工作的前提。
函式定義與型別標註
函式是程式碼組織的基本單位,它將特定功能封裝起來以便重複使用。Python 3.5 之後引入的型別標註功能,雖然不會在執行時強制檢查型別,但能夠提高程式碼的可讀性和可維護性,並支援靜態型別檢查工具。
# 定義帶有型別標註的函式
# 型別標註說明參數和回傳值的預期型別
# 這有助於程式碼文件化和 IDE 自動完成
def calculate_compound_interest(
principal: float,
rate: float,
years: int
) -> float:
"""
計算複利終值
Args:
principal: 本金金額
rate: 年利率(小數形式,如 0.05 表示 5%)
years: 投資年數
Returns:
投資終值
"""
# 使用複利公式:A = P(1 + r)^n
# 其中 A 是終值,P 是本金,r 是利率,n 是期數
return principal * (1 + rate) ** years
def format_currency(amount: float, currency: str = "TWD") -> str:
"""
將金額格式化為貨幣字串
Args:
amount: 金額數值
currency: 貨幣代碼,預設為新台幣
Returns:
格式化後的貨幣字串
"""
# 使用格式化字串將數值轉換為帶千分位的金額
# :,.2f 表示千分位分隔符和兩位小數
return f"{currency} {amount:,.2f}"
# 實際使用範例
if __name__ == "__main__":
# 設定投資參數
initial_investment = 100000.0 # 本金 10 萬
annual_rate = 0.06 # 年利率 6%
investment_period = 10 # 投資 10 年
# 計算終值
final_value = calculate_compound_interest(
principal=initial_investment,
rate=annual_rate,
years=investment_period
)
# 格式化輸出結果
print(f"初始投資: {format_currency(initial_investment)}")
print(f"年利率: {annual_rate * 100}%")
print(f"投資期間: {investment_period} 年")
print(f"投資終值: {format_currency(final_value)}")
# 計算獲利
profit = final_value - initial_investment
roi = (profit / initial_investment) * 100
print(f"總獲利: {format_currency(profit)}")
print(f"投資報酬率: {roi:.2f}%")
這段程式碼展示了 Python 函式定義的幾個重要特性。型別標註使用冒號後接型別名稱的語法,箭頭後面則標註回傳型別。文件字串(docstring)採用三重引號,遵循 Google 風格的格式,清楚說明參數和回傳值。預設參數值的設定讓函式呼叫更加彈性,使用者可以選擇性地覆寫預設值。
核心資料結構的特性與應用
Python 提供了幾種內建的資料結構,各有不同的特性和適用場景。理解這些資料結構的差異,對於選擇正確的工具解決問題至關重要。
列表(List)是最常用的資料結構,它是一個有序的可變序列。列表可以包含任意型別的元素,支援索引存取、切片操作和各種修改方法。由於列表的可變性,它適合用於需要頻繁增刪元素的場景。
元組(Tuple)與列表類似,但它是不可變的。一旦建立,就無法修改其內容。這種不可變性使元組適合用於表示固定的資料集合,例如座標點、RGB 色彩值等。元組也可以作為字典的鍵,而列表則不行。
字典(Dictionary)是一種鍵值對的集合,提供快速的查找能力。字典使用雜湊表實作,因此平均情況下查找、插入和刪除操作的時間複雜度都是 O(1)。字典適合用於需要透過標識符快速存取資料的場景。
# 列表的進階操作示範
# 列表是有序、可變的序列
def analyze_stock_prices(prices: list) -> dict:
"""
分析股票價格資料
Args:
prices: 股票價格列表
Returns:
包含各種統計指標的字典
"""
if not prices:
return {"error": "價格資料為空"}
# 基本統計計算
n = len(prices)
total = sum(prices)
average = total / n
# 找出最高和最低價
highest = max(prices)
lowest = min(prices)
# 計算價格變動
# 使用列表推導式計算每日漲跌
daily_changes = [
prices[i] - prices[i-1]
for i in range(1, n)
]
# 統計上漲和下跌天數
up_days = sum(1 for change in daily_changes if change > 0)
down_days = sum(1 for change in daily_changes if change < 0)
return {
"count": n,
"average": round(average, 2),
"highest": highest,
"lowest": lowest,
"range": highest - lowest,
"up_days": up_days,
"down_days": down_days,
"volatility": round(max(daily_changes) - min(daily_changes), 2) if daily_changes else 0
}
# 元組的應用:表示不可變的資料結構
# 例如地理座標、日期範圍等
def calculate_distance(point1: tuple, point2: tuple) -> float:
"""
計算兩點之間的歐幾里得距離
Args:
point1: 第一點座標 (x, y)
point2: 第二點座標 (x, y)
Returns:
兩點間的距離
"""
# 解包元組取得各座標分量
x1, y1 = point1
x2, y2 = point2
# 應用距離公式:sqrt((x2-x1)^2 + (y2-y1)^2)
distance = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
return distance
# 字典的進階應用:巢狀結構和方法操作
def create_portfolio(stocks: dict, cash: float) -> dict:
"""
建立投資組合結構
Args:
stocks: 股票持倉字典 {股票代碼: (股數, 成本價)}
cash: 現金部位
Returns:
完整的投資組合字典
"""
portfolio = {
"holdings": {},
"cash": cash,
"total_cost": 0.0
}
# 處理每檔股票的持倉資訊
for symbol, (shares, cost_price) in stocks.items():
position_cost = shares * cost_price
portfolio["holdings"][symbol] = {
"shares": shares,
"cost_price": cost_price,
"position_cost": position_cost
}
portfolio["total_cost"] += position_cost
portfolio["total_cost"] += cash
return portfolio
# 實際使用示範
if __name__ == "__main__":
# 股價分析範例
sample_prices = [150.5, 152.3, 151.8, 155.2, 154.0, 156.8, 158.5]
analysis = analyze_stock_prices(sample_prices)
print("股價分析結果:")
for key, value in analysis.items():
print(f" {key}: {value}")
# 距離計算範例
taipei = (25.0330, 121.5654) # 台北座標
kaohsiung = (22.6273, 120.3014) # 高雄座標
dist = calculate_distance(taipei, kaohsiung)
print(f"\n台北到高雄的座標距離: {dist:.4f} 度")
# 投資組合範例
my_stocks = {
"2330": (1000, 580.0), # 台積電
"2317": (2000, 105.0), # 鴻海
"2454": (500, 850.0) # 聯發科
}
my_portfolio = create_portfolio(my_stocks, 50000.0)
print("\n投資組合:")
print(f" 現金: {my_portfolio['cash']:,.0f}")
print(f" 總成本: {my_portfolio['total_cost']:,.0f}")
for symbol, info in my_portfolio["holdings"].items():
print(f" {symbol}: {info['shares']} 股 @ {info['cost_price']}")
這些範例展示了資料結構在實際應用中的使用方式。列表推導式提供了簡潔的方式來建立新列表,生成器運算式則能在不佔用大量記憶體的情況下處理資料。元組的解包功能讓程式碼更加清晰易讀。字典的巢狀結構可以表達複雜的資料關係。
資料讀寫與 Pandas 基礎操作
在資料科學工作流程中,資料的讀取和儲存是不可或缺的環節。Python 的 Pandas 函式庫提供了強大的資料操作能力,其核心資料結構 DataFrame 可以輕鬆處理表格式資料。
Pandas 支援多種資料格式的讀寫,包括 CSV、Excel、JSON、SQL 資料庫等。其設計理念借鑑了 R 語言的 data.frame,但在效能和功能上都有顯著提升。理解 Pandas 的基本操作對於資料前處理和探索性分析至關重要。
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
# 建立示範資料
# 模擬一個月的股票交易資料
def generate_trading_data(days: int = 30) -> pd.DataFrame:
"""
生成模擬的股票交易資料
Args:
days: 交易天數
Returns:
包含交易資料的 DataFrame
"""
# 設定隨機種子以確保結果可重現
np.random.seed(42)
# 生成日期範圍
# 使用 pandas 的 date_range 函式建立營業日序列
start_date = datetime(2024, 1, 1)
dates = pd.date_range(
start=start_date,
periods=days,
freq='B' # B 表示營業日
)
# 生成價格資料
# 使用隨機遊走模型模擬價格變動
initial_price = 100.0
returns = np.random.normal(0.001, 0.02, days) # 平均報酬 0.1%,標準差 2%
prices = initial_price * np.cumprod(1 + returns)
# 生成成交量資料
# 假設成交量服從對數常態分布
volumes = np.random.lognormal(mean=15, sigma=0.5, size=days).astype(int)
# 計算每日高低價
# 假設日內波動約為收盤價的 2%
daily_volatility = prices * 0.02
highs = prices + np.random.uniform(0, 1, days) * daily_volatility
lows = prices - np.random.uniform(0, 1, days) * daily_volatility
# 建立 DataFrame
df = pd.DataFrame({
'date': dates,
'open': prices * (1 + np.random.uniform(-0.01, 0.01, days)),
'high': highs,
'low': lows,
'close': prices,
'volume': volumes
})
# 設定日期為索引
df.set_index('date', inplace=True)
return df
def analyze_trading_data(df: pd.DataFrame) -> dict:
"""
分析交易資料並計算各種指標
Args:
df: 包含 OHLCV 資料的 DataFrame
Returns:
分析結果字典
"""
# 計算日報酬率
# 使用 pct_change 方法計算百分比變動
df['return'] = df['close'].pct_change()
# 計算移動平均線
# 5 日和 20 日移動平均是常用的技術指標
df['ma5'] = df['close'].rolling(window=5).mean()
df['ma20'] = df['close'].rolling(window=20).mean()
# 計算波動率
# 使用滾動標準差作為波動率的代理指標
df['volatility'] = df['return'].rolling(window=5).std() * np.sqrt(252)
# 彙總統計
summary = {
'period': f"{df.index[0].strftime('%Y-%m-%d')} to {df.index[-1].strftime('%Y-%m-%d')}",
'trading_days': len(df),
'start_price': round(df['close'].iloc[0], 2),
'end_price': round(df['close'].iloc[-1], 2),
'total_return': round((df['close'].iloc[-1] / df['close'].iloc[0] - 1) * 100, 2),
'avg_daily_return': round(df['return'].mean() * 100, 4),
'volatility': round(df['return'].std() * np.sqrt(252) * 100, 2),
'max_drawdown': round(calculate_max_drawdown(df['close']) * 100, 2),
'total_volume': int(df['volume'].sum()),
'avg_volume': int(df['volume'].mean())
}
return df, summary
def calculate_max_drawdown(prices: pd.Series) -> float:
"""
計算最大回撤
Args:
prices: 價格序列
Returns:
最大回撤比例
"""
# 計算累積最高價
cummax = prices.cummax()
# 計算從高點的回撤
drawdown = (prices - cummax) / cummax
# 返回最大回撤(負值)
return drawdown.min()
# 資料儲存與讀取示範
def save_and_load_demo(df: pd.DataFrame, filename: str = "trading_data"):
"""
示範資料的儲存和讀取操作
Args:
df: 要儲存的 DataFrame
filename: 檔案名稱(不含副檔名)
"""
# 儲存為 CSV 格式
# CSV 是最通用的格式,可被大多數工具讀取
csv_path = f"{filename}.csv"
df.to_csv(csv_path)
print(f"資料已儲存至 {csv_path}")
# 從 CSV 讀取
# parse_dates 參數自動解析日期欄位
# index_col 指定哪一欄作為索引
df_from_csv = pd.read_csv(
csv_path,
parse_dates=['date'],
index_col='date'
)
# 儲存為 Parquet 格式
# Parquet 是高效的列式儲存格式,適合大型資料集
parquet_path = f"{filename}.parquet"
df.to_parquet(parquet_path)
print(f"資料已儲存至 {parquet_path}")
# 從 Parquet 讀取
df_from_parquet = pd.read_parquet(parquet_path)
return df_from_csv, df_from_parquet
# 主程式
if __name__ == "__main__":
# 生成交易資料
trading_df = generate_trading_data(30)
# 顯示資料概覽
print("交易資料概覽:")
print(trading_df.head(10))
print(f"\n資料維度: {trading_df.shape}")
print(f"\n資料型別:\n{trading_df.dtypes}")
# 進行分析
analyzed_df, summary = analyze_trading_data(trading_df)
print("\n===== 交易分析摘要 =====")
for key, value in summary.items():
label = key.replace('_', ' ').title()
if 'return' in key or 'volatility' in key or 'drawdown' in key:
print(f"{label}: {value}%")
else:
print(f"{label}: {value}")
# 顯示最近幾天的完整資料
print("\n最近 5 個交易日的詳細資料:")
print(analyzed_df[['close', 'return', 'ma5', 'ma20', 'volatility']].tail())
這段程式碼展示了 Pandas 在金融資料分析中的典型應用。rolling 方法用於計算滾動統計量,這在技術分析中非常常見。pct_change 方法計算百分比變動,是計算報酬率的標準方式。資料的儲存支援多種格式,CSV 適合通用場景,而 Parquet 格式則提供更好的壓縮率和讀取效能。
蒙地卡羅模擬在金融分析的應用
蒙地卡羅模擬是一種利用隨機抽樣來估計數學問題解的方法。這種方法特別適合處理具有不確定性的問題,例如金融市場的價格預測、風險評估和選擇權定價。蒙地卡羅方法的核心思想是:透過大量的隨機模擬,可以近似出系統的統計特性。
在股票價格模擬中,最常用的模型是幾何布朗運動(Geometric Brownian Motion)。這個模型假設股票價格的對數報酬率服從常態分布,並且價格變動具有連續性。雖然這個假設在現實中不完全成立(例如市場會有跳空),但它提供了一個合理的起點來理解價格動態。
import numpy as np
import matplotlib.pyplot as plt
from typing import Tuple
def simulate_gbm_paths(
S0: float,
mu: float,
sigma: float,
T: float,
dt: float,
n_paths: int
) -> np.ndarray:
"""
使用幾何布朗運動模型模擬股票價格路徑
幾何布朗運動的隨機微分方程為:
dS = μS dt + σS dW
其中:
- S 是股票價格
- μ 是漂移率(期望報酬率)
- σ 是波動率
- W 是維納過程(布朗運動)
離散化後的模擬公式為:
S(t+dt) = S(t) * exp((μ - σ²/2)*dt + σ*√dt*Z)
其中 Z ~ N(0,1)
Args:
S0: 初始股票價格
mu: 年化漂移率(期望報酬率)
sigma: 年化波動率
T: 模擬期間(年)
dt: 時間步長(年)
n_paths: 模擬路徑數量
Returns:
模擬的價格路徑陣列,形狀為 (n_steps+1, n_paths)
"""
# 計算時間步數
n_steps = int(T / dt)
# 初始化價格陣列
# 第一維是時間,第二維是路徑
prices = np.zeros((n_steps + 1, n_paths))
prices[0] = S0
# 生成隨機增量
# 使用標準常態分布生成 Z 值
# 每個時間步和每條路徑都需要一個獨立的隨機數
Z = np.random.standard_normal((n_steps, n_paths))
# 計算漂移項和擴散項
# 漂移項考慮了 Ito 引理的修正項 -σ²/2
drift = (mu - 0.5 * sigma ** 2) * dt
diffusion = sigma * np.sqrt(dt)
# 逐步模擬價格路徑
for t in range(1, n_steps + 1):
# 應用離散化的 GBM 公式
prices[t] = prices[t-1] * np.exp(drift + diffusion * Z[t-1])
return prices
def analyze_simulation_results(
paths: np.ndarray,
confidence_level: float = 0.95
) -> dict:
"""
分析模擬結果並計算風險指標
Args:
paths: 價格路徑陣列
confidence_level: 信賴水準,用於計算 VaR
Returns:
包含各種風險指標的字典
"""
# 取得最終價格分布
final_prices = paths[-1]
initial_price = paths[0, 0]
# 計算報酬率分布
returns = (final_prices - initial_price) / initial_price
# 計算基本統計量
mean_return = np.mean(returns)
std_return = np.std(returns)
median_return = np.median(returns)
# 計算 VaR(Value at Risk)
# VaR 是在給定信賴水準下的最大預期損失
var_level = 1 - confidence_level
var = np.percentile(returns, var_level * 100)
# 計算 CVaR(Conditional Value at Risk)
# CVaR 是超過 VaR 的條件期望損失
cvar = np.mean(returns[returns <= var])
# 計算獲利機率
prob_profit = np.mean(returns > 0)
# 計算百分位數
percentiles = {
5: np.percentile(final_prices, 5),
25: np.percentile(final_prices, 25),
50: np.percentile(final_prices, 50),
75: np.percentile(final_prices, 75),
95: np.percentile(final_prices, 95)
}
return {
'mean_return': mean_return,
'std_return': std_return,
'median_return': median_return,
f'var_{int(confidence_level*100)}': var,
f'cvar_{int(confidence_level*100)}': cvar,
'prob_profit': prob_profit,
'mean_final_price': np.mean(final_prices),
'percentiles': percentiles
}
def run_monte_carlo_simulation():
"""
執行完整的蒙地卡羅模擬並展示結果
"""
# 設定模擬參數
S0 = 100.0 # 初始價格
mu = 0.08 # 年化期望報酬率 8%
sigma = 0.25 # 年化波動率 25%
T = 1.0 # 模擬期間 1 年
dt = 1/252 # 時間步長為 1 個交易日
n_paths = 10000 # 模擬 10000 條路徑
print("蒙地卡羅模擬參數:")
print(f" 初始價格: {S0}")
print(f" 期望年報酬率: {mu*100}%")
print(f" 年化波動率: {sigma*100}%")
print(f" 模擬期間: {T} 年")
print(f" 模擬路徑數: {n_paths}")
# 執行模擬
np.random.seed(123) # 設定隨機種子以確保可重現性
paths = simulate_gbm_paths(S0, mu, sigma, T, dt, n_paths)
# 分析結果
results = analyze_simulation_results(paths)
print("\n模擬結果分析:")
print(f" 平均報酬率: {results['mean_return']*100:.2f}%")
print(f" 報酬率標準差: {results['std_return']*100:.2f}%")
print(f" 中位數報酬率: {results['median_return']*100:.2f}%")
print(f" 獲利機率: {results['prob_profit']*100:.2f}%")
print(f" 95% VaR: {results['var_95']*100:.2f}%")
print(f" 95% CVaR: {results['cvar_95']*100:.2f}%")
print("\n最終價格分布:")
for pct, value in results['percentiles'].items():
print(f" 第 {pct} 百分位: {value:.2f}")
return paths, results
# 執行模擬
if __name__ == "__main__":
paths, results = run_monte_carlo_simulation()
這個蒙地卡羅模擬展示了幾個重要的金融概念。幾何布朗運動模型是選擇權定價(如 Black-Scholes 模型)的基礎。Ito 引理的修正項 -σ²/2 確保了對數價格的期望值正確。VaR 和 CVaR 是現代風險管理中的核心指標,監管機構通常要求金融機構計算這些數值。
以下是蒙地卡羅模擬流程的示意圖:
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
start
:設定模擬參數\n(S0, μ, σ, T, dt);
:初始化價格陣列\nprices[0] = S0;
:生成隨機數矩陣\nZ ~ N(0, 1);
while (t < T?) is (是)
:計算價格增量\ndS = μS dt + σS√dt Z;
:更新價格\nS(t+dt) = S(t) + dS;
:t = t + dt;
endwhile (否)
:收集最終價格分布;
:計算統計指標\n(VaR, CVaR, 報酬率);
:輸出風險分析報告;
stop
@enduml線性代數基礎概念
線性代數是現代資料科學和機器學習的數學基礎。從簡單的線性迴歸到複雜的深度神經網路,幾乎所有的演算法都建立在線性代數的概念之上。理解向量、矩陣和線性變換,不僅有助於正確使用現有的工具,也能幫助我們設計更有效率的解決方案。
向量空間與向量運算
向量可以從幾何和代數兩個角度來理解。在幾何上,向量表示空間中的方向和大小;在代數上,向量是一組有序數的集合。向量空間定義了向量可以進行的運算,包括加法和純量乘法,並且這些運算必須滿足特定的公理。
import numpy as np
def demonstrate_vector_operations():
"""
展示向量的基本運算
"""
# 建立向量
# NumPy 陣列是 Python 中表示向量的標準方式
v1 = np.array([3, 4])
v2 = np.array([1, 2])
print("向量運算示範")
print("=" * 40)
print(f"向量 v1: {v1}")
print(f"向量 v2: {v2}")
# 向量加法
# 對應元素相加
v_sum = v1 + v2
print(f"\nv1 + v2 = {v_sum}")
# 向量減法
v_diff = v1 - v2
print(f"v1 - v2 = {v_diff}")
# 純量乘法
# 每個元素乘以純量
scalar = 2.5
v_scaled = scalar * v1
print(f"{scalar} * v1 = {v_scaled}")
# 向量長度(範數)
# L2 範數:sqrt(x1² + x2² + ...)
norm_v1 = np.linalg.norm(v1)
print(f"\n||v1|| = {norm_v1}")
# 單位向量
# 將向量除以其長度得到單位向量
unit_v1 = v1 / norm_v1
print(f"v1 的單位向量: {unit_v1}")
print(f"單位向量的長度: {np.linalg.norm(unit_v1)}")
# 內積(點積)
# 結果是純量:v1·v2 = Σ(v1_i * v2_i)
dot_product = np.dot(v1, v2)
print(f"\nv1 · v2 = {dot_product}")
# 使用內積計算向量間的夾角
# cos(θ) = (v1 · v2) / (||v1|| * ||v2||)
norm_v2 = np.linalg.norm(v2)
cos_angle = dot_product / (norm_v1 * norm_v2)
angle_rad = np.arccos(cos_angle)
angle_deg = np.degrees(angle_rad)
print(f"v1 與 v2 的夾角: {angle_deg:.2f} 度")
# 投影
# v1 在 v2 上的投影向量
projection = (dot_product / np.dot(v2, v2)) * v2
print(f"v1 在 v2 上的投影: {projection}")
def demonstrate_3d_vectors():
"""
展示三維向量的特殊運算
"""
print("\n三維向量運算")
print("=" * 40)
# 建立三維向量
a = np.array([1, 0, 0]) # x 軸方向
b = np.array([0, 1, 0]) # y 軸方向
print(f"向量 a: {a}")
print(f"向量 b: {b}")
# 外積(叉積)
# 只對三維向量定義
# 結果是垂直於兩個輸入向量的向量
cross_product = np.cross(a, b)
print(f"\na × b = {cross_product}")
# 驗證垂直性
# 叉積結果與原向量的點積應為零
print(f"(a × b) · a = {np.dot(cross_product, a)}")
print(f"(a × b) · b = {np.dot(cross_product, b)}")
# 計算平行四邊形面積
# 面積 = ||a × b||
area = np.linalg.norm(cross_product)
print(f"由 a 和 b 構成的平行四邊形面積: {area}")
# 執行示範
if __name__ == "__main__":
demonstrate_vector_operations()
demonstrate_3d_vectors()
這些向量運算在資料科學中有廣泛的應用。內積用於計算相似度(如餘弦相似度)、投影用於降維分析(如 PCA)、範數用於正則化和距離計算。理解這些基本運算是學習更進階技術的基礎。
矩陣運算與線性變換
矩陣是線性代數的核心對象,它可以表示線性變換、儲存資料,或表達方程組。矩陣運算包括加法、乘法、轉置、求逆等,每種運算都有其數學意義和計算規則。
import numpy as np
def demonstrate_matrix_operations():
"""
展示矩陣的基本運算
"""
print("矩陣運算示範")
print("=" * 40)
# 建立矩陣
# 二維 NumPy 陣列表示矩陣
A = np.array([
[1, 2, 3],
[4, 5, 6]
]) # 2x3 矩陣
B = np.array([
[7, 8],
[9, 10],
[11, 12]
]) # 3x2 矩陣
print(f"矩陣 A ({A.shape[0]}x{A.shape[1]}):")
print(A)
print(f"\n矩陣 B ({B.shape[0]}x{B.shape[1]}):")
print(B)
# 矩陣乘法
# A 的列數必須等於 B 的行數
# 結果矩陣的維度是 (A的行數 x B的列數)
C = np.matmul(A, B) # 或 A @ B
print(f"\nA × B = ({C.shape[0]}x{C.shape[1]}):")
print(C)
# 矩陣轉置
# 行列互換
A_T = A.T
print(f"\nA 的轉置:")
print(A_T)
# 元素運算
# 這些是逐元素的運算,不是矩陣運算
D = np.array([
[1, 2],
[3, 4]
])
E = np.array([
[5, 6],
[7, 8]
])
print("\n方陣運算")
print(f"矩陣 D:\n{D}")
print(f"矩陣 E:\n{E}")
# 元素相乘(Hadamard product)
hadamard = D * E
print(f"\nD ⊙ E (元素相乘):\n{hadamard}")
# 矩陣乘法
matrix_product = D @ E
print(f"\nD × E (矩陣乘法):\n{matrix_product}")
def demonstrate_special_matrices():
"""
展示特殊矩陣和相關運算
"""
print("\n特殊矩陣與進階運算")
print("=" * 40)
# 單位矩陣
# 對角線為 1,其餘為 0
I = np.eye(3)
print("3x3 單位矩陣:")
print(I)
# 對角矩陣
diag_values = [1, 2, 3]
D = np.diag(diag_values)
print(f"\n對角矩陣 (對角元素: {diag_values}):")
print(D)
# 矩陣的行列式
# 只對方陣定義
A = np.array([
[3, 1],
[2, 4]
])
det_A = np.linalg.det(A)
print(f"\n矩陣 A:\n{A}")
print(f"det(A) = {det_A:.2f}")
# 矩陣的逆
# 只有行列式非零的方陣才可逆
if det_A != 0:
A_inv = np.linalg.inv(A)
print(f"\nA 的逆矩陣:")
print(A_inv)
# 驗證:A × A⁻¹ = I
product = A @ A_inv
print(f"\nA × A⁻¹:")
print(np.round(product, 10)) # 四捨五入以處理浮點誤差
# 矩陣的跡(trace)
# 對角元素之和
trace_A = np.trace(A)
print(f"\ntr(A) = {trace_A}")
# 矩陣的秩(rank)
# 線性獨立的行(或列)的最大數量
rank_A = np.linalg.matrix_rank(A)
print(f"rank(A) = {rank_A}")
def solve_linear_system():
"""
使用矩陣求解線性方程組
"""
print("\n線性方程組求解")
print("=" * 40)
# 方程組:
# 2x + y = 5
# x + 3y = 5
# 係數矩陣
A = np.array([
[2, 1],
[1, 3]
])
# 常數向量
b = np.array([5, 5])
print("方程組:")
print("2x + y = 5")
print("x + 3y = 5")
# 方法 1:使用逆矩陣
# x = A⁻¹b
x_inv = np.linalg.inv(A) @ b
print(f"\n使用逆矩陣求解: x = {x_inv}")
# 方法 2:使用 solve 函式(數值穩定性更好)
x_solve = np.linalg.solve(A, b)
print(f"使用 solve 函式: x = {x_solve}")
# 驗證解
residual = A @ x_solve - b
print(f"\n驗證 (Ax - b): {residual}")
# 執行示範
if __name__ == "__main__":
demonstrate_matrix_operations()
demonstrate_special_matrices()
solve_linear_system()
矩陣運算在機器學習中無處不在。線性迴歸的正規方程式、神經網路的前向傳播和反向傳播、主成分分析的特徵分解,都依賴於這些基本的矩陣運算。NumPy 利用底層的 BLAS 和 LAPACK 函式庫,提供了高效能的實作。
幾何變換與旋轉矩陣
線性變換可以透過矩陣來表示,這在電腦圖形學、機器人學和物理模擬中有重要應用。常見的幾何變換包括旋轉、縮放、剪切和反射。理解這些變換的矩陣表示,有助於處理各種空間變換問題。
旋轉矩陣是最常用的幾何變換之一。在二維空間中,旋轉矩陣將向量繞原點旋轉指定的角度。旋轉矩陣是正交矩陣(其轉置等於其逆),這意味著它保持向量的長度和向量間的夾角。
import numpy as np
def rotation_matrix_2d(theta: float) -> np.ndarray:
"""
建立二維旋轉矩陣
旋轉矩陣的形式:
R(θ) = [cos(θ) -sin(θ)]
[sin(θ) cos(θ)]
這個矩陣將向量逆時針旋轉角度 θ
Args:
theta: 旋轉角度(弧度)
Returns:
2x2 旋轉矩陣
"""
cos_t = np.cos(theta)
sin_t = np.sin(theta)
return np.array([
[cos_t, -sin_t],
[sin_t, cos_t]
])
def rotation_matrix_3d(axis: str, theta: float) -> np.ndarray:
"""
建立三維旋轉矩陣
三維空間中的旋轉需要指定旋轉軸
Args:
axis: 旋轉軸 ('x', 'y', 'z')
theta: 旋轉角度(弧度)
Returns:
3x3 旋轉矩陣
"""
cos_t = np.cos(theta)
sin_t = np.sin(theta)
if axis == 'x':
# 繞 x 軸旋轉
return np.array([
[1, 0, 0],
[0, cos_t, -sin_t],
[0, sin_t, cos_t]
])
elif axis == 'y':
# 繞 y 軸旋轉
return np.array([
[cos_t, 0, sin_t],
[0, 1, 0],
[-sin_t, 0, cos_t]
])
elif axis == 'z':
# 繞 z 軸旋轉
return np.array([
[cos_t, -sin_t, 0],
[sin_t, cos_t, 0],
[0, 0, 1]
])
else:
raise ValueError(f"無效的旋轉軸: {axis}")
def demonstrate_rotation():
"""
展示旋轉變換的應用
"""
print("旋轉變換示範")
print("=" * 40)
# 二維旋轉
# 將向量旋轉 90 度(π/2 弧度)
theta = np.pi / 2
R = rotation_matrix_2d(theta)
# 原始向量指向 x 軸正方向
v = np.array([1, 0])
# 旋轉後的向量
v_rotated = R @ v
print("二維旋轉:")
print(f" 原始向量: {v}")
print(f" 旋轉角度: {np.degrees(theta):.0f} 度")
print(f" 旋轉後: {np.round(v_rotated, 10)}")
# 驗證旋轉矩陣的性質
print("\n旋轉矩陣性質驗證:")
# 正交性:R^T = R^(-1)
R_inv = np.linalg.inv(R)
R_T = R.T
print(f" R^T 近似等於 R^(-1): {np.allclose(R_T, R_inv)}")
# 行列式為 1
det_R = np.linalg.det(R)
print(f" det(R) = {det_R:.0f}")
# 保持向量長度
norm_original = np.linalg.norm(v)
norm_rotated = np.linalg.norm(v_rotated)
print(f" 原始長度: {norm_original}, 旋轉後長度: {norm_rotated:.10f}")
# 三維旋轉
print("\n三維旋轉:")
# 繞 z 軸旋轉 45 度
theta_3d = np.pi / 4
R_z = rotation_matrix_3d('z', theta_3d)
v_3d = np.array([1, 0, 0])
v_3d_rotated = R_z @ v_3d
print(f" 原始向量: {v_3d}")
print(f" 繞 z 軸旋轉 {np.degrees(theta_3d):.0f} 度")
print(f" 旋轉後: {np.round(v_3d_rotated, 4)}")
def demonstrate_other_transforms():
"""
展示其他常見的幾何變換
"""
print("\n其他幾何變換")
print("=" * 40)
# 縮放矩陣
# 在 x 和 y 方向分別縮放 2 倍和 0.5 倍
scale_x, scale_y = 2.0, 0.5
S = np.array([
[scale_x, 0],
[0, scale_y]
])
v = np.array([1, 1])
v_scaled = S @ v
print("縮放變換:")
print(f" 原始向量: {v}")
print(f" 縮放因子: x={scale_x}, y={scale_y}")
print(f" 縮放後: {v_scaled}")
# 剪切矩陣
# x 方向的剪切
shear = 0.5
Sh = np.array([
[1, shear],
[0, 1]
])
v_sheared = Sh @ v
print("\n剪切變換:")
print(f" 原始向量: {v}")
print(f" 剪切因子: {shear}")
print(f" 剪切後: {v_sheared}")
# 反射矩陣
# 關於 x 軸的反射
Ref_x = np.array([
[1, 0],
[0, -1]
])
v_test = np.array([1, 2])
v_reflected = Ref_x @ v_test
print("\n反射變換:")
print(f" 原始向量: {v_test}")
print(f" 關於 x 軸反射後: {v_reflected}")
# 執行示範
if __name__ == "__main__":
demonstrate_rotation()
demonstrate_other_transforms()
以下是常見幾何變換的矩陣形式示意圖:
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
package "二維線性變換" {
class "旋轉 R(θ)" as rotation {
[cos(θ) -sin(θ)]
[sin(θ) cos(θ)]
--
保持長度
保持夾角
det(R) = 1
}
class "縮放 S" as scaling {
[sx 0]
[0 sy]
--
改變長度
不改變方向
det(S) = sx*sy
}
class "剪切 Sh" as shear {
[1 k]
[0 1]
--
保持面積
改變形狀
det(Sh) = 1
}
class "反射 Ref" as reflection {
關於 x 軸:
[1 0]
[0 -1]
--
改變方向性
det(Ref) = -1
}
}
note bottom of rotation
旋轉是正交變換
R^T = R^(-1)
end note
note bottom of scaling
特殊情況:
- sx = sy 等比縮放
- sx = -1 反射
end note
@enduml特徵值分解與主成分分析基礎
特徵值和特徵向量是理解矩陣本質的關鍵概念。對於一個方陣 A,如果存在非零向量 v 和純量 λ,使得 Av = λv,則 λ 是 A 的特徵值,v 是對應的特徵向量。特徵分解將矩陣分解為特徵向量和特徵值的組合,這在主成分分析(PCA)、譜聚類等演算法中有重要應用。
import numpy as np
def demonstrate_eigendecomposition():
"""
展示特徵值分解及其應用
"""
print("特徵值分解")
print("=" * 40)
# 建立一個對稱矩陣
# 對稱矩陣的特徵值都是實數,特徵向量彼此正交
A = np.array([
[4, 2],
[2, 3]
])
print(f"矩陣 A:\n{A}")
# 計算特徵值和特徵向量
eigenvalues, eigenvectors = np.linalg.eig(A)
print(f"\n特徵值: {eigenvalues}")
print(f"\n特徵向量:\n{eigenvectors}")
# 驗證特徵方程 Av = λv
print("\n驗證特徵方程 Av = λv:")
for i in range(len(eigenvalues)):
lam = eigenvalues[i]
v = eigenvectors[:, i]
# 計算 Av 和 λv
Av = A @ v
lambda_v = lam * v
print(f" λ_{i+1} = {lam:.4f}")
print(f" v_{i+1} = {v}")
print(f" Av = {Av}")
print(f" λv = {lambda_v}")
print(f" 相等: {np.allclose(Av, lambda_v)}\n")
# 使用特徵分解重建矩陣
# A = VΛV^(-1),其中 V 是特徵向量矩陣,Λ 是特徵值對角矩陣
V = eigenvectors
Lambda = np.diag(eigenvalues)
V_inv = np.linalg.inv(V)
A_reconstructed = V @ Lambda @ V_inv
print(f"重建的矩陣:\n{np.round(A_reconstructed, 10)}")
print(f"重建成功: {np.allclose(A, A_reconstructed)}")
def simple_pca_demo():
"""
展示主成分分析的基本概念
"""
print("\n主成分分析基礎")
print("=" * 40)
# 生成二維資料
# 兩個變數有正相關
np.random.seed(42)
n = 100
# 生成相關的資料
mean = [0, 0]
cov = [[1, 0.8], [0.8, 1]] # 共變異數矩陣,相關係數 0.8
data = np.random.multivariate_normal(mean, cov, n)
print(f"資料維度: {data.shape}")
print(f"資料均值: {data.mean(axis=0)}")
# 步驟 1:中心化資料
# 減去均值使資料以原點為中心
data_centered = data - data.mean(axis=0)
# 步驟 2:計算共變異數矩陣
cov_matrix = np.cov(data_centered.T)
print(f"\n共變異數矩陣:\n{cov_matrix}")
# 步驟 3:計算特徵值和特徵向量
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)
# 按特徵值大小排序
idx = eigenvalues.argsort()[::-1]
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:, idx]
print(f"\n特徵值(降序): {eigenvalues}")
print(f"\n主成分方向:\n{eigenvectors}")
# 計算解釋變異比例
total_var = eigenvalues.sum()
explained_var_ratio = eigenvalues / total_var
cumulative_var_ratio = np.cumsum(explained_var_ratio)
print("\n解釋變異:")
for i, (var_ratio, cum_ratio) in enumerate(zip(explained_var_ratio, cumulative_var_ratio)):
print(f" PC{i+1}: {var_ratio*100:.2f}% (累積: {cum_ratio*100:.2f}%)")
# 步驟 4:投影資料到主成分
# 將資料投影到第一主成分
pc1 = eigenvectors[:, 0]
projected = data_centered @ pc1
print(f"\n投影到第一主成分後的資料維度: {projected.shape}")
print(f"投影資料的變異數: {projected.var():.4f}")
print(f"第一主成分的特徵值: {eigenvalues[0]:.4f}")
return data, eigenvectors, eigenvalues
# 執行示範
if __name__ == "__main__":
demonstrate_eigendecomposition()
data, eigenvectors, eigenvalues = simple_pca_demo()
主成分分析(PCA)是一種降維技術,它找出資料中變異最大的方向。這些方向就是共變異數矩陣的特徵向量,對應的特徵值表示該方向上的變異量。透過保留最大的幾個特徵值對應的主成分,可以在保留大部分資訊的同時減少資料的維度。
Pandas DataFrame 的矩陣操作
Pandas DataFrame 可以視為帶有標籤的矩陣,它結合了 NumPy 的數值計算能力和資料庫的資料管理功能。理解 DataFrame 與矩陣的關係,可以更有效地利用 Pandas 進行資料分析。
import pandas as pd
import numpy as np
def demonstrate_dataframe_matrix_operations():
"""
展示 DataFrame 的矩陣運算
"""
print("DataFrame 矩陣運算")
print("=" * 40)
# 建立 DataFrame
# DataFrame 可以視為帶有行列標籤的矩陣
df = pd.DataFrame({
'A': [1, 2, 3],
'B': [4, 5, 6],
'C': [7, 8, 9]
}, index=['x', 'y', 'z'])
print("原始 DataFrame:")
print(df)
# 取得底層的 NumPy 陣列
matrix = df.values # 或 df.to_numpy()
print(f"\n底層矩陣:\n{matrix}")
print(f"型別: {type(matrix)}")
# 矩陣轉置
# DataFrame 的 T 屬性返回轉置的 DataFrame
df_T = df.T
print(f"\n轉置:\n{df_T}")
# 矩陣乘法
# 使用 @ 運算符或 dot 方法
df1 = pd.DataFrame({
'a': [1, 2],
'b': [3, 4]
})
df2 = pd.DataFrame({
'x': [5, 6],
'y': [7, 8]
})
result = df1 @ df2.values # 將 df2 轉換為陣列以進行矩陣乘法
print(f"\n矩陣乘法結果:\n{result}")
# 協方差矩陣
# cov() 方法計算 DataFrame 各欄位間的協方差
cov_matrix = df.cov()
print(f"\n協方差矩陣:\n{cov_matrix}")
# 相關係數矩陣
# corr() 方法計算 Pearson 相關係數
corr_matrix = df.corr()
print(f"\n相關係數矩陣:\n{corr_matrix}")
def analyze_stock_correlations():
"""
使用 DataFrame 分析股票相關性
"""
print("\n股票相關性分析")
print("=" * 40)
# 生成模擬的股票報酬率資料
np.random.seed(42)
n_days = 100
# 模擬三檔股票的報酬率
# 設定不同的相關性
base_returns = np.random.normal(0, 0.02, n_days)
returns = pd.DataFrame({
'TECH': base_returns + np.random.normal(0, 0.01, n_days),
'BANK': base_returns * 0.5 + np.random.normal(0, 0.015, n_days),
'UTIL': np.random.normal(0.001, 0.01, n_days) # 較不相關
})
print("報酬率統計:")
print(returns.describe())
# 計算相關係數矩陣
corr = returns.corr()
print(f"\n相關係數矩陣:\n{corr}")
# 計算協方差矩陣
cov = returns.cov()
print(f"\n協方差矩陣:\n{cov}")
# 使用特徵分解分析風險結構
eigenvalues, eigenvectors = np.linalg.eig(cov.values)
# 排序
idx = eigenvalues.argsort()[::-1]
eigenvalues = eigenvalues[idx]
eigenvectors = eigenvectors[:, idx]
print("\n風險結構分析 (主成分):")
total_var = eigenvalues.sum()
for i, val in enumerate(eigenvalues):
print(f" PC{i+1}: 變異數 = {val:.6f}, 解釋比例 = {val/total_var*100:.1f}%")
# 主成分載荷
loadings = pd.DataFrame(
eigenvectors,
index=returns.columns,
columns=[f'PC{i+1}' for i in range(len(eigenvalues))]
)
print(f"\n主成分載荷:\n{loadings}")
return returns, corr, cov
# 執行示範
if __name__ == "__main__":
demonstrate_dataframe_matrix_operations()
returns, corr, cov = analyze_stock_correlations()
這個範例展示了如何將線性代數的概念應用於實際的資料分析。相關係數矩陣是標準化後的協方差矩陣,它提供了變數間線性關係的度量。主成分分析可以揭示資料中的隱藏結構,例如在股票報酬率中識別共同的風險因子。
資料科學工作流程的最佳實踐
在結束本文之前,值得總結一些資料科學工作流程中的最佳實踐。這些實踐不僅能提高工作效率,也能確保分析結果的可重現性和可靠性。
首先是資料處理的管線化。將資料處理步驟組織成清晰的管線,每個步驟都有明確的輸入和輸出。這樣做可以方便調試、測試和修改。Pandas 的方法鏈(method chaining)是實現管線化的好方法。
其次是型別檢查和資料驗證。在處理資料之前,驗證資料的型別、範圍和完整性。及早發現資料問題可以避免在分析後期遇到難以追蹤的錯誤。
第三是向量化運算。盡量使用 NumPy 和 Pandas 的向量化操作,而不是 Python 的迴圈。向量化操作利用底層的優化庫,速度可以快上百倍。
第四是隨機種子的控制。在涉及隨機操作的程式碼中,設定隨機種子以確保結果可重現。這對於調試和報告都很重要。
最後是文件和註解。清晰的文件和註解不僅幫助他人理解你的程式碼,也幫助未來的自己。特別是對於複雜的數學運算,說明所用的公式和假設是非常必要的。
透過掌握這些 Python 資料科學基礎和線性代數知識,讀者將能夠更自信地進行資料分析工作,並為學習更進階的機器學習技術打下堅實的基礎。Python 和線性代數的結合,提供了一個強大而靈活的工具集,能夠處理從簡單的數據清理到複雜的統計建模的各種任務。