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