Pandas 提供了 Group By 和視窗函式等強大工具,方便我們對資料進行更精細的分析。Group By 可以根據指定的欄位將資料分組,並對每個分組應用聚合函式,例如計算總和、平均值等。視窗函式則允許我們在資料的不同子集上進行計算,例如計算移動平均、移動總和等,這在時間序列分析中特別有用。本文將會示範如何使用這些函式來分析股票價格走勢、計算電影評分以及處理棒球資料。我們也會探討如何使用自訂函式來滿足更複雜的分析需求,以及如何使用 pd.Grouper 處理時間序列資料。最後,我們還會使用視覺化工具來呈現分析結果,讓資料更容易理解和解讀。

分組與視窗運算:深入解析pandas中的Group By與視窗函式

在資料分析與處理中,pandas函式庫提供了強大的分組(Group By)與視窗運算(Window Operations)功能,讓我們能夠更靈活地對資料進行聚合運算與滑動計算。本文將深入探討這兩個重要概念,並結合實際範例展示其應用。

分組運算(Group By)與自訂函式

當我們需要對資料進行分組並套用自訂函式時,pandas的DataFrameGroupBy.apply()方法提供了極大的彈性。這個方法允許我們對每個分組套用任意函式,並根據函式的回傳值推斷最合適的輸出結構。

import pandas as pd

# 建立範例資料
data = {
    'group': ['group_a', 'group_a', 'group_b', 'group_b'],
    'value': [1, 2, 3, 4]
}
df = pd.DataFrame(data)

# 定義自訂函式
def custom_function(group):
    return group['value'].sum()

# 分組並套用自訂函式
result = df.groupby('group', observed=True).apply(custom_function, include_groups=False)
print(result)

程式碼解析:

  1. 我們首先建立了一個包含分組欄位和數值欄位的DataFrame。
  2. 定義了一個簡單的自訂函式custom_function,用於計算每個分組的數值總和。
  3. 使用groupby()方法對資料進行分組,並透過apply()套用自訂函式。
  4. include_groups=False引數確保分組欄位不會包含在運算中。

視窗運算(Window Operations)

視窗運算允許我們在資料的不同子集上進行計算,常見的應用包括移動平均、移動總和等。pandas提供了兩種主要的視窗運算:滾動視窗(Rolling Window)和擴充套件視窗(Expanding Window)。

滾動視窗(Rolling Window)

滾動視窗運算是根據固定大小的視窗進行計算。

# 建立範例序列
ser = pd.Series([0, 1, 2, 4, 8, 16], dtype=pd.Int64Dtype())

# 進行滾動視窗求和
rolling_sum = ser.rolling(2).sum().astype(pd.Int64Dtype())
print(rolling_sum)

程式碼解析:

  1. 建立了一個包含2的冪次方的序列。
  2. 使用rolling(2)建立大小為2的滾動視窗。
  3. 套用sum()函式計算視窗內的總和。
  4. 將結果轉換為Int64Dtype以保持資料型別的一致性。
  flowchart TD
    A[開始] --> B[建立序列]
    B --> C[定義滾動視窗大小]
    C --> D[進行滾動求和]
    D --> E[輸出結果]

圖表翻譯:

此圖示展示了滾動視窗運算的流程。首先建立輸入序列,接著定義滾動視窗的大小,然後進行滾動運算,最後輸出結果。這個過程清晰地說明瞭滾動視窗的工作原理。

擴充套件視窗(Expanding Window)

擴充套件視窗會考慮所有之前的資料點。

# 進行擴充套件視窗平均
expanding_mean = ser.expanding().mean().astype(pd.Float64Dtype())
print(expanding_mean)

程式碼解析:

  1. 使用expanding()建立擴充套件視窗。
  2. 套用mean()函式計算累積平均值。
  3. 將結果轉換為浮點數型別以處理小數。

實際應用:股票資料分析

讓我們使用輝達(Nvidia)股票資料來示範視窗運算的實際應用。

