如果你曾經使用過Pandas處理表格數據,你可能會熟悉導入數據、清洗和轉換的過程,然後將其用作模型的輸入。然而,當你需要擴展和將程式碼投入生產時,你的Pandas管道很可能開始崩潰並運行緩慢。在這篇文章中,我將分享2個技巧,幫助你提升Pandas程式碼的執行速度,提高資料處理效率並避免常見的陷阱。
在Pandas中,向量化操作是一種高效的工具,能夠以更簡潔的方式處理整個數據框的列,而無需逐行循環。
廣播是向量化操作的關鍵要素,它允許您直觀地操作具有不同形狀的物件。
eg1: 具有3個元素的陣列a與標量b相乘,得到與Source形狀相同的陣列。
eg2: 在進行加法運算時,將形狀為(4,1)的陣列a與形狀為(3,)的陣列b相加,結果會得到一個形狀為(4,3)的數組。
已有很多文章討論了這一點,特別是在深度學習中,大規模矩陣乘法很常見。本文將以兩個簡短例子來討論。
首先,假設您想要計算給定整數在列中出現的次數。以下是 2 種可能的方法。
"""计算DataFrame X 中 "column_1" 列中等于目标值 target 的元素个数。参数:X: DataFrame,包含要计算的列 "column_1"。target: int,目标值。返回值:int,等于目标值 target 的元素个数。"""# 使用循环计数def count_loop(X, target: int) -> int:return sum(x == target for x in X["column_1"])# 使用矢量化操作计数def count_vectorized(X, target: int) -> int:return (X["column_1"] == target).sum()
現在假設有一個DataFrame帶有日期列並希望將其偏移給定的天數。使用向量化運算計算如下:
def offset_loop(X, days: int) -> pd.DataFrame:d = pd.Timedelta(days=days)X["column_const"] = [x + d for x in X["column_10"]]return Xdef offset_vectorized(X, days: int) -> pd.DataFrame:X["column_const"] = X["column_10"] + pd.Timedelta(days=days)return X
第一個也是最直覺的迭代方法是使用Python for迴圈。
def loop(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:res = []i_remove_col = df.columns.get_loc(remove_col)i_words_to_remove_col = df.columns.get_loc(words_to_remove_col)for i_row in range(df.shape[0]):res.append(remove_words(df.iat[i_row, i_remove_col], df.iat[i_row, i_words_to_remove_col]))return result
def apply(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return df.apply(func=lambda x: remove_words(x[remove_col], x[words_to_remove_col]), axis=1).tolist()
在 df.apply 的每次迭代中,提供的可呼叫函數取得一個 Series,其索引為 df.columns,其值是行的。這意味著 pandas 必須在每個循環中產生該序列,這是昂貴的。為了降低成本,最好對您知道將使用的df 子集呼叫apply,如下所示:
def apply_only_used_cols(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return df[[remove_col, words_to_remove_col]].apply(func=lambda x: remove_words(x[remove_col], x[words_to_remove_col]), axis=1)
使用itertuples與列表相結合進行迭代肯定會更好。 itertuples產生帶有行資料的(命名)元組。
def itertuples_only_used_cols(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return [remove_words(x[0], x[1])for x in df[[remove_col, words_to_remove_col]].itertuples(index=False, name=None)]
zip接受可迭代物件並產生元組,其中第i個元組依序包含所有給定可迭代物件的第i個元素。
def zip_only_used_cols(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return [remove_words(x, y) for x, y in zip(df[remove_col], df[words_to_remove_col])]
def to_dict_only_used_columns(df: pd.DataFrame) -> list[str]:return [remove_words(row[remove_col], row[words_to_remove_col])for row in df[[remove_col, words_to_remove_col]].to_dict(orient="records")]
除了我們討論的迭代技術之外,另外兩種方法可以幫助提高程式碼的效能:快取和並行化。如果使用相同的參數多次呼叫 pandas 函數,快取會特別有用。例如,如果remove_words應用於具有許多重複值的資料集,您可以使用它functools.lru_cache來儲存函數的結果並避免每次都重新計算它們。要使用lru_cache,只需將 @lru_cache裝飾器新增至 的聲明中 remove_words,然後使用您首選的迭代方法將該函數應用於您的資料集。這可以顯著提高程式碼的速度和效率。以下面的程式碼為例:
@lru_cachedef remove_words(...):... # Same implementation as beforedef zip_only_used_cols_cached(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return [remove_words(x, y) for x, y in zip(df[remove_col], df[words_to_remove_col])]
新增此裝飾器會產生一個函數,該函數會「記住」之前遇到的輸入的輸出,因此無需再次執行所有程式碼。
最後一張王牌是使用 pandarallel 跨多個獨立的 df 區塊並行化我們的函數呼叫。該工具易於使用:您只需匯入並初始化它,然後將所有 .applys 變更為 .parallel_applys。
from pandarallel import pandarallelpandarallel.initialize(nb_workers=min(os.cpu_count(), 12))def parapply_only_used_cols(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return df[[remove_col, words_to_remove_col]].parallel_apply(lambda x: remove_words(x[remove_col], x[words_to_remove_col]), axis=1)
以上是提升Pandas程式碼效率的兩個絕妙技巧的詳細內容。更多資訊請關注PHP中文網其他相關文章!