返回文章列表

Python 資料分析:滑動與擴充套件視窗技術應用

本文探討滑動視窗和擴充套件視窗在 Python 資料分析中的應用,使用 Pandas 和 Matplotlib 計算移動平均、年內最高最低值等指標,並以股票價格和棒球打擊率分析為例,輔以視覺化圖表,展現這些技術的實用性。

資料分析 Python

滑動視窗和擴充套件視窗是資料分析中常用的技術,能有效地計算各種指標,例如移動平均、年內最高最低值等。這些技術在金融市場分析、銷售趨勢預測等領域都有廣泛的應用。本文將以 Python 的 Pandas 和 Matplotlib 函式庫為工具,示範如何運用滑動視窗和擴充套件視窗技術進行資料分析和視覺化。首先,我們會使用滑動視窗計算股票收盤價的移動平均線,藉此平滑價格波動並觀察趨勢。接著,我們會運用擴充套件視窗計算股票的年內最高、最低和平均收盤價,以便追蹤年度績效。最後,我們會將這些技術應用於棒球打擊率分析,比較不同年份的最佳打擊手,並使用小提琴圖視覺化每個賽季的打擊率分佈,以深入瞭解資料的特性。

資料分析與視覺化:滑動視窗與擴充套件視窗的應用

在資料分析中,滑動視窗(rolling window)和擴充套件視窗(expanding window)是兩個非常強大的工具,能夠幫助我們計算移動平均、年內最高最低值等指標。這些工具不僅能夠提供深入的資料洞察,還能夠輔助我們進行更精確的決策。玄貓將探討這些技術的應用,並提供實際案例來說明其操作方法。

滑動視窗與移動平均

滑動視窗是一種在固定長度範圍內進行計算的方法。例如,我們可以使用滑動視窗來計算30天、60天和90天的移動平均。這對於股票價格分析非常有用,因為它能夠平滑價格波動,幫助我們更清楚地看到趨勢。

首先,我們需要安裝所需的函式庫:

import pandas as pd
import matplotlib.pyplot as plt

接著,讀取資料並計算移動平均:

# 假設 df 是我們的資料框,並且已經包含 "Close" 欄位
df = pd.read_csv('stock_data.csv')  # 讀取股票資料

# 計算30天、60天和90天的移動平均
df['ma30'] = df['Close'].rolling(window=30).mean()
df['ma60'] = df['Close'].rolling(window=60).mean()
df['ma90'] = df['Close'].rolling(window=90).mean()

# 繪製圖表
plt.figure(figsize=(14, 7))
plt.plot(df['Close'], label='Close Price')
plt.plot(df['ma30'], label='30-Day Moving Average')
plt.plot(df['ma60'], label='60-Day Moving Average')
plt.plot(df['ma90'], label='90-Day Moving Average')
plt.legend()
plt.show()

內容解密:

上述程式碼首先讀取了股票資料並計算了30天、60天和90天的移動平均。rolling(window=30).mean() 表示在每個時間點上計算前30天的平均值。接著,使用 matplotlib 則是繪製出這些移動平均線和收盤價,幫助我們更直觀地觀察趨勢。

年內最高最低值與擴充套件視窗

擴充套件視窗(expanding window)是另一種強大的工具,它能夠在每個時間點上計算從起始點到當前點的所有資料。這對於計算年內或季度內的最高最低值非常有用。

首先,我們需要使用 pd.Grouper 來對資料進行分組:

# 計算年內的最小、最大和平均收盤價
yearly_stats = df.groupby(pd.Grouper(freq='YS')).expanding().agg(['min', 'max', 'mean'])

# 繪製圖表
yearly_stats.drop(level=0, axis=1).plot(figsize=(14, 7))
plt.title('Year-to-Date Min, Max, and Mean Close Prices')
plt.show()

內容解密:

上述程式碼首先使用 pd.Grouper(freq='YS') 對資料進行按年分組。接著,expanding().agg(['min', 'max', 'mean']) 則是在每個時間點上計算從起始點到當前點的所有資料的最小值、最大值和平均值。最後,使用 matplotlib 則是繪製出這些統計資料,幫助我們更清楚地觀察每年的收盤價變化。

月份與季度分析

除了年份分析外,我們還可以對月份和季度進行類別似的操作。例如,如果我們想要計算每個季度的最高最低收盤價,可以這樣做:

# 計算季度內的最小、最大和平均收盤價
quarterly_stats = df.groupby(pd.Grouper(freq='QS')).expanding().agg(['min', 'max', 'mean'])

# 繪製圖表
quarterly_stats.drop(level=0, axis=1).plot(figsize=(14, 7))
plt.title('Quarter-to-Date Min, Max, and Mean Close Prices')
plt.show()

內容解密:

上述程式碼與年份分析類別似,但這次我們使用 pd.Grouper(freq='QS') 對資料進行按季度分組。接著,expanding().agg(['min', 'max', 'mean']) 則是在每個季度內計算從起始點到當前點的所有資料的最小值、最大值和平均值。最後,使用 matplotlib 則是繪製出這些統計資料。

動態圖示

使用Plantuml可以方便地呈現流程圖或架構圖。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Python 資料分析:滑動與擴充套件視窗技術應用

package "資料視覺化流程" {
    package "資料準備" {
        component [資料載入] as load
        component [資料清洗] as clean
        component [資料轉換] as transform
    }

    package "圖表類型" {
        component [折線圖 Line] as line
        component [長條圖 Bar] as bar
        component [散佈圖 Scatter] as scatter
        component [熱力圖 Heatmap] as heatmap
    }

    package "美化輸出" {
        component [樣式設定] as style
        component [標籤註解] as label
        component [匯出儲存] as export
    }
}

load --> clean --> transform
transform --> line
transform --> bar
transform --> scatter
transform --> heatmap
line --> style --> export
bar --> label --> export

note right of scatter
  探索變數關係
  發現異常值
end note

@enduml

此圖示展示了從讀取資料到繪製年度統計圖表的整個流程。首先讀取資料後,計算滑動視窗以得到移動平均線;接著將資料分組成年度資料;然後在每個時間點上計算擴充套件視窗;最後繪製年度統計圖表。

做結

透過滑動視窗和擴充套件視窗技術,我們可以更深入地分析股票市場中的趨勢和波動。這些工具不僅能夠幫助我們更清楚地觀察市場行為,還能夠提供更精確的預測模型。玄貓希望這些實際案例能夠幫助大家更好地理解和應用這些技術。

在實際應用中,建議結合其他技術指標和機器學習模型來進行更全面的市場分析。此外,保持對市場趨勢和政策變化的敏感性也是至關重要的。

利用群組統計方法分析電影評分及棒球打擊率

解決電影評分分析中的爭議點

在電影評分分析中,不同的方法可能會對於某些年份的最高評分電影產生不同的結論。例如,在2012年和2014年的情況中,兩種方法對於最高評分電影的選擇產生了爭議。這是由於當出現平局時,每種方法都有其獨特的處理方式。這些處理方式各有優缺點,並沒有絕對正確或錯誤之分。然而,如果我們需要更細緻的控制,可以使用groupbyapply來實作。

以下是具體的例子:

import pandas as pd

# 假設我們有一個包含電影評分的DataFrame
data = {
    "movie_title": ["The Dark Knight Rises", "Django Unchained", "Queen of the Mountains", "Butterfly Girl"],
    "title_year": [2012, 2012, 2014, 2014],
    "imdb_score": [8.5, 8.5, 8.7, 8.7]
}
df = pd.DataFrame(data)

# 自定義函式來處理每個年份的最高評分電影
def top_rated(df: pd.DataFrame):
    top_rating = df["imdb_score"].max()
    top_rated = df[df["imdb_score"] == top_rating]["movie_title"].unique()
    if len(top_rated) == 1:
        return top_rated[0]
    else:
        return top_rated

# 使用groupby和apply來應用上述函式
result = df.groupby("title_year").apply(top_rated, include_groups=False).to_frame().rename(columns={0: "top_rated_movie(s)"})

print(result)

內容解密:

在這段程式碼中,我們首先定義了一個包含電影評分資料的DataFrame。接著,我們自定義了一個函式top_rated,這個函式接受一個DataFrame作為引數,並且會找出該年份中最高評分的電影。如果有多部電影評分相同,則會傳回這些電影的列表。

在主程式中,我們使用groupby將DataFrame按照年份進行群組化,然後應用我們自定義的top_rated函式來計算每個年份的最高評分電影。最終結果會以DataFrame形式傳回,顯示每個年份的最高評分電影。