# 載入股票資料
df = pd.read_csv(
    "data/NVDA.csv",
    usecols=["Date", "Close"],
    parse_dates=["Date"],
    dtype_backend="numpy_nullable"
).set_index("Date")

# 計算不同週期的移動平均線
df_ma = df.assign(
    ma30=df["Close"].rolling(30).mean().astype(pd.Float64Dtype()),
    ma60=df["Close"].rolling(60).mean().astype(pd.Float64Dtype()),
    ma90=df["Close"].rolling(90).mean().astype(pd.Float64Dtype())
)

# 繪製股價與移動平均線
import matplotlib.pyplot as plt
df_ma.plot()
plt.show()

程式碼解析:

  1. 載入輝達股票的歷史收盤價資料。
  2. 使用滾動視窗計算30天、60天和90天的移動平均線。
  3. 將原始股價與各條移動平均線繪製在同一個圖表上。

隨著資料分析需求的日益複雜,我們可以期待以下發展方向:

  1. 更高效的運算效能:未來版本可能會進一步最佳化這些運算的效能。
  2. 更豐富的視窗函式:可能會支援更多自訂的視窗運算方式。
  3. 與其他函式庫的整合:例如與機器學習函式庫的結合,將視窗運算應用於預測分析。

這些發展將進一步擴充套件資料分析的可能性,為使用者提供更多創新的應用場景。透過結合視窗運算與分組運算,我們可以實作更複雜的資料分析任務,為商業決策提供更有力的支援。

Pandas GroupBy 操作詳解與實務應用

在資料分析中,Pandas 的 GroupBy 功能是一個強大且靈活的工具,用於對資料進行分組並套用各種聚合函式。本文將深入探討 GroupBy 的使用方法,並結合實際案例進行詳細說明。

GroupBy 基本概念

GroupBy 操作的核心思想是根據一個或多個鍵將資料分成多個群組,然後對每個群組套用聚合函式。常見的應用場景包括:

  • 按年份統計電影評分最高的值
  • 按季度分析股票價格的變化趨勢
  • 按類別計算產品的平均銷售量

時間序列資料的分組分析

對於時間序列資料,Pandas 提供了 pd.Grouper 函式,可以方便地按不同的時間頻率進行分組。例如:

import pandas as pd

# 建立示例資料
date_range = pd.date_range(start='2020-01-01', end='2023-12-31', freq='D')
df = pd.DataFrame({
    'date': date_range,
    'value': range(len(date_range))
})
df.set_index('date', inplace=True)

# 按年度進行分組並計算累積統計量
annual_stats = df.groupby(pd.Grouper(freq='YS')).expanding().agg(['min', 'max', 'mean'])

# 繪製結果
annual_stats.droplevel(axis=1, level=0).reset_index(level=0, drop=True).plot()

圖表翻譯:

此圖示展示了按年度分組的累積統計量變化趨勢。透過 pd.Grouper(freq='YS') 將資料按年度劃分,並使用 expanding() 計算每個年度內的累積最小值、最大值和平均值。圖表清晰地展示了不同年度間的資料變化情況。

電影資料分析例項

假設我們有一個包含電影資訊的資料集,現在需要找出每年評分最高的電影。以下是具體實作步驟:

  1. 資料準備:讀取 CSV 檔案並選擇需要的欄位
  2. 資料清理:將年份欄位轉換為整數型別
  3. 分組與排序:按年份分組並找出評分最高的電影
# 讀取資料並選擇需要的欄位
df = pd.read_csv('data/movie.csv', usecols=['movie_title', 'title_year', 'imdb_score'])

# 將年份轉換為整數型別
df['title_year'] = df['title_year'].astype(pd.Int16Dtype())

# 方法1:排序後分組取最後一個值
top_movies = df.sort_values(['title_year', 'imdb_score']).groupby('title_year')[['movie_title']].agg(top_rated_movie=pd.NamedAgg('movie_title', 'last'))

# 方法2:使用 idxmax 直接取得最高評分的電影
top_movies_idxmax = df.set_index('movie_title').groupby('title_year').agg(top_rated_movie=pd.NamedAgg('imdb_score', 'idxmax'))

