據(jù)處理核心:行列選擇與數(shù)據(jù)轉(zhuǎn)換實(shí)戰(zhàn)指南)
1. 從“選擇”到“轉(zhuǎn)換”Polars數(shù)據(jù)處理的核心兩步如果你從Pandas轉(zhuǎn)向Polars或者剛開始用Polars處理數(shù)據(jù)最先要搞明白的不是它的語法有多快而是怎么把數(shù)據(jù)“拿過來”和“變過去”。這說的就是行列選擇和數(shù)據(jù)轉(zhuǎn)換。很多新手卡住的地方往往是以為Polars和Pandas用法一樣結(jié)果在選列、過濾行或者轉(zhuǎn)換類型時(shí)報(bào)錯(cuò)。Polars的核心優(yōu)勢(shì)在于惰性執(zhí)行和并行計(jì)算但前提是你的操作得在它的“語法體系”里。行列選擇是定位數(shù)據(jù)的“坐標(biāo)”而轉(zhuǎn)換則是改變數(shù)據(jù)的“形態(tài)”比如把字符串日期變成時(shí)間戳、把分類文本轉(zhuǎn)成數(shù)值。這兩步做對(duì)了后續(xù)的分析、聚合、輸出才能順暢。我建議先別急著處理復(fù)雜邏輯從最基礎(chǔ)的select和with_columns開始把單張表玩熟。你會(huì)發(fā)現(xiàn)很多在Pandas里需要繞一下的操作在Polars里有更直接、更高效的做法。這篇文章就圍繞這兩個(gè)核心動(dòng)作拆解具體的用法、常見的坑以及怎么組合起來解決實(shí)際問題。2. 行列選擇用對(duì)方法效率翻倍行列選擇是數(shù)據(jù)操作的起點(diǎn)。Polars提供了多種方式但每種都有其適用的場(chǎng)景和性能考量。用錯(cuò)了方法在數(shù)據(jù)量大的時(shí)候速度差異會(huì)非常明顯。2.1 列選擇select是主力[]是快捷方式選擇列最常用的方法是select。它接受列名、表達(dá)式或者一個(gè)列名列表。import polars as pl # 假設(shè)我們有一個(gè)DataFrame df df pl.DataFrame({ “id”: [1, 2, 3], “name”: [“Alice”, “Bob”, “Charlie”], “value”: [100.5, 200.3, 150.0], “category”: [“A”, “B”, “A”] }) # 1. 選擇單列或多列 df_selected df.select(“name”, “value”) # 選擇 name 和 value 列 df_selected df.select([“name”, “value”]) # 用列表形式也一樣 # 2. 使用 pl.col 對(duì)象進(jìn)行更靈活的選擇 df_selected df.select(pl.col(“name”), pl.col(“value”) * 2) # 選擇name列并計(jì)算value*2作為一個(gè)新列pl.col是構(gòu)建表達(dá)式的核心它允許你在選擇的同時(shí)進(jìn)行計(jì)算。這是Polars非常強(qiáng)大的地方你不需要先選擇列再賦值可以一步到位。除了select你也可以用類似Pandas的[]索引但這通常只用于非常簡(jiǎn)單的列名選取且返回的是一個(gè)Series單列或新的DataFrame多列。對(duì)于復(fù)雜的表達(dá)式[]就無能為力了。# 使用 [] 選擇單列返回 Series name_series df[“name”] # 使用 [] 選擇多列返回 DataFrame sub_df df[[“name”, “value”]]我的經(jīng)驗(yàn)是只要是涉及列的操作無論是單純選擇還是附帶計(jì)算優(yōu)先使用select配合pl.col。[]只在你臨時(shí)需要查看某一列或者進(jìn)行非常簡(jiǎn)單的切片時(shí)用。因?yàn)閟elect能更好地融入Polars的表達(dá)式API和惰性執(zhí)行框架。2.2 行選擇filter是核心切片要小心選擇行主要靠filter方法它接受一個(gè)布爾表達(dá)式。# 選擇 value 大于 150 的行 df_filtered df.filter(pl.col(“value”) 150) # 組合條件選擇 df_filtered df.filter((pl.col(“value”) 150) (pl.col(“category”) “A”))這里的關(guān)鍵是條件表達(dá)式必須用pl.col來構(gòu)建。直接寫df[“value”] 150在某些簡(jiǎn)單情況下可能行得通但在復(fù)雜查詢或惰性模式下容易出錯(cuò)不推薦。對(duì)于按位置選擇行切片Polars提供了slice方法。但要注意在惰性LazyFrame上直接按整數(shù)位置切片是低效的因?yàn)樗ǔP枰扔?jì)算或排序。在DataFrame上可以但要明白其含義。# 選擇前2行 df_head df.slice(0, 2) # 跳過前1行取之后的所有行 df_tail df.slice(1, -1) # -1 表示剩余全部一個(gè)重要的避坑點(diǎn)不要試圖用df[0:2]這樣的Python列表切片語法來選擇行這在Polars中通常不是你想要的結(jié)果或者會(huì)直接報(bào)錯(cuò)。行選擇邏輯條件用filter位置切片用slice界限要清晰。2.3 組合選擇行列同時(shí)篩選實(shí)際工作中我們經(jīng)常需要同時(shí)篩選行和列。Polars鼓勵(lì)鏈?zhǔn)秸{(diào)用順序很重要。# 先篩選行再選擇列更高效 result df.filter(pl.col(“category”) “A”).select(“id”, “name”, “value”) # 也可以先選擇列再篩選行。但在惰性執(zhí)行下優(yōu)化器會(huì)嘗試重排操作最終效率可能差不多。 # 對(duì)于人類閱讀來說“先過濾行減少數(shù)據(jù)量再選列”的邏輯更直觀。在惰性執(zhí)行LazyFrame中Polars的查詢優(yōu)化器會(huì)自動(dòng)對(duì)操作進(jìn)行重新排序如謂詞下推、投影下推以獲得最佳性能。所以你可以按照最自然的邏輯去寫Polars會(huì)在后臺(tái)幫你優(yōu)化。3. 數(shù)據(jù)轉(zhuǎn)換用with_columns和cast改變數(shù)據(jù)形態(tài)選出了需要的數(shù)據(jù)下一步就是轉(zhuǎn)換。轉(zhuǎn)換的目的通常有兩個(gè)1) 改變數(shù)據(jù)類型2) 基于現(xiàn)有列計(jì)算新列。3.1 類型轉(zhuǎn)換cast方法這是最直接的“轉(zhuǎn)換”。比如把字符串轉(zhuǎn)成整數(shù)、浮點(diǎn)數(shù)轉(zhuǎn)成整數(shù)、或者轉(zhuǎn)成日期類型。# 假設(shè) value 列是字符串我們想轉(zhuǎn)成浮點(diǎn)數(shù) df_converted df.with_columns(pl.col(“value”).cast(pl.Float64)) # 一次轉(zhuǎn)換多列類型 df_converted df.with_columns( pl.col(“id”).cast(pl.Int32), pl.col(“value”).cast(pl.Int64), # 浮點(diǎn)轉(zhuǎn)整數(shù)會(huì)截?cái)嘈?shù) ) # 更安全的轉(zhuǎn)換使用 strictFalse 避免因轉(zhuǎn)換失敗而報(bào)錯(cuò)無法轉(zhuǎn)換的會(huì)變成 null df_safe df.with_columns(pl.col(“some_column”).cast(pl.Int64, strictFalse))類型轉(zhuǎn)換的常見坑精度丟失Float64轉(zhuǎn)Int64會(huì)直接去掉小數(shù)部分不是四舍五入。需要四舍五入的話要先round再cast。轉(zhuǎn)換失敗比如把“hello”轉(zhuǎn)成整數(shù)會(huì)失敗。在批處理中一個(gè)失敗可能導(dǎo)致整個(gè)任務(wù)停止。務(wù)必使用strictFalse或提前清洗數(shù)據(jù)。日期時(shí)間字符串轉(zhuǎn)日期時(shí)間要用pl.col(“date_str”).str.strptime(pl.Date, “%Y-%m-%d”)而不是簡(jiǎn)單的cast。cast通常用于同大類下的類型轉(zhuǎn)換如不同整數(shù)之間。3.2 計(jì)算新列與列變換with_columns的舞臺(tái)with_columns是Polars中添加新列或替換現(xiàn)有列的主要方法。它可以在保留原有列的同時(shí)添加由表達(dá)式計(jì)算出的新列。# 1. 添加新列 df_new df.with_columns( (pl.col(“value”) * 1.1).alias(“value_with_tax”), # 計(jì)算含稅價(jià)新列名為 value_with_tax pl.lit(“processed”).alias(“status”) # 添加一個(gè)常量列 ) # 2. 替換覆蓋現(xiàn)有列 df_replaced df.with_columns( (pl.col(“value”) * 2).alias(“value”) # 列名與現(xiàn)有列相同則覆蓋 ) # 3. 復(fù)雜的表達(dá)式轉(zhuǎn)換 df_complex df.with_columns( pl.when(pl.col(“value”) 150) .then(pl.lit(“High”)) .otherwise(pl.lit(“Low”)) .alias(“value_level”), # 條件賦值類似 SQL CASE WHEN pl.col(“name”).str.to_uppercase().alias(“name_upper”) # 字符串操作 )with_columns的核心優(yōu)勢(shì)并行計(jì)算每個(gè)表達(dá)式獨(dú)立計(jì)算Polars會(huì)自動(dòng)并行化。表達(dá)式API可以與pl.col、字符串函數(shù)、時(shí)間函數(shù)等無縫結(jié)合。惰性友好在LazyFrame上使用可以極大優(yōu)化整個(gè)查詢計(jì)劃。與Pandas的對(duì)比在Pandas里你可能會(huì)寫df[‘new_col’] df[‘old_col’] * 2。在Polars里對(duì)應(yīng)的就是df.with_columns((pl.col(‘old_col’) * 2).alias(‘new_col’))。雖然寫法不同但Polars的方式更符合其函數(shù)式、表達(dá)式的設(shè)計(jì)哲學(xué)也更容易被優(yōu)化。3.3 使用select進(jìn)行“轉(zhuǎn)換式選擇”有時(shí)候轉(zhuǎn)換的目的就是為了選出新的結(jié)果集這時(shí)可以直接用select。# 選擇原有列的同時(shí)直接計(jì)算并選擇新列 result df.select( “id”, “name”, (pl.col(“value”) * 1.1).alias(“value_taxed”), pl.col(“category”).cast(pl.Categorical).alias(“category_cat”) )這相當(dāng)于先with_columns添加新列再select選擇它們。對(duì)于一次性轉(zhuǎn)換并輸出的場(chǎng)景直接用select更簡(jiǎn)潔。4. 實(shí)戰(zhàn)串聯(lián)一個(gè)完整的數(shù)據(jù)清洗與轉(zhuǎn)換流程現(xiàn)在我們把行列選擇和轉(zhuǎn)換組合起來看一個(gè)更接近真實(shí)場(chǎng)景的例子處理一份銷售數(shù)據(jù)。假設(shè)我們有一個(gè)原始的銷售記錄DataFramesales_df存在以下問題amount列是字符串類型且有貨幣符號(hào)如“$100.5”。sale_date列是“YYYYMMDD”格式的字符串。我們只需要2023年以后的數(shù)據(jù)。我們想計(jì)算一個(gè)“折扣后金額”列假設(shè)打9折并只保留相關(guān)列輸出。import polars as pl # 模擬原始數(shù)據(jù) sales_df pl.DataFrame({ “order_id”: [“A001”, “A002”, “A003”, “A004”], “amount”: [“$100.50”, “$200.00”, “$150.75”, “$80.30”], “sale_date”: [“20221215”, “20230120”, “20230510”, “20240105”], “customer”: [“Alice”, “Bob”, “Alice”, “Charlie”] }) print(“原始數(shù)據(jù):”) print(sales_df) print(sales_df.schema) # 查看數(shù)據(jù)類型 # 第一步類型轉(zhuǎn)換與清洗 # 1. 清洗 amount 列去掉$轉(zhuǎn)成浮點(diǎn)數(shù) # 2. 轉(zhuǎn)換 sale_date 列字符串轉(zhuǎn)日期 df_cleaned sales_df.with_columns( pl.col(“amount”).str.replace_all(“\$”, “”).cast(pl.Float64).alias(“amount”), pl.col(“sale_date”).str.strptime(pl.Date, “%Y%m%d”).alias(“sale_date”) ) print(“\n清洗轉(zhuǎn)換后:”) print(df_cleaned) print(df_cleaned.schema) # 第二步行篩選過濾 # 只保留2023年及以后的銷售記錄 df_filtered df_cleaned.filter(pl.col(“sale_date”) pl.Date(2023, 1, 1)) print(“\n過濾后2023年以后:”) print(df_filtered) # 第三步列轉(zhuǎn)換與選擇 # 1. 計(jì)算折扣后金額9折 # 2. 選擇最終需要的列 final_df df_filtered.with_columns( (pl.col(“amount”) * 0.9).alias(“amount_after_discount”) ).select( “order_id”, “customer”, “sale_date”, “amount”, “amount_after_discount” ) print(“\n最終結(jié)果:”) print(final_df)這個(gè)流程的關(guān)鍵點(diǎn)順序通常是清洗/轉(zhuǎn)換類型-過濾行-計(jì)算新列/選擇列。先處理臟數(shù)據(jù)和非標(biāo)類型能避免后續(xù)過濾和計(jì)算出錯(cuò)。鏈?zhǔn)秸{(diào)用上述步驟可以寫成一行鏈?zhǔn)秸{(diào)用sales_df.with_columns(...).filter(...).with_columns(...).select(...)。Polars的惰性執(zhí)行會(huì)優(yōu)化整個(gè)鏈條。alias的使用在with_columns中如果新列名與舊列名相同如amount會(huì)覆蓋舊列。如果想保留舊列需要指定不同的alias。5. 性能考量與進(jìn)階技巧當(dāng)你熟悉基礎(chǔ)操作后這些進(jìn)階技巧能幫你寫出更高效、更優(yōu)雅的Polars代碼。5.1 惰性執(zhí)行LazyFrame下的選擇與轉(zhuǎn)換所有上述操作在LazyFrame上同樣適用而且強(qiáng)烈建議在數(shù)據(jù)處理管道中使用惰性模式。# 創(chuàng)建 LazyFrame lazy_sales sales_df.lazy() # 構(gòu)建完整的惰性查詢計(jì)劃 query (lazy_sales .with_columns( pl.col(“amount”).str.replace_all(“\$”, “”).cast(pl.Float64).alias(“amount”), pl.col(“sale_date”).str.strptime(pl.Date, “%Y%m%d”) ) .filter(pl.col(“sale_date”) pl.Date(2023, 1, 1)) .with_columns((pl.col(“amount”) * 0.9).alias(“amount_after_discount”)) .select(“order_id”, “customer”, “sale_date”, “amount”, “amount_after_discount”) ) # 查看優(yōu)化后的查詢計(jì)劃可選用于調(diào)試 print(query.explain()) # 真正執(zhí)行計(jì)算得到結(jié)果 DataFrame final_df_lazy query.collect()惰性執(zhí)行的優(yōu)勢(shì)在于查詢優(yōu)化Polars會(huì)合并操作、下推謂詞和投影減少中間數(shù)據(jù)移動(dòng)。流水線并行整個(gè)計(jì)劃可以更高效地利用多核CPU。避免物化中間結(jié)果除非調(diào)用collect、fetch或sink否則數(shù)據(jù)不會(huì)真正加載到內(nèi)存中計(jì)算。5.2 多列批量操作當(dāng)需要對(duì)很多列進(jìn)行相同操作時(shí)比如將所有數(shù)值列轉(zhuǎn)為Float32手動(dòng)寫每一列很麻煩。Polars提供了便捷的方式。# 選擇所有數(shù)值列進(jìn)行轉(zhuǎn)換 df df.with_columns(pl.col(pl.NUMERIC_DTYPES).cast(pl.Float32)) # 選擇所有列名以“_temp”結(jié)尾的列并刪除 df df.select(pl.all().exclude(pl.col(“^.*_temp$”))) # 使用正則表達(dá)式排除 # 對(duì)多列應(yīng)用同一個(gè)字符串操作 df df.with_columns(pl.col([“col1”, “col2”, “col3”]).str.strip_chars())5.3 處理轉(zhuǎn)換中的錯(cuò)誤與空值數(shù)據(jù)轉(zhuǎn)換時(shí)臟數(shù)據(jù)或邊界情況可能導(dǎo)致錯(cuò)誤。Polars的表達(dá)式API提供了穩(wěn)健的處理方式。# 1. 使用 strictFalse 在 cast 時(shí)容忍錯(cuò)誤產(chǎn)生 null df df.with_columns(pl.col(“dirty_int”).cast(pl.Int64, strictFalse)) # 2. 使用 fill_null 為轉(zhuǎn)換產(chǎn)生的 null 提供默認(rèn)值 df df.with_columns( pl.col(“dirty_int”).cast(pl.Int64, strictFalse).fill_null(-1) ) # 3. 使用 replace 在轉(zhuǎn)換前清洗數(shù)據(jù) df df.with_columns( pl.col(“amount_str”).str.replace(“[^0-9.]”, “”, literalFalse).cast(pl.Float64) ) # 這個(gè)正則表達(dá)式移除了所有非數(shù)字和小數(shù)點(diǎn)的字符5.4 選擇與轉(zhuǎn)換的性能陷阱避免在select或with_columns中重復(fù)計(jì)算相同表達(dá)式。如果多個(gè)新列依賴同一個(gè)復(fù)雜計(jì)算先計(jì)算并保存到一個(gè)臨時(shí)列或者使用alias后在其他表達(dá)式中引用pl.col(“temp_col”)。謹(jǐn)慎使用apply函數(shù)。apply會(huì)將操作切換到Python的慢速循環(huán)中失去Polars的向量化性能優(yōu)勢(shì)。絕大多數(shù)標(biāo)準(zhǔn)操作字符串處理、數(shù)學(xué)運(yùn)算、日期計(jì)算都有內(nèi)置的表達(dá)式方法應(yīng)優(yōu)先使用。filter條件盡量簡(jiǎn)單。復(fù)雜的filter條件可能阻礙查詢優(yōu)化。如果條件太復(fù)雜考慮分步過濾或者先用with_columns計(jì)算出一個(gè)布爾列再基于此列過濾。6. 總結(jié)從知道到熟練Polars的行列選擇與轉(zhuǎn)換核心是掌握select、filter、with_columns和cast這幾個(gè)方法并理解其背后的表達(dá)式哲學(xué)pl.col。一個(gè)高效的Polars數(shù)據(jù)處理流程通常是這樣的讀入數(shù)據(jù)為L(zhǎng)azyFrame如果數(shù)據(jù)量大。清洗與類型轉(zhuǎn)換使用with_columns配合str.*方法、cast和strptime將數(shù)據(jù)整理成干凈、類型正確的狀態(tài)。行過濾使用filter基于業(yè)務(wù)邏輯篩選數(shù)據(jù)盡早減少數(shù)據(jù)量。特征工程/列轉(zhuǎn)換使用with_columns計(jì)算衍生列、聚合指標(biāo)等。列選擇使用select精準(zhǔn)選擇最終需要的列減少內(nèi)存占用和I/O。執(zhí)行與輸出調(diào)用collect()惰性模式或直接得到結(jié)果然后寫入文件或進(jìn)行下一步分析。剛開始你可能會(huì)覺得Polars的表達(dá)式寫法有點(diǎn)繞不如Pandas的賦值直觀。但一旦習(xí)慣你會(huì)發(fā)現(xiàn)這種聲明式的風(fēng)格讓復(fù)雜的數(shù)據(jù)管道更清晰、更易維護(hù)并且能實(shí)實(shí)在在地享受到性能提升。最關(guān)鍵的是先確保單條數(shù)據(jù)轉(zhuǎn)換的邏輯正確再通過鏈?zhǔn)秸{(diào)用組合成完整管道這是用好Polars最踏實(shí)的一條路徑。