在資料處理過程中,經常需要對 DataFrame 進行多個步驟的操作,傳統方法容易造成程式碼冗長或難以閱讀。Pandas 提供的 pipe 方法能有效改善這個問題,它允許將多個處理步驟組合成清晰的管道,提升程式碼可讀性和維護性。搭配 assign 方法可以更簡潔地修改 DataFrame 欄位。此外,nlargest 和 nsmallest 方法則提供了方便的 Top N 分析功能,例如快速找出資料集中最大或最小的 N 筆資料,這在實際資料分析中非常實用。這些技巧都能有效提升 Pandas 程式碼的效率和可讀性。
資料處理流程的最佳實踐:使用 pandas 管道(pipe)提升程式碼可讀性
在資料分析過程中,我們經常需要對 DataFrame 進行多步驟的處理。傳統的做法是將每個步驟的結果指定給一個新的變數,或是將函式呼叫巢狀在一起。然而,這些方法會使程式碼變得冗長或難以閱讀。pandas 提供的 pipe 方法可以將多個處理步驟組合成一個清晰的管道(pipeline),提升程式碼的可讀性和維護性。
使用 pipe 方法串接資料處理步驟
首先,我們定義幾個簡單的函式來修改 DataFrame 中的資料欄位。每個函式都接受一個 DataFrame 並傳回一個新的 DataFrame。
import pandas as pd
def change_col1(df: pd.DataFrame) -> pd.DataFrame:
return df.assign(col1=pd.Series([4, 5, 6], dtype=pd.Int64Dtype()))
def change_col2(df: pd.DataFrame, str_case: str = "upper") -> pd.DataFrame:
if str_case == "upper":
values = ["X", "Y", "Z"]
else:
values = ["x", "y", "z"]
return df.assign(col2=pd.Series(values, dtype=pd.StringDtype()))
內容解密:
change_col1函式使用assign方法新增或修改col1欄位,並賦予新的數值。change_col2函式同樣使用assign方法修改col2欄位,並根據str_case引數決定新值的大小寫。
傳統的做法是將每個步驟的結果指定給一個新的變數:
df = pd.DataFrame({'col1': [1, 2, 3], 'col2': ['a', 'b', 'c']})
df2 = change_col1(df)
df3 = change_col2(df2)
print(df3)
或者巢狀呼叫函式:
print(change_col2(change_col1(df)))
然而,這些方法要麼使程式碼冗長,要麼難以閱讀。使用 pipe 方法可以解決這些問題:
df.pipe(change_col1).pipe(change_col2, str_case="lower")
內容解密:
pipe方法將 DataFrame 傳遞給指定的函式,並傳回處理後的結果。- 可以鏈式呼叫多個
pipe方法,將多個處理步驟串接起來。 - 在呼叫
pipe方法時,可以傳遞額外的引數給目標函式,如str_case="lower"。
使用 nlargest 和 nsmallest 進行 Top N 分析
在實際的資料分析中,Top N 分析是一種常見的需求。pandas 提供了 nlargest 和 nsmallest 方法來方便地進行這種分析。
範例:找出評分最高的 100 部電影中預算最低的 5 部
首先,讀取電影資料集並選擇需要的欄位:
df = pd.read_csv(
"data/movie.csv",
usecols=["movie_title", "imdb_score", "budget", "gross"],
dtype_backend="numpy_nullable",
)
使用 nlargest 方法選出評分最高的 100 部電影:
top_100_movies = df.nlargest(100, "imdb_score")
再使用 nsmallest 方法從這 100 部電影中找出預算最低的 5 部:
lowest_budget_movies = top_100_movies.nsmallest(5, "budget")
print(lowest_budget_movies)
內容解密:
nlargest方法根據指定的欄位選出最大的 N 筆資料。nsmallest方法根據指定的欄位選出最小的 N 筆資料。- 可以鏈式呼叫這兩個方法來進行更複雜的分析,如先選出評分最高的 100 部電影,再從中找出預算最低的 5 部。
使用 Pandas 進行股票交易策略分析:計算追蹤停損訂單價格
在股票交易中,停損訂單是一種常見的風險管理策略。當股票價格達到某一特定水平時,停損訂單會自動觸發買入或賣出操作,以限制損失或保護收益。本文將介紹如何使用 Pandas 計算追蹤停損訂單價格,並探討相關的交易策略。
什麼是追蹤停損訂單?
追蹤停損訂單是一種動態的停損策略,它會根據股票價格的變化調整停損價格。當股票價格上漲時,停損價格也會隨之上調,但當股票價格下跌時,停損價格保持不變。這種策略可以幫助投資者在股票價格上漲時鎖定收益,同時在價格下跌時限制損失。
使用 Pandas 計算追蹤停損訂單價格
首先,我們需要載入 Nvidia(NVDA)股票的歷史價格資料,並將其轉換為 Pandas 的 DataFrame 或 Series 物件。以下是範例程式碼:
import pandas as pd
# 載入 Nvidia 股票歷史價格資料
df = pd.read_csv(
"data/NVDA.csv",
usecols=["Date", "Close"],
parse_dates=["Date"],
index_col=["Date"],
).convert_dtypes(dtype_backend="numpy_nullable")
# 將 DataFrame 轉換為 Series
ser = df.squeeze()
# 計算追蹤停損訂單價格(假設初始購買價格為第一天的收盤價)
ser_cummax = ser.cummax()
stop_prices = ser_cummax.mul(0.9) # 10% 的停損比例
# 顯示前五天的追蹤停損訂單價格
print(stop_prices.head())
程式碼解析:
pd.read_csv: 載入 Nvidia 股票的歷史價格資料,並指定日期欄位為索引。df.squeeze(): 將 DataFrame 轉換為 Series,以便進行後續的運算。ser.cummax(): 計算每一天的累計最高收盤價。ser_cummax.mul(0.9): 將累計最高收盤價乘以 0.9,計算出 10% 的追蹤停損訂單價格。
分析追蹤停損訂單的觸發條件
當股票價格跌破追蹤停損訂單價格時,停損訂單會被觸發。我們可以使用以下程式碼找出觸發停損訂單的交易日:
# 找出股票價格低於追蹤停損訂單價格的交易日
trigger_days = ser[ser <= stop_prices]
# 顯示第一個觸發停損訂單的交易日
print((ser <= stop_prices).idxmax())
程式碼解析:
ser <= stop_prices: 建立一個布林 Series,標示出股票價格低於追蹤停損訂單價格的交易日。(ser <= stop_prices).idxmax(): 找出第一個True值對應的索引,即第一個觸發停損訂單的交易日。
棒球資料分析:找出最佳球員與最佳打擊順序
找出表現最佳的棒球球員
美國的棒球運動長期以來一直是深入分析研究的物件,資料收集可以追溯到20世紀初。對於美國職業棒球大聯盟(MLB)的球隊來說,先進的資料分析有助於回答諸如「我應該為X球員支付多少薪水?」和「根據目前的比賽狀態,我應該怎麼做?」等問題。對於球迷來說,同樣的資料可以用來進行無盡的辯論,比如「誰是史上最偉大的球員?」。
本篇食譜將使用從retrosheet.org收集的資料。根據Retrosheet的許可要求,您應該瞭解以下法律免責宣告:
本文中使用的資訊是免費獲得的,但受Retrosheet版權保護。有興趣的各方可以聯絡Retrosheet,網址為www.retrosheet.org。
如何實作
首先,讓我們讀入總結的資料,並將id列(代表唯一的球員)設定為索引:
df = pd.read_parquet("data/mlb_batting_summaries.parquet").set_index("id")
df
輸出結果如下:
ab r h hr
id
abadf001 0 0 0 0
abboa001 0 0 0 0
abboc001 3 0 1 0
abrac001 847 116 208 20
abrea001 0 0 0 0
... ... ... ... ...
zimmk001 0 0 0 0
zimmr001 255 27 62 14
zubet001 1 0 0 0
zunig001 0 0 0 0
zunim001 572 82 111 41
2183 rows × 4 columns
在棒球運動中,很少有球員能夠在所有統計類別中都表現出色。通常,一個擊出很多全壘打(hr)的球員可能更強大,能夠將球擊得更遠,但可能不如專門收集很多安打(h)的球員那樣頻繁地做到這一點。使用pandas,我們很幸運地不需要逐一檢視每個指標;簡單地呼叫pd.DataFrame.idxmax就可以檢視每一列,找到最大值,並傳回與該最大值相關的行索引值:
df.idxmax()
輸出結果如下:
ab semim001
r freef001
h freef001
hr judga001
dtype: string
由此可見,球員semim001(Marcus Semien)擁有最多的打數,freef001(Freddie Freeman)擁有最多的得分和安打,而judga001(Aaron Judge)則擊出了最多的全壘打。
更深入的分析
如果您想進一步瞭解這些優秀球員在所有類別中的表現,可以取pd.DataFrame.idxmax的輸出結果,隨後呼叫pd.Series.unique來取得唯一值,並將其用作整體DataFrame的掩碼:
best_players = df.idxmax().unique()
mask = df.index.isin(best_players)
df[mask]
輸出結果如下:
ab r h hr
id
freef001 1849 368 590 81
judga001 1487 301 433 138
semim001 1979 338 521 100
程式碼解密:
df.idxmax():找出每個欄位的最大值索引。pd.Series.unique:取得唯一值,用於建立掩碼。df.index.isin(best_players):建立掩碼以篩選出最佳球員。df[mask]:使用掩碼篩選出最佳球員的資料。
圖表呈現
為了更好地視覺化這些資料,可以使用pd.DataFrame.style.highlight_max來突出顯示這些球員在哪些類別中表現最佳:
df[mask].style.highlight_max()
此圖示呈現了最佳球員在不同類別中的表現。
圖表翻譯: 此圖表顯示了最佳球員在不同統計類別中的表現,其中突出顯示了每個類別的最大值。
瞭解哪個打擊順序位置得分最多
在棒球比賽中,球隊允許9名擊球手按照一定的順序上場,1代表第一個上場的擊球手,9代表最後一個。通常,球隊會將一些最好的擊球手放在「打擊順序的前面」(即較低的數字位置),以最大限度地增加他們上場並得分的機會。然而,這並不總是意味著第一個上場的擊球手總是第一個得分。
如何實作
與前面的食譜類別似,我們將使用從retrosheet.org取得的資料。對於這個特定的資料集,我們將設定year和team列作為行索引,剩下的列則顯示擊球順序中的位置:
df = pd.read_parquet("data/runs_scored_by_team.parquet").set_index(["year", "team"])
df
輸出結果如下:
1 2 3 ... 7 8 9
year team
2000 ANA 124 107 100 ... 77 76 54
ARI 110 106 109 ... 72 68 40
ATL 113 125 124 ... 77 74 39
BAL ...
使用pd.DataFrame.idxmax,我們可以檢視每個年份和球隊哪個位置得分最多。然而,在這個資料集中,我們希望pd.DataFrame.idxmax識別的索引標籤實際上是在列中,而不是在行中。幸運的是,pandas仍然可以透過axis=1引數輕鬆計算出這一點:
df.idxmax(axis=1)
輸出結果如下:
year team
2000 ANA 1
ARI ...
2023 SLN ...
Length: ...
從那裡,我們可以使用pd.Series.value_counts來瞭解某個位置在擊球順序中代表最多得分的次數。我們還將使用normalize=True引數,這將給我們一個頻率而不是總數:
df.idxmax(axis=1).value_counts(normalize=True)
輸出結果如下:
1 ...
2 ...
...
Name: proportion, dtype: float64
不出所料,第一棒擊球手得分最多,佔據了48%的比例。
更深入的分析
我們可能想要進一步探索並回答這個問題:「對於第一棒擊球手得分最多的球隊來說,第二多的得分者是誰?」
為了計算這一點,我們可以建立一個掩碼來過濾第一棒擊球手得分最多的球隊,從我們的資料集中刪除該列,然後重複使用相同的pd.DataFrame.idxmax方法來識別下一個位置:
mask = df.idxmax(axis=1).eq("1")
df[mask].drop(columns=["1"]).idxmax(axis=1).value_counts(normalize=True)
輸出結果如下:
2 ...
3 ...
...
Name: proportion, dtype: float64
正如您所看到的,如果一個球隊的第一棒擊球手沒有領先該隊得分,那麼第二棒擊球手幾乎有50%的機率成為領先者。
程式碼解密:
df.idxmax(axis=1):找出每個年份和球隊哪個位置得分最多。pd.Series.value_counts(normalize=True):計算每個位置得分最多的頻率。mask = df.idxmax(axis=1).eq("1"):建立掩碼以篩選出第一棒擊球手得分最多的球隊。df[mask].drop(columns=["1"]).idxmax(axis=1):找出這些球隊中第二多的得分者。