這樣的方法可以讓我們更靈活地處理不同情況下的最高評分電影選擇。

比較棒球選手打擊率

在棒球統計中,比較不同年份最佳打擊手之間的表現是一個挑戰性問題。單純地依賴打擊率(batting average)來比較可能會因為年度間打擊率標準不同而產生誤導。因此,我們需要考慮更多因素來進行比較。

資料準備

首先,我們從Retrosheet取得從2000年到2023年的棒球比賽記錄。這些記錄包含了每場比賽中每位選手的表現資料。

df = pd.read_parquet("data/mlb_batting_lines.parquet")
print(df.head())

內容解密:

這段程式碼展示瞭如何從parquet檔案讀取棒球比賽記錄資料。這些資料包括了每場比賽中的詳細記錄,如出場次數、安打次數等。

計算年度打擊率

接下來,我們需要將這些詳細記錄整合成年度總計,並計算每位選手的年度打擊率。

# 使用groupby將資料按照年份和選手ID進行群組化
annual_stats = df.groupby(["year", "id"]).agg(
    total_ab=pd.NamedAgg(column="ab", aggfunc="sum"),
    total_h=pd.NamedAgg(column="h", aggfunc="sum")
)

# 計算打擊率
batting_averages = annual_stats.assign(avg=lambda x: x["total_h"] / x["total_ab"]).drop(columns=["total_ab", "total_h"])

print(batting_averages.head())

內容解密:

在這段程式碼中,我們首先使用groupby將資料按照年份和選手ID進行群組化,並且對於每個群組計算總出場次數和總安打次數。接著,我們使用assign來計算打擊率(安打次數除以出場次數),並且使用drop移除不再需要的列。

結果

最終結果展示了每位選手在每年的打擊率。這些資料可以幫助我們進行更深入的分析和比較。

下一步

接下來,我們可以進一步探討如何將這些結果應用到更複雜的模型中去,或者如何結合更多外部資料來進行更深入的分析。

打擊率資料分析與視覺化

在進行打擊率(batting average)分析時,資料品質問題必須考慮。一個棒球賽季中,球隊可能會使用只在特定情境下出賽的球員,這些球員的上場次數(plate appearances)可能非常少。有些擊球手甚至可能整個賽季都沒有「上場打擊」(at bat),這會導致除以零的情況,從而產生 NaN 值。即使有上場打擊,但次數相對較少的情況下,小樣本可能會嚴重扭曲打擊率。

美國職業棒球大聯盟(Major League Baseball)有嚴格規定,決定一名擊球手在某一年內是否符合記錄資格需要多少上場次數。我們可以代理這個規則,要求至少 400 次上場打擊來過濾資料。以下是程式碼範例:

df = df.groupby(["year", "id"]).agg(
    total_ab=pd.NamedAgg(column="ab", aggfunc="sum"),
    total_h=pd.NamedAgg(column="h", aggfunc="sum")
).loc[lambda df: df["total_ab"] > 400].assign(
    avg=lambda x: x["total_h"] / x["total_ab"]
).drop(columns=["total_ab", "total_h"])

內容解密:

  • groupby(["year", "id"]):根據年度和球員 ID 分組。
  • agg:聚合函式,計算每個分組的總上場打擊次數(total_ab)和總安打次數(total_h)。
  • .loc[lambda df: df["total_ab"] > 400]:篩選出上場打擊次數超過 400 次的球員。
  • assign(avg=lambda x: x["total_h"] / x["total_ab"]):計算打擊率,即安打次數除以上場打擊次數。
  • drop(columns=["total_ab", "total_h"]):移除不需要的列。

篩選後的結果如下:

| 年份 | 球員 ID | 打擊率 | |


|



|


-| | 2000 | abreb001 | 0.315972 | | 2000 | alfoe001 | 0.323529 | | … | … | … | | 2023 | walkc002 | 0.257732 |

接下來,我們進一步總結每個賽季的平均和最高打擊率:

averages = df.groupby(["year", "id"]).agg(
    total_ab=pd.NamedAgg(column="ab", aggfunc="sum"),
    total_h=pd.NamedAgg(column="h", aggfunc="sum")
).loc[lambda df: df["total_ab"] > 400].assign(
    avg=lambda x: x["total_h"] / x["total_ab"]
).drop(columns=["total_ab", "total_h"])
results = averages.groupby("year").agg(
    league_mean_avg=pd.NamedAgg(column="avg", aggfunc="mean"),
    league_max_avg=pd.NamedAgg(column="avg", aggfunc="max"),
    batting_champion=pd.NamedAgg(column="avg", aggfunc="idxmax")
)

內容解密:

  • averages.groupby("year").agg(...):根據年度進行分組,計算每年聯盟的平均打擊率(league_mean_avg)、最高打擊率(league_max_avg)以及最高打擊率的球員(batting_champion)。

結果如下:

| 年份 | 聯盟平均打擊率 | 聯盟最高打擊率 | 最佳打者 | |


|




-|




-|




| | 2000 | 0.284512 | 0.372414 | (2000, heltt001) | | … | … | … | … | | 2023 | 0.261457 | 0.353659 | (2023, arral001) |

從結果可以看出,每年的聯盟平均打擊率波動較大。例如,2019 年的聯盟平均打擊率為 .269,而當年的最佳打者 Tim Anderson 的打擊率為 .335。相比之下,Derrek Lee 在 2005 年的表現也達到了 .335,但當年的聯盟平均打擊率為 .277。因此,Tim Anderson 的表現相對於當時整體聯盟來說更加出色。

資料視覺化

僅計算平均值並不足以完整描述每個賽季內部的情況。我們可以使用小提琴圖(violin plot)來更詳細地理解每個賽季的打擊率分佈情況。首先,我們需要進行一些準備工作:

import matplotlib.pyplot as plt
import seaborn as sns

# 啟動互動模式
plt.ion()

# 準備資料
sns_df = averages.reset_index()
years = sns_df["year"].unique()
cat = pd.CategoricalDtype(sorted(years), ordered=True)
sns_df["year"] = sns_df["year"].astype(cat)

內容解密:

  • import matplotlib.pyplot as pltimport seaborn as sns:匯入繪相簿。
  • plt.ion():啟動互動模式。
  • sns_df = averages.reset_index():將索引轉換為資料列。
  • years = sns_df["year"].unique():取得唯一年份。
  • cat = pd.CategoricalDtype(sorted(years), ordered=True):建立排序類別型別。
  • sns_df["year"] = sns_df["year"].astype(cat):將年份轉換為類別型別。

接下來,我們繪製小提琴圖來展示不同年份的打擊率分佈:

mask = (sns_df["year"] >= 2000) & (sns_df["year"] < 2010)
fig, ax = plt.subplots()
sns.violinplot(
    data=sns_df[mask],
    ax=ax,
    x="avg",
    y="year",
    order=sns_df.loc[mask, "year"].unique()
)
ax.set_xlim(0.15, 0.4)
plt.show()
mask = sns_df["year"] >= 2010
fig, ax = plt.subplots()
sns.violinplot(
    data=sns_df[mask],
    ax=ax,
    x="avg",
    y="year",
    order=sns_df.loc[mask, "year"].unique()
)
ax.set_xlim(0.15, 0.4)
plt.show()

內容解密:

  • mask:篩選出指定年份範圍的資料。
  • fig, ax = plt.subplots():建立繪圖區域。
  • sns.violinplot(...):繪製小提琴圖。
  • ax.set_xlim(0.15, 0.4):設定 x 軸範圍。

透過這些視覺化工具,我們可以更直觀地理解每個賽季的打擊率分佈情況。例如,有些年份的資料分佈可能會偏斜(如 2014 年偏右、2018 年偏左),但整體來看,這些資料大致符合正態分佈。

小提琴圖示

此圖示展示了不同年份的小提琴圖,用於分析各年度之間的打擊率分佈情況。X 軸表示打擊率,Y 軸表示年份。

流程圖示:
graph TD;
    A[匯入資料] --> B[準備資料];
    B --> C[篩選年份];
    C --> D[繪製小提琴圖];

分析:

  • 資料匯入與準備:首先匯入並準備資料。
  • 年份篩選:篩選出特定年份範圍內的資料。
  • 繪製小提琴圖:使用 Seaborn 函式庫繪製小提琴圖。

透過這些步驟和視覺化工具,我們可以更全面地理解每個賽季內部的變化及其對整體表現的影響。