自定義聚合函式

在某些情況下,預設的聚合函式無法滿足需求,這時可以自定義函式來實作更複雜的邏輯。例如:

def top_rated_movies(df):
    top_rating = df['imdb_score'].max()
    top_movies = df[df['imdb_score'] == top_rating]['movie_title'].unique()
    return top_movies[0] if len(top_movies) == 1 else top_movies

# 套用自定義函式
top_movies_custom = df.groupby('title_year').apply(top_rated_movies, include_groups=False).to_frame('top_rated_movie(s)')

程式碼解析

  1. pd.Grouper:用於按不同的時間頻率進行分組,如按年度(‘YS’)、季度(‘QS’)或月度(‘MS’)劃分資料。
  2. expanding().agg():計算累積統計量,支援多種聚合函式,如最小值、最大值和平均值。
  3. sort_values():對資料進行排序,為後續的分組操作做準備。
  4. idxmax:直接取得每組中最大值對應的索引,適用於快速定位最高評分的電影。
  5. 自定義函式:透過定義函式實作更靈活的聚合邏輯,如處理評分相同的多部電影情況。

視覺化呈現

為了更直觀地展示分析結果,可以使用 Matplotlib 或其他視覺化工具繪製相關圖表。例如:

  flowchart TD
    A[資料讀取] --> B[資料清理]
    B --> C[分組操作]
    C --> D{是否需要自定義聚合函式}
    D -->|是| E[定義並套用自定義函式]
    D -->|否| F[使用內建聚合函式]
    E --> G[結果分析與視覺化]
    F --> G

圖表翻譯:

此流程圖展示了使用 Pandas 進行資料分析的完整流程。從資料讀取開始,經過資料清理後進行分組操作。根據需求選擇是否使用自定義聚合函式,最終對結果進行分析並視覺化展示。圖表清晰地展示了不同步驟之間的邏輯關係。

棒球資料分析:計算年度打擊率

在棒球資料分析中,打擊率(Batting Average)是一個重要的指標,用於評估球員的打擊表現。本文將介紹如何使用Python和Pandas函式庫來計算球員的年度打擊率,並探討資料品質問題對計算結果的影響。

資料準備

首先,我們需要載入包含棒球比賽資料的資料集。資料集以Parquet格式儲存,包含2000年至2023年間的棒球比賽紀錄。

import pandas as pd

# 載入資料集
df = pd.read_parquet("data/mlb_batting_lines.parquet")
print(df.head())

資料解讀:

此程式碼區塊用於載入棒球比賽資料。pd.read_parquet函式從指定路徑讀取Parquet檔案,並將資料儲存在DataFrame物件df中。print(df.head())用於顯示資料的前幾行,以確認資料載入正確。

資料集介紹

資料集包含了每場比賽中球員的表現紀錄。每行代表一位球員在某場比賽中的資料。資料集的部分欄位如下:

  • year:比賽年份
  • game:比賽ID
  • starttime:比賽開始時間
  • ab:打席次數(At Bats)
  • h:安打次數(Hits)
  • hr:全壘打次數(Home Runs)

計算年度打擊率

要計算球員的年度打擊率,我們需要對資料進行分組匯總。首先,按yearid(球員ID)進行分組,然後計算每位球員每年的總打席次數和總安打次數。

# 按年份和球員ID分組,計算總打席次數和總安打次數
grouped_df = df.groupby(["year", "id"]).agg(
    total_ab=pd.NamedAgg(column="ab", aggfunc="sum"),
    total_h=pd.NamedAgg(column="h", aggfunc="sum"),
)
print(grouped_df.head())

資料解讀:

此程式碼區塊對資料進行分組匯總。groupby(["year", "id"])將資料按年份和球員ID分組,agg函式計算每組的總打席次數(total_ab)和總安打次數(total_h)。

計算打擊率

有了總打席次數和總安打次數後,我們可以計算打擊率。打擊率的計算公式為:安打次數 / 打席次數。

