
1. 项目概述从“集训”到“实战”的第三天如果你正在经历数学建模的集训或者自己制定了为期十天的密集学习计划那么第三天往往是一个关键的分水岭。前两天你可能还在熟悉基本概念、安装环境、回顾数学知识而第三天通常意味着你要开始“动真格”了——直面数据。无论是国赛、美赛还是亚太杯拿到赛题后第一道坎往往就是那一堆可能杂乱无章、格式不一的数据。如何快速、准确、高效地“驯服”这些数据将其转化为可供模型使用的“燃料”是决定你后续建模效率甚至成败的关键一步。今天我们就聚焦于这个核心环节利用 Python 生态中的两大神器——NumPy 和 Pandas进行数据分析的预处理与探索。这不仅仅是学习几个函数。在真实的数学建模竞赛中数据处理的时间可能占到整个解题过程的30%-50%。一个干净、结构化的数据集能让你的模型构建事半功倍而一个充满缺失值、异常值和错误格式的数据集则可能让你在模型调试的泥潭里挣扎数小时。因此第三天的目标非常明确掌握用 NumPy 处理数值计算和多维数组的底层能力以及用 Pandas 进行表格数据操作和高级分析的“瑞士军刀”级技巧。我们将从最实际的场景出发模拟一道赛题的数据处理全流程让你不仅知道函数怎么用更明白在什么情况下该用什么以及为什么这么做。2. 核心工具解析NumPy与Pandas的定位与协同在动手之前我们必须厘清 NumPy 和 Pandas 的关系与分工。很多新手会混淆两者的用途导致代码冗长低效。2.1 NumPy高性能数值计算的基石NumPy 的核心是ndarrayN-dimensional arrayN维数组。你可以把它想象成一个极其高效、专为数值计算设计的“超级表格”但它的行和列必须是同一种数据类型通常是整数或浮点数。它的优势在于速度快底层由C语言实现对数组的操作如矩阵乘法、求和、统计避免了Python循环的低效速度提升可达数十甚至上百倍。功能强提供了丰富的数学函数np.sin,np.exp、线性代数运算np.dot,np.linalg.inv、随机数生成np.random和广播机制是许多科学计算库如SciPy、Scikit-learn的基础。在数学建模中NumPy 主要承担核心数值算法实现和模型输入/输出的数据容器角色。例如当你需要实现一个自定义的优化算法、进行矩阵运算求解方程组或者将处理好的数据转换为模型所需的矩阵格式时NumPy 是你的不二之选。注意不要试图用 NumPy 数组来存储混杂了字符串、日期和数字的数据这会导致所有数据被强制转换为字符串类型丧失数值计算能力。2.2 Pandas数据分析与处理的“指挥官”Pandas 构建在 NumPy 之上其核心数据结构是Series一维带标签数组和DataFrame二维表格型数据结构。DataFrame可以理解为 Excel 表格在 Python 中的化身但功能强大得多。异构数据每一列可以是不同的数据类型整数、浮点数、字符串、时间等。智能索引支持通过行/列标签loc或位置iloc进行灵活的数据切片和选择。数据处理流水线提供了数据清洗处理缺失值、重复值、转换类型转换、合并、透视、分组聚合、时间序列分析等一站式解决方案。在数学建模中Pandas 是你进行数据导入、清洗、探索和预处理的主要工具。从CSV、Excel文件读入原始数据到检查数据质量、处理异常、特征工程再到将干净数据输出给 NumPy 或模型库Pandas 贯穿始终。2.3 协同工作流从Pandas到NumPy再回到Pandas一个典型的数据处理流程是数据入口用 Pandas 的read_csv、read_excel读取原始数据得到一个DataFrame。数据清洗与探索在 Pandas 中进行数据查看df.head()、df.info()、处理缺失值df.dropna()、df.fillna()、类型转换df.astype()、特征衍生等。模型准备将清洗好的DataFrame中的数值列通过.values属性转换为 NumPy 数组ndarray喂给机器学习模型如 Scikit-learn或你自己的数值计算函数。结果整合将模型输出的 NumPy 数组结果重新包装成 Pandas 的Series或DataFrame方便后续的分析、可视化或输出到文件。理解这个分工能让你在编码时思路清晰工具选用得当。3. 环境准备与核心库安装避坑指南工欲善其事必先利其器。安装是第一步也是新手最容易“卡住”的地方。3.1 Python与IDE的选择Python版本强烈推荐使用 Python 3.8 及以上版本它们对科学计算库的支持最稳定。避免使用 Python 2.x。集成开发环境IDEPyCharm推荐功能全面对项目管理、调试、库安装的支持非常好特别适合中型项目。社区版免费。VS Code轻量灵活通过安装 Python 插件也能获得极佳的体验适合喜欢高度自定义的用户。Jupyter Notebook / Lab非常适合数据探索和交互式分析能分段执行代码并即时看到结果和图表是数学建模中写“解题草稿”的利器。建议在本地安装或使用 VS Code 的 Jupyter 扩展。3.2 使用pip安装NumPy与Pandas打开你的命令行Windows 的 CMD/PowerShellMac/Linux 的 Terminal执行以下命令。最关键的一步是使用国内镜像源速度会快很多。# 使用清华镜像源安装 pip install numpy pandas -i https://pypi.tuna.tsinghua.edu.cn/simple如果你已经安装了 Anaconda一个集成了大量科学计算包的Python发行版那么 NumPy 和 Pandas 通常已经预装好了。可以通过conda list查看。3.3 常见安装问题与解决方案pip不是内部或外部命令原因Python 未正确添加到系统环境变量 PATH 中。解决重新安装 Python 时务必勾选 “Add Python to PATH”。如果已安装需手动添加 Python 安装目录和其下的Scripts目录到系统环境变量。安装超时或速度极慢解决如上所示始终使用-i参数指定国内镜像源。除了清华源还有阿里云 (https://mirrors.aliyun.com/pypi/simple/)、豆瓣源等。PyCharm中安装失败解决不要在PyCharm的终端里直接打pip。点击File - Settings - Project - Python Interpreter点击号搜索numpy和pandas进行安装。这里本质上也是调用pip但环境管理更清晰。版本冲突现象安装某个包时提示已存在的某个包版本不兼容。解决这是最棘手的问题。建议为数学建模项目创建一个独立的虚拟环境virtual environment。在项目目录下# 创建虚拟环境 python -m venv math_modeling_env # 激活环境 (Windows) math_modeling_env\Scripts\activate # 激活环境 (Mac/Linux) source math_modeling_env/bin/activate # 在激活的环境下安装包 pip install numpy pandas ...这样能保证项目依赖的纯净性。安装成功后在 Python 交互环境或脚本开头导入并验证版本import numpy as np import pandas as pd print(fNumPy版本: {np.__version__}) print(fPandas版本: {pd.__version__})4. 实战演练模拟赛题数据预处理全流程让我们通过一个模拟的赛题场景将知识串联起来。假设题目是“城市共享单车使用情况分析与预测”我们拿到了一个bike_data.csv文件。4.1 数据加载与初窥import numpy as np import pandas as pd # 1. 加载数据 df pd.read_csv(bike_data.csv) # 假设文件在当前目录 # 如果文件路径有中文或空格建议使用原始字符串 rC:\path\to\file.csv # 2. 首次查看了解数据全貌 print(数据形状行数列数:, df.shape) print(\n前5行数据:) print(df.head()) print(\n数据基本信息列名、非空数量、类型:) print(df.info()) print(\n数值型列的快速统计描述:) print(df.describe())执行df.info()后你可能会看到类似输出class pandas.core.frame.DataFrame RangeIndex: 10000 entries, 0 to 9999 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 datetime 10000 non-null object 1 season 10000 non-null int64 2 holiday 9990 non-null float64 3 workingday 10000 non-null int64 4 weather 10000 non-null int64 5 temp 9980 non-null float64 6 atemp 9980 non-null float64 7 humidity 9950 non-null float64 8 windspeed 9900 non-null float64 9 count 10000 non-null int64 # 目标变量租车数量 dtypes: float64(5), int64(4), object(1) memory usage: 781.4 KB立刻能发现的问题datetime是object类型即字符串需要转换为时间类型。holiday,temp,atemp,humidity,windspeed存在数量不等的缺失值Non-Null Count小于总行数。holiday是float64但按理应该是0/1的整数类型。4.2 数据清洗处理缺失值与异常值处理缺失值策略取决于缺失比例和业务逻辑。# 查看每列缺失比例 missing_ratio df.isnull().sum() / len(df) print(各列缺失值比例:) print(missing_ratio) # 策略1删除缺失值过多的列例如缺失超过30% # df df.drop(columns[windspeed]) # 假设windspeed缺失多且不重要 # 策略2删除含有缺失值的行适用于缺失很少且行数充足的情况 # df df.dropna() # 谨慎使用可能删掉大量数据 # 策略3填充缺失值最常用 # 对于数值列用均值、中位数或前后值填充 df[temp].fillna(df[temp].median(), inplaceTrue) # 用中位数填充温度避免极端值影响 df[humidity].fillna(df[humidity].mean(), inplaceTrue) # 用均值填充湿度 # 对于分类列用众数填充 df[holiday].fillna(df[holiday].mode()[0], inplaceTrue) # 对于可能有时序关系的列用前向或后向填充 # df[windspeed].fillna(methodffill, inplaceTrue) # 用前一个值填充 # 转换数据类型 df[holiday] df[holiday].astype(int) df[datetime] pd.to_datetime(df[datetime]) # 转换为datetime类型处理异常值常用方法是基于统计学如3σ原则或业务知识。# 方法1基于标准差适用于近似正态分布的数据 mean_val df[count].mean() std_val df[count].std() upper_bound mean_val 3 * std_val lower_bound mean_val - 3 * std_val # 标记异常值或将其截断 df[count_clipped] df[count].clip(lowerlower_bound, upperupper_bound) # 方法2基于分位数IQR法 Q1 df[count].quantile(0.25) Q3 df[count].quantile(0.75) IQR Q3 - Q1 lower_bound_iqr Q1 - 1.5 * IQR upper_bound_iqr Q3 1.5 * IQR # 直接过滤掉异常值所在的行谨慎 # df df[(df[count] lower_bound_iqr) (df[count] upper_bound_iqr)]实操心得对于目标变量如count的异常值要特别小心。直接删除可能会损失重要信息比如节假日爆发性增长。更好的做法是将其视为特殊事件或使用更稳健的模型。对于特征变量如windspeed的异常值可以尝试截断或视为缺失值处理。4.3 特征工程从原始数据中挖掘信息特征工程是提升模型性能的关键。利用datetime列我们可以提取出大量有意义的特征。# 从datetime列提取时间特征 df[hour] df[datetime].dt.hour df[day_of_week] df[datetime].dt.dayofweek # 周一0周日6 df[month] df[datetime].dt.month df[is_weekend] df[day_of_week].apply(lambda x: 1 if x 5 else 0) df[is_rush_hour] df[hour].apply(lambda x: 1 if (7 x 9) or (17 x 19) else 0) # 创建交互特征例如温度和湿度的综合体感 df[temp_humidity] df[temp] * df[humidity] # 对分类特征进行编码如果后续使用树模型有时不需要 # 独热编码 (One-Hot Encoding) # season_dummies pd.get_dummies(df[season], prefixseason) # df pd.concat([df, season_dummies], axis1)4.4 数据转换与Pandas-NumPy协作清洗和特征工程后数据通常还在Pandas DataFrame中。在送入模型前我们需要将其转换为NumPy数组并划分特征X和目标y。# 选择最终用于建模的特征列 feature_columns [season, holiday, workingday, weather, temp, humidity, windspeed, hour, is_weekend, is_rush_hour, temp_humidity] target_column count X df[feature_columns].values # .values 属性将 DataFrame 转换为 NumPy 数组 y df[target_column].values print(f特征矩阵 X 的形状: {X.shape}) # (样本数 特征数) print(f目标向量 y 的形状: {y.shape}) # (样本数) # 此时X 和 y 都是纯粹的 NumPy ndarray可以用于 # 1. 使用 Scikit-learn 进行数据标准化、划分训练测试集 # 2. 自己编写梯度下降等数值优化算法 # 3. 进行复杂的矩阵运算5. NumPy核心操作为建模提供计算引擎当数据准备好后NumPy 负责底层的重型计算。以下是数学建模中最高频的操作。5.1 数组创建与索引切片# 创建数组 arr_from_list np.array([1, 2, 3, 4, 5]) arr_zeros np.zeros((3, 4)) # 3行4列的全0矩阵 arr_ones np.ones((2, 3, 4)) # 2个3行4列的全1三维数组 arr_range np.arange(0, 10, 2) # 类似 range但生成数组 [0, 2, 4, 6, 8] arr_random np.random.randn(100, 5) # 100行5列的标准正态分布随机数 # 索引与切片与Python列表类似但支持多维 arr np.array([[1,2,3], [4,5,6], [7,8,9]]) print(arr[0, 1]) # 输出 2 (第0行第1列) print(arr[:, 1]) # 输出所有行的第1列 [2, 5, 8] print(arr[1:, :2]) # 输出第1行及之后所有行的前2列 [[4,5], [7,8]] # 布尔索引非常强大 condition arr 5 print(condition) # 布尔矩阵 print(arr[condition]) # 输出所有大于5的元素 [6, 7, 8, 9]5.2 广播机制与向量化运算这是 NumPy 高效的核心。避免使用 Python 的for循环# 低效的循环做法 a np.random.rand(10000) b np.random.rand(10000) result_loop np.empty(10000) for i in range(10000): result_loop[i] a[i] b[i] # 高效的向量化做法 result_vectorized a b # 直接对整个数组操作速度极快 # 广播允许不同形状的数组进行运算 matrix np.ones((3, 4)) # 3x4 row_vector np.array([1, 2, 3, 4]) # (4,) # row_vector 会被“广播”成 [[1,2,3,4], [1,2,3,4], [1,2,3,4]]然后与 matrix 相加 result_broadcast matrix row_vector print(result_broadcast)5.3 线性代数与统计函数数学建模离不开线性代数。# 矩阵乘法 A np.array([[1,2], [3,4]]) B np.array([[5,6], [7,8]]) C_dot np.dot(A, B) # 或者 A B (Python 3.5) print(矩阵乘法:\n, C_dot) # 矩阵转置、逆、行列式 A_T A.T # 求逆前最好判断是否可逆行列式不为0 if np.linalg.det(A) ! 0: A_inv np.linalg.inv(A) print(矩阵的逆:\n, A_inv) # 解线性方程组 Ax b b np.array([5, 11]) x np.linalg.solve(A, b) # 比先求逆再乘更快更稳定 print(f方程的解: {x}) # 统计函数 data np.random.randn(1000) print(f均值: {np.mean(data):.2f}) print(f标准差: {np.std(data):.2f}) print(f中位数: {np.median(data):.2f}) print(f相关系数矩阵:\n {np.corrcoef(X[:, :5], rowvarFalse)}) # 计算前5个特征的相关系数6. Pandas进阶技巧高效数据操作与聚合6.1 数据分组与聚合GroupBy这是数据分析的“灵魂”操作。例如我们想看看不同季节season和不同小时hour的平均租车数量。# 单层分组 group_by_season df.groupby(season)[count].mean() print(不同季节的平均租车量:) print(group_by_season) # 多层分组 group_by_season_hour df.groupby([season, hour])[count].agg([mean, std, count]) print(\n不同季节、不同小时段的租车量统计:) print(group_by_season_hour.head(10)) # 重置索引将分组结果变回方便的DataFrame result_df group_by_season_hour.reset_index()6.2 数据合并与连接当数据来自多个源时需要合并。# 创建两个示例DataFrame df1 pd.DataFrame({key: [A, B, C], value1: [1, 2, 3]}) df2 pd.DataFrame({key: [B, C, D], value2: [4, 5, 6]}) # 内连接默认只保留两个都有的key inner_join pd.merge(df1, df2, onkey, howinner) # 结果B, C # 左连接保留左边所有右边没有的填NaN left_join pd.merge(df1, df2, onkey, howleft) # 结果A, B, C (D没有) # 外连接保留所有 outer_join pd.merge(df1, df2, onkey, howouter) # 结果A, B, C, D6.3 数据透视表透视表可以快速进行多维度的交叉分析功能类似Excel数据透视表。# 创建一个透视表查看不同天气(weather)和工作日/非工作日(workingday)下的平均温度和平均租车量 pivot_table df.pivot_table( values[temp, count], # 要聚合的数值列 indexweather, # 行索引 columnsworkingday, # 列索引 aggfunc{temp: mean, count: [mean, sum]} # 对不同列应用不同的聚合函数 ) print(pivot_table)7. 常见问题与排查技巧实录在实际操作中你一定会遇到各种报错和意外情况。这里记录几个最典型的。7.1 数据类型错误与转换问题进行数值计算时报错TypeError: unsupported operand type(s)或者用比较浮点数出现意外结果。排查先用df.dtypes或arr.dtype查看数据类型。字符串或对象类型无法参与计算。解决# Pandas 转换 df[column] pd.to_numeric(df[column], errorscoerce) # 强制转换非数字变NaN df[column] df[column].astype(float) # 已知是数字时直接转 # NumPy 转换 arr arr.astype(np.float64) # 浮点数比较 # 错误if a b: # 正确if np.abs(a - b) 1e-9: # 使用极小容差7.2 索引错乱与重置问题经过多次筛选、合并后DataFrame 的行索引变得不连续如 0, 3, 5, 7...这可能导致某些按位置索引的操作iloc出错。解决使用df.reset_index(dropTrue, inplaceTrue)。dropTrue表示不把旧索引存为新列inplaceTrue表示直接修改原df。7.3 内存优化问题处理大型数据集几十万行以上时内存占用过高程序变慢甚至崩溃。技巧指定数据类型读入时就用dtype参数指定如df pd.read_csv(data.csv, dtype{column1: int32, column2: float32})。int32、float32比默认的int64、float64省一半内存。分类类型对于重复值多的字符串列如‘天气’、‘城市名’转换为category类型。df[weather] df[weather].astype(category)分块读取对于超大文件使用chunksize参数。chunk_iter pd.read_csv(huge_data.csv, chunksize50000) for chunk in chunk_iter: process(chunk) # 逐块处理7.4 性能优化避免链式赋值问题代码df[df[count] 100][temp] 25可能不会报错但实际修改可能不成功或触发SettingWithCopyWarning。原因这是链式索引chained indexingPandas 无法判断你是想修改原始数据的一个副本还是视图。正确做法使用.loc或.iloc进行单次索引赋值。# 正确 df.loc[df[count] 100, temp] 25 # 或者如果你确定要修改一个副本 df_subset df[df[count] 100].copy() df_subset[temp] 25集训第三天核心是建立起“数据驱动”的思维。拿到数据后不要急于套模型而是花足够的时间在 Pandas 中进行探索和清洗理解每一个变量的分布、关系和潜在问题。然后用 NumPy 为核心计算做好准备。这个基础打牢了后续的建模、求解和验证才会顺畅。在实际比赛中你可以将这部分数据处理流程封装成函数或模块在拿到新题新数据时快速复用这将为你节省大量宝贵时间。记住干净的数据是优秀模型的一半。