據(jù)預(yù)處理與探索性分析:數(shù)學(xué)建模的基石)
1. 項(xiàng)目概述從數(shù)據(jù)搬運(yùn)工到建模分析師如果你已經(jīng)跟著上一篇文章把Pandas的基本數(shù)據(jù)結(jié)構(gòu)DataFrame和Series玩得比較熟了那恭喜你你已經(jīng)成功從“數(shù)據(jù)小白”晉級(jí)為“數(shù)據(jù)搬運(yùn)工”。但數(shù)學(xué)建模的世界遠(yuǎn)不止把數(shù)據(jù)從一個(gè)地方搬到另一個(gè)地方那么簡(jiǎn)單。搬運(yùn)只是第一步更重要的是你得知道搬來(lái)的這些“磚頭”數(shù)據(jù)是什么材質(zhì)、能不能用、該怎么用。這就是我們這次要深入探討的核心數(shù)據(jù)預(yù)處理與探索性分析——這是連接原始數(shù)據(jù)和數(shù)學(xué)模型之間最關(guān)鍵、也最容易被忽視的橋梁。很多新手朋友一拿到數(shù)據(jù)尤其是從網(wǎng)上爬下來(lái)的天氣數(shù)據(jù)、或者公司導(dǎo)出的業(yè)務(wù)報(bào)表就迫不及待地開(kāi)始套用復(fù)雜的回歸、分類模型結(jié)果往往慘不忍睹。模型報(bào)錯(cuò)還是小事更可怕的是模型“跑通了”但結(jié)論完全錯(cuò)誤這就是“垃圾進(jìn)垃圾出”。Pandas在這個(gè)階段扮演的角色就是一個(gè)無(wú)比強(qiáng)大的“數(shù)據(jù)質(zhì)檢員”和“數(shù)據(jù)整形師”。它不僅能幫你快速看清數(shù)據(jù)的全貌更能高效地處理那些臟亂差的數(shù)據(jù)把它們整理成建模算法“愛(ài)吃”的格式。這次我們就聚焦于如何利用Pandas為你的數(shù)學(xué)建模項(xiàng)目打下堅(jiān)實(shí)、干凈的數(shù)據(jù)基礎(chǔ)。2. 數(shù)據(jù)預(yù)處理建模前的“大掃除”拿到一份原始數(shù)據(jù)比如一份包含日期、城市、最高溫、最低溫、天氣狀況、AQI等字段的天氣數(shù)據(jù)CSV文件直接丟給模型是行不通的。我們得先進(jìn)行一系列清理和轉(zhuǎn)換。2.1 數(shù)據(jù)讀取與初窺知己知彼讀取數(shù)據(jù)是第一步但怎么讀卻有講究。除了最常用的pd.read_csv對(duì)于Excel文件可以用pd.read_excel需要安裝openpyxl或xlrd庫(kù)對(duì)于從網(wǎng)頁(yè)API獲取的JSON數(shù)據(jù)可以用pd.read_json。import pandas as pd # 假設(shè)我們有一個(gè)天氣數(shù)據(jù)文件 # 注意編碼問(wèn)題中文路徑或內(nèi)容可能需指定encodingutf-8或gbk df pd.read_csv(weather_data.csv)讀進(jìn)來(lái)之后千萬(wàn)別急著動(dòng)手清洗。先用幾個(gè)方法快速“瞟一眼”數(shù)據(jù)建立第一印象df.head()/df.tail()/df.sample(5)看頭、看尾、隨機(jī)看。了解數(shù)據(jù)大致樣子有沒(méi)有奇怪的符號(hào)、明顯的錯(cuò)誤。df.info()這是最重要的初步診斷工具。它會(huì)列出所有列的非空值數(shù)量、數(shù)據(jù)類型。一眼就能看出哪些列缺失嚴(yán)重以及Pandas自動(dòng)推斷的數(shù)據(jù)類型是否正確。比如一個(gè)應(yīng)該是數(shù)字的列被識(shí)別成了object字符串這通常意味著里面混入了非數(shù)字字符如“N/A”、“-”。df.describe()針對(duì)數(shù)值型列快速計(jì)算計(jì)數(shù)、均值、標(biāo)準(zhǔn)差、最小值、四分位數(shù)、最大值。它能立刻幫你發(fā)現(xiàn)異常值。比如氣溫出現(xiàn)200度AQI出現(xiàn)負(fù)值在這里就會(huì)原形畢露。實(shí)操心得df.info()是我每次必看的第一項(xiàng)。如果數(shù)據(jù)量很大df.describe(includeall)可以包含非數(shù)值列的統(tǒng)計(jì)如唯一值數(shù)量、最高頻值但計(jì)算稍慢。對(duì)于初步探索先看數(shù)值列的描述統(tǒng)計(jì)往往更高效。2.2 處理缺失值填坑的藝術(shù)缺失值NaN是現(xiàn)實(shí)數(shù)據(jù)的常態(tài)。處理方式無(wú)非三種刪除、填充、保留。Pandas提供了靈活的工具。發(fā)現(xiàn)缺失df.isnull().sum()可以統(tǒng)計(jì)每列的缺失數(shù)量。df[df[某列].isnull()]可以查看所有包含該列缺失值的行。刪除缺失df.dropna()會(huì)刪除任何包含缺失值的行。df.dropna(subset[關(guān)鍵列])只刪除在關(guān)鍵列上缺失的行。慎用全局刪除容易損失大量數(shù)據(jù)。填充缺失這是更常用的方法。固定值填充df[列名].fillna(0)或df.fillna({列A: 0, 列B: 未知})。適合分類變量或?qū)I(yè)務(wù)影響明確的場(chǎng)景。統(tǒng)計(jì)值填充df[列名].fillna(df[列名].mean())用均值填充median()用中位數(shù)對(duì)異常值不敏感mode()[0]用眾數(shù)。這是數(shù)值型字段的常見(jiàn)做法。前后向填充df.fillna(methodffill)用前一個(gè)有效值填充或bfill。在處理時(shí)間序列數(shù)據(jù)如按時(shí)間排序的天氣數(shù)據(jù)時(shí)特別有用假設(shè)天氣不會(huì)突變。# 示例處理一份學(xué)生成績(jī)數(shù)據(jù) df[數(shù)學(xué)].fillna(df[數(shù)學(xué)].median(), inplaceTrue) # 中位數(shù)填充數(shù)學(xué)成績(jī)?nèi)笔?df[班級(jí)].fillna(未知班級(jí), inplaceTrue) # 字符串列用特定值填充 # 對(duì)于時(shí)間序列的溫度數(shù)據(jù)可以考慮用前一天的溫度填充 df[溫度] df[溫度].fillna(methodffill)避坑指南填充缺失值會(huì)引入偏差尤其是當(dāng)缺失并非隨機(jī)時(shí)。例如不愿意透露收入的人可能收入普遍較高或較低用均值填充會(huì)扭曲分布。在建模報(bào)告中必須說(shuō)明缺失值的處理方式及其潛在影響。2.3 處理異常值找出“害群之馬”異常值可能包含重要信息如欺詐檢測(cè)也可能是錯(cuò)誤數(shù)據(jù)。需要結(jié)合業(yè)務(wù)判斷。標(biāo)準(zhǔn)差法假設(shè)數(shù)據(jù)服從正態(tài)分布通常認(rèn)為在均值 ± 3倍標(biāo)準(zhǔn)差之外的值是異常值。四分位距IQR法更穩(wěn)健不依賴正態(tài)分布假設(shè)。計(jì)算上四分位數(shù)Q3和下四分位數(shù)Q1定義IQR Q3 - Q1。通常認(rèn)為小于Q1 - 1.5*IQR或大于Q3 1.5*IQR的值是異常值。# 使用IQR法識(shí)別‘AQI’列的異常值 Q1 df[AQI].quantile(0.25) Q3 df[AQI].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR # 找出異常值 outliers df[(df[AQI] lower_bound) | (df[AQI] upper_bound)] print(f發(fā)現(xiàn) {len(outliers)} 個(gè)AQI異常值) # 處理異常值這里選擇用上下邊界值截?cái)郬insorization而非直接刪除 df[AQI_processed] df[AQI].clip(lowerlower_bound, upperupper_bound)注意事項(xiàng)直接刪除異常值要非常謹(jǐn)慎。在環(huán)境分析中極高的AQI值可能是真實(shí)的污染事件包含了關(guān)鍵信息。更好的做法是創(chuàng)建一個(gè)標(biāo)識(shí)異常值的布爾列在建模時(shí)作為特征輸入或者使用對(duì)異常值不敏感的模型如樹(shù)模型。2.4 數(shù)據(jù)類型轉(zhuǎn)換讓數(shù)據(jù)“名正言順”Pandas自動(dòng)推斷的數(shù)據(jù)類型有時(shí)不準(zhǔn)需要手動(dòng)轉(zhuǎn)換這對(duì)后續(xù)分析和建模效率至關(guān)重要。轉(zhuǎn)換為數(shù)值型pd.to_numeric(df[列名], errorscoerce)。errorscoerce會(huì)將無(wú)法轉(zhuǎn)換的值如字符串‘N/A’變?yōu)镹aN而不是報(bào)錯(cuò)。轉(zhuǎn)換為日期時(shí)間pd.to_datetime(df[日期列], format%Y-%m-%d)。指定format參數(shù)可以加速轉(zhuǎn)換并避免歧義。轉(zhuǎn)換為分類類型df[城市] df[城市].astype(category)。對(duì)于像城市、天氣狀況這類有限取值的字符串列轉(zhuǎn)換為category類型可以極大節(jié)省內(nèi)存提高分組、排序的速度。# 常見(jiàn)轉(zhuǎn)換操作 df[溫度] pd.to_numeric(df[溫度], errorscoerce) # 強(qiáng)制轉(zhuǎn)數(shù)字非法變NaN df[觀測(cè)日期] pd.to_datetime(df[觀測(cè)日期]) df[天氣] df[天氣].astype(category) # 檢查轉(zhuǎn)換結(jié)果 print(df.dtypes)3. 探索性數(shù)據(jù)分析用Pandas“看透”數(shù)據(jù)數(shù)據(jù)清洗干凈后就要開(kāi)始探索了。探索性數(shù)據(jù)分析的目標(biāo)是發(fā)現(xiàn)模式、趨勢(shì)、異常和關(guān)系為特征工程和模型選擇提供依據(jù)。3.1 單變量分析了解每一個(gè)“個(gè)體”數(shù)值變量除了describe()可視化是王道。雖然Pandas繪圖功能基于Matplotlib但直接調(diào)用非常方便。import matplotlib.pyplot as plt df[最高溫].hist(bins30, edgecolorblack) # 直方圖看分布 plt.title(最高溫度分布) plt.xlabel(溫度(℃)) plt.ylabel(頻數(shù)) plt.show() df[AQI].plot(kindbox) # 箱線圖看分散情況和異常值 plt.title(AQI箱線圖) plt.show()直方圖看分布形狀是否正態(tài)、偏斜箱線圖一眼看清中位數(shù)、四分位距和異常值點(diǎn)。分類變量使用value_counts()和條形圖。weather_counts df[天氣狀況].value_counts() print(weather_counts) weather_counts.plot(kindbar) plt.title(天氣狀況頻數(shù)統(tǒng)計(jì)) plt.xticks(rotation45) # 旋轉(zhuǎn)x軸標(biāo)簽避免重疊 plt.show()這能立刻看出“晴”、“多云”、“雨”等天氣出現(xiàn)的頻率。3.2 多變量關(guān)系分析發(fā)現(xiàn)“連線”建模的核心是找出變量之間的關(guān)系。數(shù)值 vs 數(shù)值散點(diǎn)圖和相關(guān)矩陣。# 散點(diǎn)圖看兩個(gè)數(shù)值變量的關(guān)系 df.plot(kindscatter, x最高溫, yAQI, alpha0.5) # alpha設(shè)置透明度 plt.title(最高溫與AQI散點(diǎn)圖) plt.show() # 計(jì)算所有數(shù)值列之間的相關(guān)系數(shù)Pearson numeric_df df.select_dtypes(include[number]) correlation_matrix numeric_df.corr() print(correlation_matrix) # 用熱圖可視化相關(guān)系數(shù)矩陣需要seaborn庫(kù) import seaborn as sns plt.figure(figsize(10,8)) sns.heatmap(correlation_matrix, annotTrue, cmapcoolwarm, center0) plt.title(變量相關(guān)系數(shù)熱圖) plt.show()相關(guān)系數(shù)接近1或-1表示強(qiáng)相關(guān)接近0表示弱相關(guān)。熱圖能直觀展示哪些變量可能互為冗余。分類 vs 數(shù)值分組聚合和箱線圖。這是分析不同類別下數(shù)值指標(biāo)差異的黃金方法。# 按‘天氣狀況’分組查看‘AQI’的統(tǒng)計(jì)信息 grouped df.groupby(天氣狀況)[AQI] print(grouped.describe()) # 用箱線圖可視化不同天氣下的AQI分布 df.boxplot(columnAQI, by天氣狀況) plt.title(不同天氣狀況下的AQI分布) plt.suptitle() # 去除自動(dòng)生成的默認(rèn)標(biāo)題 plt.xlabel(天氣狀況) plt.ylabel(AQI) plt.xticks(rotation45) plt.show()這個(gè)方法能清晰回答諸如“雨天和晴天的AQI有顯著差異嗎”這類問(wèn)題。3.3 時(shí)間序列分析抓住“趨勢(shì)”對(duì)于帶有時(shí)間戳的數(shù)據(jù)如每日天氣時(shí)間序列分析是重點(diǎn)。重采樣將高頻數(shù)據(jù)聚合為低頻數(shù)據(jù)或者反之。# 假設(shè)df已按日期排序且‘日期’列是datetime類型 df.set_index(觀測(cè)日期, inplaceTrue) # 將日期設(shè)為索引 # 計(jì)算每周的平均溫度 weekly_avg_temp df[溫度].resample(W).mean() weekly_avg_temp.plot(title周平均溫度變化趨勢(shì)) plt.show() # 計(jì)算每月的AQI最大值 monthly_max_aqi df[AQI].resample(M).max()滑動(dòng)窗口計(jì)算計(jì)算移動(dòng)平均線平滑短期波動(dòng)觀察長(zhǎng)期趨勢(shì)。# 計(jì)算7天移動(dòng)平均溫度 df[溫度_7d_avg] df[溫度].rolling(window7).mean() df[[溫度, 溫度_7d_avg]].plot(title溫度與7日移動(dòng)平均線) plt.show()4. 特征工程初探為模型制造“彈藥”原始數(shù)據(jù)字段不一定適合直接喂給模型。特征工程就是創(chuàng)造新特征以更好地表示潛在規(guī)律。4.1 從現(xiàn)有特征中創(chuàng)造新特征從日期中提取年月日、星期幾、是否周末、是否節(jié)假日等對(duì)很多模型都很有用。df[年份] df.index.year df[月份] df.index.month df[星期幾] df.index.dayofweek # 周一0, 周日6 df[是否周末] df[星期幾].apply(lambda x: 1 if x 5 else 0)分箱將連續(xù)變量如年齡、收入離散化成幾個(gè)區(qū)間如青年、中年、老年有時(shí)能使線性模型更易捕捉非線性關(guān)系。# 將溫度分為低溫、舒適、高溫三檔 bins [-float(inf), 10, 25, float(inf)] labels [低溫, 舒適, 高溫] df[溫度檔位] pd.cut(df[溫度], binsbins, labelslabels)交互特征將兩個(gè)或多個(gè)特征相乘或相加捕捉協(xié)同效應(yīng)。例如在電商分析中“瀏覽次數(shù)”和“停留時(shí)長(zhǎng)”的乘積可能比單獨(dú)兩個(gè)特征更能預(yù)測(cè)購(gòu)買意愿。4.2 文本特征簡(jiǎn)單處理如果數(shù)據(jù)中有文本列如天氣狀況描述“晴間多云”需要將其轉(zhuǎn)化為數(shù)值。獨(dú)熱編碼將分類變量轉(zhuǎn)換為二進(jìn)制向量。Pandas的get_dummies函數(shù)一鍵完成。# 對(duì)‘天氣狀況’列進(jìn)行獨(dú)熱編碼 weather_dummies pd.get_dummies(df[天氣狀況], prefix天氣) df pd.concat([df, weather_dummies], axis1) # 將新列合并回原數(shù)據(jù)框 print(df[[天氣_晴, 天氣_多云, 天氣_雨]].head())編碼后模型就能理解這些分類信息了。5. 實(shí)戰(zhàn)演練分析城市天氣數(shù)據(jù)讓我們用一個(gè)綜合例子串聯(lián)以上所有步驟。假設(shè)我們有一個(gè)city_weather.csv文件包含多個(gè)城市一段時(shí)間內(nèi)的每日天氣數(shù)據(jù)。5.1 數(shù)據(jù)加載與初探import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # 1. 加載數(shù)據(jù) df pd.read_csv(city_weather.csv, parse_dates[date]) print(數(shù)據(jù)形狀:, df.shape) print(\n前5行數(shù)據(jù):) print(df.head()) print(\n數(shù)據(jù)信息:) print(df.info()) print(\n數(shù)值列描述統(tǒng)計(jì):) print(df.describe())5.2 數(shù)據(jù)清洗與預(yù)處理# 2. 處理缺失值 print(缺失值統(tǒng)計(jì):) print(df.isnull().sum()) # 假設(shè)我們決定溫度用列均值填充天氣狀況用前向填充時(shí)間序列風(fēng)速用0填充 df[temp_max].fillna(df[temp_max].median(), inplaceTrue) df[weather].fillna(methodffill, inplaceTrue) df[wind_speed].fillna(0, inplaceTrue) # 3. 處理異常值以最高溫為例使用IQR法 Q1 df[temp_max].quantile(0.25) Q3 df[temp_max].quantile(0.75) IQR Q3 - Q1 df[temp_max] df[temp_max].clip(lowerQ1-1.5*IQR, upperQ31.5*IQR) # 4. 數(shù)據(jù)類型轉(zhuǎn)換 df[city] df[city].astype(category)5.3 探索性數(shù)據(jù)分析# 5. 單變量分析 plt.figure(figsize(12,4)) plt.subplot(1,2,1) df[temp_max].hist(bins30, edgecolorblack) plt.title(最高溫度分布) plt.subplot(1,2,2) df.boxplot(columntemp_max, bycity) plt.suptitle() plt.title(各城市最高溫分布) plt.tight_layout() plt.show() # 查看天氣狀況頻次 print(df[weather].value_counts()) # 6. 多變量分析 - 城市與溫度的關(guān)系分類 vs 數(shù)值 city_temp_stats df.groupby(city)[temp_max].agg([mean, std, count]) print(city_temp_stats) # 多變量分析 - 溫度與濕度的關(guān)系數(shù)值 vs 數(shù)值 df.plot(kindscatter, xtemp_max, yhumidity, alpha0.5) plt.title(最高溫與濕度散點(diǎn)圖) plt.show() # 計(jì)算相關(guān)系數(shù) corr_matrix df.select_dtypes(include[number]).corr() sns.heatmap(corr_matrix, annotTrue, cmapcoolwarm, center0) plt.title(數(shù)值變量相關(guān)系數(shù)熱圖) plt.show()5.4 特征工程與數(shù)據(jù)重塑# 7. 特征工程 # 從日期提取特征 df[month] df[date].dt.month df[day_of_week] df[date].dt.dayofweek df[is_weekend] df[day_of_week].apply(lambda x: 1 if x 5 else 0) # 對(duì)天氣狀況進(jìn)行獨(dú)熱編碼 weather_dummies pd.get_dummies(df[weather], prefixw) df pd.concat([df, weather_dummies], axis1) # 8. 數(shù)據(jù)準(zhǔn)備建模示例為每個(gè)城市創(chuàng)建單獨(dú)的數(shù)據(jù)集或特征矩陣 # 假設(shè)我們想預(yù)測(cè)溫度選擇特征和標(biāo)簽 features [humidity, wind_speed, month, day_of_week, is_weekend] [col for col in df.columns if col.startswith(w_)] target temp_max X df[features] y df[target] print(特征矩陣形狀:, X.shape) print(目標(biāo)變量形狀:, y.shape)6. 常見(jiàn)問(wèn)題與排查技巧實(shí)錄在實(shí)際操作中你肯定會(huì)遇到各種報(bào)錯(cuò)和奇怪的現(xiàn)象。這里記錄幾個(gè)高頻問(wèn)題問(wèn)題1讀取CSV文件時(shí)出現(xiàn)UnicodeDecodeError。原因文件編碼不是默認(rèn)的utf-8可能是gbk、gb2312或latin-1。解決嘗試指定編碼pd.read_csv(file.csv, encodinggbk)。如果不知道編碼可以用chardet庫(kù)檢測(cè)。問(wèn)題2df.describe()看不到所有列只顯示了數(shù)值列。原因describe()默認(rèn)只針對(duì)數(shù)值列intfloat進(jìn)行統(tǒng)計(jì)。解決使用df.describe(includeall)來(lái)包含所有類型的列包括object和category但非數(shù)值列的統(tǒng)計(jì)項(xiàng)如唯一值、最高頻值會(huì)不同。問(wèn)題3分組聚合groupby后數(shù)據(jù)變成了奇怪的MultiIndex格式難以進(jìn)一步操作。原因groupby操作默認(rèn)會(huì)將分組鍵作為索引。解決在聚合時(shí)使用as_indexFalse參數(shù)或者在聚合后使用reset_index()方法。# 方法1 result df.groupby(city, as_indexFalse)[temp].mean() # 方法2 result df.groupby(city)[temp].mean().reset_index()問(wèn)題4使用fillna或astype等方法后原DataFrame好像沒(méi)變?cè)虼蠖鄶?shù)Pandas操作不是就地修改inplace而是返回一個(gè)新的DataFrame除非顯式指定inplaceTrue或重新賦值。解決# 方式一重新賦值 df[column] df[column].fillna(value) # 方式二使用inplace參數(shù)但該參數(shù)在未來(lái)版本可能被棄用習(xí)慣重新賦值更安全 df[column].fillna(value, inplaceTrue)問(wèn)題5處理大型DataFrame時(shí)內(nèi)存不足或速度極慢。解決思路指定數(shù)據(jù)類型讀入時(shí)用dtype參數(shù)指定每列類型或用astype轉(zhuǎn)換特別是將object轉(zhuǎn)為category分類文本或int/float數(shù)值。使用分塊讀取pd.read_csv(big_file.csv, chunksize10000)然后循環(huán)處理每個(gè)塊。只讀取需要的列pd.read_csv(big_file.csv, usecols[col1, col2])??紤]使用Dask或Modin庫(kù)它們提供了類似Pandas的API但能進(jìn)行并行計(jì)算處理超出內(nèi)存的數(shù)據(jù)。問(wèn)題6時(shí)間序列重采樣或滑動(dòng)窗口計(jì)算時(shí)結(jié)果開(kāi)頭出現(xiàn)很多NaN。原因這是正?,F(xiàn)象。例如計(jì)算7天移動(dòng)平均前6天沒(méi)有足夠的數(shù)據(jù)來(lái)計(jì)算平均值所以結(jié)果是NaN。解決可以使用min_periods參數(shù)來(lái)指定計(jì)算所需的最小觀測(cè)數(shù)。例如rolling(window7, min_periods1).mean()表示即使只有1個(gè)數(shù)據(jù)點(diǎn)也計(jì)算其實(shí)就是它本身這樣前6天就不會(huì)是NaN了。但需理解這改變了計(jì)算含義。掌握這些預(yù)處理和探索技巧意味著你不再是數(shù)據(jù)的被動(dòng)接收者而是主動(dòng)的審視者和塑造者。你能判斷數(shù)據(jù)的質(zhì)量理解數(shù)據(jù)的故事并把它整理成最適合建模的形態(tài)。這往往比后續(xù)選擇哪個(gè)高級(jí)模型更能決定一個(gè)數(shù)學(xué)建模項(xiàng)目的成敗。在下次的分享中我們會(huì)更進(jìn)一步探討如何利用Pandas進(jìn)行更復(fù)雜的數(shù)據(jù)整合、分組聚合以及面向建模的最終數(shù)據(jù)準(zhǔn)備。