# 計算打擊率並刪除中間結果
batting_avg = grouped_df.assign(avg=lambda x: x["total_h"] / x["total_ab"]).drop(columns=["total_ab", "total_h"])
print(batting_avg.head())

資料解讀:

此程式碼區塊計算打擊率。assign函式新增一個欄位avg,其值為total_h除以total_ab。隨後,drop函式刪除不再需要的欄位。

資料品質問題

在計算打擊率時,可能會遇到一些資料品質問題。例如,有些球員的打席次數很少,這可能導致打擊率的計算結果不準確。此外,如果某位球員沒有任何打席紀錄,則會出現除以零的錯誤。

# 篩選出打席次數大於400的球員
qualified_batters = grouped_df.loc[lambda df: df["total_ab"] > 400].assign(avg=lambda x: x["total_h"] / x["total_ab"]).drop(columns=["total_ab", "total_h"])
print(qualified_batters.head())

資料解讀:

此程式碼區塊篩選出打席次數大於400的球員,以確保計算出的打擊率具有參考價值。

Mermaid圖表:資料處理流程

  flowchart TD
    A[載入資料] --> B[分組匯總]
    B --> C[計算打擊率]
    C --> D{篩選合格球員}
    D -->|是| E[輸出結果]
    D -->|否| F[忽略]

圖表翻譯:

此圖示展示了資料處理的流程。首先,載入棒球比賽資料。接著,按年份和球員ID進行分組匯總,計算總打席次數和總安打次數。然後,計算打擊率。最後,篩選出打席次數大於400的球員,並輸出結果。

資料分組分析與標準化處理

在資料分析過程中,分組匯總是一種常見且重要的操作。本章節將深入探討如何利用pandas的Group By功能對棒球選手的打擊資料進行深入分析,並透過標準化處理來比較不同年度的表現。

資料準備與初步分析

首先,我們需要對資料進行分組匯總,以計算各年度選手的打擊率。以下程式碼展示瞭如何實作這一點:

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

程式碼解析

此段程式碼首先根據yearid進行分組,然後計算每個選手的總打數和總安打數。接著,篩選出總打數大於400的選手,並計算其打擊率。最後,刪除中間計算過程中的臨時欄位。

年度最佳打擊表現分析

進一步分析各年度的最佳打擊表現,可以透過以下程式碼實作:

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")
)

分析結果解讀

此分析結果顯示了各年度的平均打擊率、最高打擊率及其對應的選手身份。透過比較不同年度的資料,可以發現打擊表現的變化趨勢。例如,2000年的平均打擊率為0.284,而2019年的平均打擊率下降至0.269,但兩年度的最佳打擊率均為0.335。

資料視覺化

為了更直觀地展示各年度打擊率的分佈情況,我們可以使用小提琴圖進行視覺化:

import matplotlib.pyplot as plt
import seaborn as sns

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()

圖表解析

小提琴圖清晰地展示了2000-2009年間各年度打擊率的分佈情況。透過設定x軸的範圍,可以確保不同年度的比較是在相同的尺度下進行。

標準化處理

為了更公平地比較不同年度選手的表現,我們引入了Z-score標準化:

def normalize(ser: pd.Series) -> pd.Series:
    return (ser - ser.mean()) / ser.std()

averages.assign(
    normalized_avg=averages.groupby("year").transform(normalize)
).groupby("year").agg(
    max_normalized_avg=pd.NamedAgg(column="normalized_avg", aggfunc="max")
)

標準化結果分析

透過標準化處理,我們可以發現2023年的選手錶現最為突出。這種方法有效地消除了不同年度間因整體水平差異所帶來的影響,使得比較更具參考價值。

圖表說明

此流程圖展示了整個資料分析的過程,從資料準備到最終的結果分析,每一步都清晰地展示了資料處理的流程和邏輯關係。透過這個流程圖,讀者可以快速理解資料分析的整體框架和關鍵步驟。

透過上述分析,我們不僅深入理解了棒球選手的打擊表現變化,還掌握了利用pandas進行高效資料分析的方法。這些技術和方法具有很高的實用價值,可以應用於多個領域的資料分析工作中。