五行程式、三個考點。① None 放進整數清單的瞬間,整欄被默默升格成 float64(本站 pandas 3.0.2 實跑:[2022.0, nan, 2024.0])。② median() 預設跳過缺值、只看 [2022, 2024]——偶數筆取中間兩個的平均 = 2023.0(2023 根本不在原始資料裡)。③ astype('Int64') 大寫 I 是 pandas 可空整數——print 實跑輸出逐字是 [2022, 2023, 2024] 與 Int64(答案 A)。加映:小寫 i 的 astype('int64') 見 NaN 直接 IntCastingNaNError——報錯訊息還推薦你用 'Int64'。
執行下列程式後,year 欄位的值與資料型態判讀何者正確?
import pandas as pd # 帶 pandas 進場,慣例縮寫 pd df = pd.DataFrame({'year': [2022, None, 2024]}) # 三筆年份,中間破一個洞 df['year'] = df['year'].fillna(df['year'].median()).astype('Int64') # 中位數補洞 → 轉可空整數 print(df['year'].tolist()) # 印出值清單 print(df['year'].dtype) # 印出資料型態
想像一本全班的座號名簿:座號本來都是整數。有一格沒填——為了記住「這格是空的」,pandas 把整欄換成「可以寫小數的鉛筆」(float64)來寫,因為它的字典裡「空格」(NaN)是浮點世界的公民,整數格式容不下它。這就是實跑看到的第一幕:[2022, None, 2024] 進表後變成 [2022.0, nan, 2024.0]——你沒叫它變,它默默升格了。
補洞用的是中位數:把洞跳過、剩 [2022, 2024] 排排站,偶數筆就取中間兩個的平均——2023.0。補完想改回整數,有兩種筆:小寫 i 的 int64 是老派原子筆——格式乾淨但一格空白都不准(見 NaN 直接報錯);大寫 I 的 Int64 是新款鋼筆——整數格式、還能保留空格(<NA>)。本題先補了洞再轉,所以兩種筆都行;但考卷寫的是大 I,輸出就是可空整數 Int64。
median() 預設 skipna——跳洞計算、回傳 float(不是字串)。③ 大 I 小 i 之差:'Int64' 能裝 <NA>;'int64' 見 NaN 就丟 IntCastingNaNError——報錯訊息還親自推薦你 e.g. 'Int64'。點擊後出現漸進式說明:白話說明 → 說清楚一點 → 常見錯誤與考點。
點擊後出現漸進式說明。
五段,一段一段走完。右上角的「看位置」可以把這一段放回完整程式裡看。
import pandas as pd # 資料表工具箱,慣例縮寫 pd
pandas 是 Python 的資料表工具箱,慣例縮寫 pd(跟 numpy 的 np、seaborn 的 sns 同一種暗號)。本題的主角們——DataFrame、fillna、median、astype——全住在這箱子裡。
df = pd.DataFrame({'year': [2022, None, 2024]}) # 字典建表:鍵當欄名、清單當欄值
字典建表:鍵 'year' 當欄名、清單當欄值。重點在 None——缺值進了數值欄,會被換成 NaN;而 NaN 是浮點世界的公民,整數格式容不下它,所以整欄被升格成 float64。實跑證據:這行跑完 dtype 已是 float64、值是 [2022.0, nan, 2024.0]——注意 2022 悄悄長出了 .0 的名牌。
df['year'] = df['year'].fillna(df['year'].median()).astype('Int64') # 三站流水線+回寫
一行三站,從最裡面讀出來:站 1 median()——預設跳過缺值,只看 [2022, 2024],偶數筆取中間兩個的平均 = 2023.0(實跑 type 是 float64,不是字串——D 的死因)。站 2 fillna(2023.0)——把洞補上:[2022.0, 2023.0, 2024.0],此刻仍是 float64。站 3 astype('Int64')——轉成大寫 I 的 pandas 可空整數。最後 df['year'] = 把成品回寫同一欄。
print(df['year'].tolist()) # Series → Python 清單再印出
tolist() 把 Series 變回普通 Python 清單。實跑輸出:[2022, 2023, 2024]——乾淨整數、沒有 .0(Int64 的元素 tolist 後是 Python int)。三個值都對得上:2022 與 2024 原班人馬、2023 是 median 補進來的。
print(df['year'].dtype) # 印出資料型態——第二行證據
實跑輸出:Int64——大寫 I,pandas 的可空整數(nullable integer)。兩行輸出跟選項 A 逐字吻合:[2022, 2023, 2024] + Int64。大小寫在這裡是身分證:int64(小 i)是 numpy 整數、Int64(大 I)是 pandas 擴充型態——只有後者裝得下 <NA>。
同一份三筆年份,換三種裝法——dtype 會走到哪裡去?(pandas 3.0.2 全部實跑):
dtype=object 才做得出來——預設推斷根本不走那條路。「整數混缺值」的預設歸宿是浮點——這是 pandas 的地基設定,不是 bug。median() 在這份資料上到底怎麼算?三個視角實跑:
如果不先 fillna、直接轉整數呢?大小寫各跑一遍:
IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer. Replace or remove non-finite values or cast to an integer type that supports these values (e.g. 'Int64')——pandas 自己告訴你:要嘛先補洞(Replace)、要嘛用大 I('Int64')。本題兩件都做了:先 fillna 再轉大 I——雙保險,穩穩落地。把第 3 行拆成四個時刻,每一站看兩件事:值長什麼樣、dtype 是誰:
缺值 × 型別的兩張必備小抄:
| dtype | 寫法 | 能裝缺值嗎 | 缺值長相 |
|---|---|---|---|
| int64(numpy 小 i) | astype('int64') / astype(int) | ×(IntCastingNaNError) | — |
| Int64(pandas 大 I) | astype('Int64') | ○ | <NA> |
| float64 | 預設推斷(缺值的預設歸宿) | ○ | NaN |
| object | 手動 dtype=object | ○ | None 原樣保留 |
| boolean(大寫家族) | astype('boolean') | ○(bool 小寫則×) | <NA> |
# 第 14 頁的同一招:sklearn 方言(實跑同補 2023.0) from sklearn.impute import SimpleImputer # 補值器 imp = SimpleImputer(strategy='median') # 策略:中位數——跟 fillna(median) 同心 imp.fit_transform(df[['year']]) # 實跑 → [[2022.], [2023.], [2024.]]
fillna(df['year'].median()) 就是第 14 頁 SimpleImputer(strategy='median') 的 pandas 方言——實跑同樣補進 2023.0。差別在紀律:正規 ML 流程的 median 要在「訓練集」上算(第 15 頁的洩漏防線——統計量只能來自訓練資料);本題是單表資料清理,全欄算 median 沒問題。median 本人已是系列三朝元老:第 13 頁(describe 三兄弟)、第 20 頁(Spark approxQuantile)、本頁(fillna 的填料)——偏態與缺值場景,中位數永遠是第一人選。三站實跑背書:median() 跳洞算出 2023.0(偶數筆取中間兩個平均);fillna 補洞 → [2022.0, 2023.0, 2024.0];astype('Int64') 收尾——print 實跑輸出逐字是 [2022, 2023, 2024] 與 Int64。大寫 I 的「可空整數」描述也精準:它是 pandas 擴充型態、裝得下 <NA>。
兩個矛盾:① 預設推斷是 float64 不是 object(實跑)——object 要手動 dtype=object 才會出現;② None 進數值表的瞬間就變 nan——「2022.0 長了浮點名牌」卻配「原裝的 None」是兩個世界的零件拼在一起。而且程式明明跑了 fillna——洞已經補掉,None 不可能還在。
0 是 fillna(0) 的結果——本題補的是 median()=2023,不是 0(實跑 fillna(0) 世界:[2022, 0, 2024]);bool 只在 astype(bool) 出現——實跑 [2022, 0, 2024] 轉 bool 得 [True, False, True](0 變 False)。兩個零件都不在本題程式裡——而且「值是整數清單、dtype 卻是 bool」本身就自相矛盾。
實跑 type(df['year'].median()) → numpy.float64——數值欄的 median 回傳數值,不是字串。就算硬對字串欄呼叫 median,pandas 3 直接丟 TypeError「Cannot perform reduction 'median' with string dtype」——連「回傳字串」的機會都沒有。整條程式實跑一路綠燈,astype('Int64') 順利執行。
再看一次同一段程式。這次你手上有一句口訣了:缺值逼整數變浮點;median 補洞、Int64 復籍。
df = pd.DataFrame({'year': [2022, None, 2024]}) # None 進場 → 整欄升格 float64 df['year'] = df['year'].fillna(df['year'].median()).astype('Int64') # 2023.0 補洞 → 大 I 收尾
八題,全部都是本題的延伸。答錯會直接告訴你錯在哪。
「缺值處理與型別轉換」是中級科目二大數據處理分析與應用的資料清理招牌考點,fillna、median、astype 三件套輪流出場。最常見的六種問法:① 給程式問輸出的值與 dtype(本題——驗升格、median、Int64 三站);② 問 None 放進整數欄後 dtype 變什麼(float64——不是 object);③ 問 median/mean/mode 補值的適用場景(偏態用 median);④ 問 Int64 與 int64 的差別(可空 vs 不可空);⑤ 問不先補洞直接轉 int 會怎樣(IntCastingNaNError);⑥ 問 fillna 與 SimpleImputer 的關係(同一招兩方言、訓練集算統計量)。一句口訣:缺值逼整數變浮點;median 補洞、Int64 復籍。
不是同一個東西,但在數值欄裡會被「統一收編」。None 是 Python 的「什麼都沒有」(NoneType 物件);NaN(Not a Number)是 IEEE 754 浮點標準定義的特殊浮點值——它天生住在 float 世界。把 None 放進數值清單建表,pandas 會把它換成 NaN 來表示缺值(實跑:[2022, None, 2024] 進表變 [2022.0, nan, 2024.0])——正因為 NaN 是 float、int64 容不下,整欄才被升格成 float64。只有手動 dtype=object 時 None 才會原樣保留(但那是「雜物櫃」模式,數值運算全部變慢變怪)。pandas 擴充型態另立了第三種缺值符號 pd.NA——Int64 欄裡的缺值顯示為 <NA>,這是三兄弟的分工:None(Python)、NaN(float)、pd.NA(pandas 擴充家族)。
因為 numpy 的欄是「同型態陣列」——一欄一種 dtype,沒有「這格 int、那格 NaN」的混搭。NaN 只存在於浮點世界(它就是一種特殊的 float),所以「整數+缺值」的最小公倍數是 float64——pandas 預設就走這條路(實跑證據:dtype 直接是 float64、2022 長出 .0)。這正是大寫家族存在的理由:Int64 擴充型態用「數值陣列+遮罩」兩層結構——數字照樣用整數存,另外一張遮罩記「哪幾格是缺值」——才能做到「整數格式、又能裝 <NA>」。考場記法:預設世界「int 見洞變 float」;擴充世界「Int64 見洞掛 NA」。
兩步:① median 預設 skipna=True——先把 NaN 跳過,名單剩 [2022, 2024];② 偶數筆的中位數定義是「中間兩個的平均」——(2022+2024)/2 = 2023.0。所以中位數完全可以是「資料裡不存在的值」——它是位置統計量的內插結果,不是從名單裡挑一個。對照組實跑:skipna=False 時 median 直接回 nan(一顆老鼠屎壞一鍋粥)——fillna(nan) 等於什麼都沒補;mean() 在本題也是 2023.0(兩點對稱時 mean=median),但偏態資料兩者會分家——這正是第 13 頁三兄弟的老戲。補值選 median 的理由:不怕極端值拉偏(偏態金額、年齡、收入都是它的主場)。
差在「能不能裝缺值」,背後是兩套型態系統。int64(小 i)是 numpy 原生整數——高效、緊湊,但陣列裡不准有缺值:實跑 astype('int64') 遇到 NaN 直接丟 IntCastingNaNError。Int64(大 I)是 pandas 的擴充型態(extension dtype)——整數儲存+缺值遮罩,缺值顯示為 <NA>:實跑不先 fillna 直接 astype('Int64') 得到 [2022, <NA>, 2024],完全合法。同家族還有 'boolean'(可空布林,實跑 [True, <NA>, False])、'string' 等。考場陷阱:題目寫 'Int64' 還是 'int64',先看大小寫再作答——本題先 fillna 再轉,兩種都能跑;但 dtype 印出來是題目指定的 Int64。
兩條路兩個結局(都實跑)。astype('Int64'):合法——[2022, <NA>, 2024]、dtype Int64,缺值被 pd.NA 接手保留。astype('int64') 或 astype(int):當場爆炸——IntCastingNaNError: Cannot convert non-finite values (NA or inf) to integer. Replace or remove non-finite values or cast to an integer type that supports these values (e.g. 'Int64')——注意報錯訊息親自推薦了兩條活路:先補洞(Replace,本題的 fillna)或改用大 I('Int64')。本題程式兩件都做了——雙保險。這也是選項 D 的反面教材:整條程式一路綠燈,真正會失敗的是「小 i +不補洞」的組合。
同一招、兩種方言。本頁 df['year'].fillna(df['year'].median()) 是 pandas 手動版;第 14 頁的 SimpleImputer(strategy='median') 是 sklearn 元件版——實跑對同一份資料同樣補進 2023.0。差別在「紀律內建與否」:SimpleImputer 裝進 Pipeline 後,fit 只在訓練集上算統計量、transform 拿同一把尺套測試集——第 15 頁的洩漏防線自動成立。手動 fillna 要自己守規矩:先切 train/test,median 只能在訓練集上算,再分別填兩邊——對全資料算 median 再切分,就是把測試集的資訊漏進了前處理(單表資料清理如本題無妨;建模流程必須講究)。
三條線。「不報錯的錯」家族:int + None 默默升格 float64——沒有警告、沒有錯誤、型態卻變了,跟第 9 頁(LSTM shape 默默廣播)、第 18 頁(head 撈到鍵序前三)同款「程式跑得動≠語意正確」。「補值家族」:本頁 fillna(median) ↔ 第 14 頁 SimpleImputer(strategy='median')(同招兩方言)↔ 第 15 頁「統計量只能來自訓練集」(補值的洩漏紀律)。「median 三部曲」:第 13 頁它是描述統計的三兄弟之一、第 20 頁 Spark 用草圖近似它、本頁它成了補洞的填料——偏態與缺值場景,中位數永遠是第一人選。