# 古代玻璃成分数据清洗与预处理的Python实现方案
## 问题解构与处理思路
古代玻璃成分数据的清洗与预处理是数模竞赛C题中的基础且关键环节。根据竞赛要求,需要系统性地解决以下核心问题:
| 处理阶段 | 主要任务 | 技术难点 |
|---------|---------|---------|
| 数据读取 | 多表单数据加载与整合 | 处理Excel多sheet结构,数据类型识别 |
| 缺失值处理 | 填补或删除缺失数据 | 选择合理的填充策略,避免引入偏差 |
| 异常值检测 | 识别并处理异常数据点 | 确定异常值阈值,区分真实异常与测量误差 |
| 数据规范化 | 统一数据尺度与格式 | 处理不同量纲的化学成分数据 |
## 完整代码实现方案
### 1. 环境配置与数据读取
```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
from sklearn.preprocessing import StandardScaler
import warnings
warnings.filterwarnings('ignore')
# 读取三个表单的数据
def load_data(file_path):
"""
加载古代玻璃成分数据
"""
try:
df1 = pd.read_excel(file_path, sheet_name='表单1')
df2 = pd.read_excel(file_path, sheet_name='表单2')
df3 = pd.read_excel(file_path, sheet_name='表单3')
print(f"表单1形状: {df1.shape}, 表单2形状: {df2.shape}, 表单3形状: {df3.shape}")
return df1, df2, df3
except Exception as e:
print(f"数据读取错误: {e}")
return None, None, None
# 示例调用
df1, df2, df3 = load_data('附件.xlsx')
```
### 2. 数据质量评估与探索
```python
def data_quality_assessment(df, sheet_name):
"""
全面评估数据质量
"""
print(f"\n=== {sheet_name} 数据质量评估 ===")
# 基本信息
print(f"数据形状: {df.shape}")
print(f"列名: {list(df.columns)}")
# 缺失值统计
missing_info = df.isnull().sum()
missing_percent = (missing_info / len(df)) * 100
missing_df = pd.DataFrame({
'缺失数量': missing_info,
'缺失比例(%)': missing_percent
})
print("\n缺失值统计:")
print(missing_df[missing_df['缺失数量'] > 0])
# 数据类型检查
print(f"\n数据类型分布:")
print(df.dtypes.value_counts())
return missing_df
# 对各表单进行质量评估
missing_df1 = data_quality_assessment(df1, "表单1")
missing_df2 = data_quality_assessment(df2, "表单2")
missing_df3 = data_quality_assessment(df3, "表单3")
```
### 3. 缺失值处理策略
```python
def handle_missing_values(df, strategy='auto'):
"""
智能处理缺失值
strategy: 'auto'自动选择, 'mean'均值填充, 'median'中位数填充, 'delete'删除
"""
df_clean = df.copy()
# 识别数值型列和分类型列
numeric_cols = df_clean.select_dtypes(include=[np.number]).columns
categorical_cols = df_clean.select_dtypes(include=['object']).columns
print(f"数值型列: {list(numeric_cols)}")
print(f"分类型列: {list(categorical_cols)}")
for col in df_clean.columns:
if df_clean[col].isnull().sum() > 0:
missing_count = df_clean[col].isnull().sum()
missing_ratio = (missing_count / len(df_clean)) * 100
print(f"\n处理列 {col}: 缺失{missing_count}个({missing_ratio:.2f}%)")
if col in numeric_cols:
# 数值型列处理
if strategy == 'auto':
# 根据缺失比例选择策略
if missing_ratio < 5:
# 少量缺失使用中位数填充(对异常值不敏感)
fill_value = df_clean[col].median()
method = 'median'
elif missing_ratio < 30:
# 中等缺失使用均值填充
fill_value = df_clean[col].mean()
method = 'mean'
else:
# 大量缺失考虑删除或高级插值
fill_value = df_clean[col].median()
method = 'median(大量缺失)'
else:
# 指定策略
if strategy == 'mean':
fill_value = df_clean[col].mean()
method = 'mean'
elif strategy == 'median':
fill_value = df_clean[col].median()
method = 'median'
else:
fill_value = df_clean[col].mean()
method = 'mean'
df_clean[col] = df_clean[col].fillna(fill_value)
print(f" → 使用{method}填充: {fill_value:.4f}")
elif col in categorical_cols:
# 分类型列使用众数填充
mode_value = df_clean[col].mode()[0] if not df_clean[col].mode().empty else '未知'
df_clean[col] = df_clean[col].fillna(mode_value)
print(f" → 使用众数填充: {mode_value}")
return df_clean
# 应用缺失值处理
df1_clean = handle_missing_values(df1, strategy='auto')
df2_clean = handle_missing_values(df2, strategy='auto')
df3_clean = handle_missing_values(df3, strategy='auto')
```
### 4. 异常值检测与处理
```python
def detect_and_handle_outliers(df, method='iqr', threshold=3):
"""
多方法异常值检测与处理
method: 'iqr'四分位距法, 'zscore'Z分数法, 'isolation'孤立森林
"""
df_outlier_processed = df.copy()
numeric_cols = df_outlier_processed.select_dtypes(include=[np.number]).columns
outlier_report = {}
for col in numeric_cols:
original_data = df_outlier_processed[col].dropna()
if method == 'iqr':
# 四分位距法
Q1 = original_data.quantile(0.25)
Q3 = original_data.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = original_data[(original_data < lower_bound) | (original_data > upper_bound)]
elif method == 'zscore':
# Z分数法
z_scores = np.abs(stats.zscore(original_data))
outliers = original_data[z_scores > threshold]
lower_bound = original_data.mean() - threshold * original_data.std()
upper_bound = original_data.mean() + threshold * original_data.std()
outlier_count = len(outliers)
outlier_ratio = (outlier_count / len(original_data)) * 100
outlier_report[col] = {
'outlier_count': outlier_count,
'outlier_ratio': outlier_ratio,
'lower_bound': lower_bound if 'lower_bound' in locals() else None,
'upper_bound': upper_bound if 'upper_bound' in locals() else None,
'outliers': outliers
}
# 对异常值进行缩尾处理(Winsorization)
if outlier_count > 0:
if method == 'iqr':
df_outlier_processed[col] = np.clip(df_outlier_processed[col], lower_bound, upper_bound)
elif method == 'zscore':
df_outlier_processed[col] = np.clip(df_outlier_processed[col], lower_bound, upper_bound)
return df_outlier_processed, outlier_report
# 应用异常值处理
df1_final, outlier_report1 = detect_and_handle_outliers(df1_clean, method='iqr')
df2_final, outlier_report2 = detect_and_handle_outliers(df2_clean, method='iqr')
df3_final, outlier_report3 = detect_and_handle_outliers(df3_clean, method='iqr')
# 输出异常值报告
def print_outlier_report(report, sheet_name):
print(f"\n=== {sheet_name} 异常值检测报告 ===")
for col, info in report.items():
if info['outlier_count'] > 0:
print(f"{col}: {info['outlier_count']}个异常值({info['outlier_ratio']:.2f}%)")
print_outlier_report(outlier_report1, "表单1")
print_outlier_report(outlier_report2, "表单2")
print_outlier_report(outlier_report3, "表单3")
```
### 5. 数据可视化与验证
```python
def visualize_data_quality(original_df, cleaned_df, sheet_name):
"""
可视化数据清洗效果
"""
numeric_cols = original_df.select_dtypes(include=[np.number]).columns
# 选择前4个数值型特征进行可视化
cols_to_plot = numeric_cols[:4] if len(numeric_cols) >= 4 else numeric_cols
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
axes = axes.ravel()
for i, col in enumerate(cols_to_plot):
if i < 4:
# 绘制箱线图对比
data_to_plot = [original_df[col].dropna(), cleaned_df[col]]
axes[i].boxplot(data_to_plot, labels=['原始', '清洗后'])
axes[i].set_title(f'{col} - 清洗前后对比')
axes[i].set_ylabel('数值')
plt.suptitle(f'{sheet_name} - 数据清洗效果可视化', fontsize=16)
plt.tight_layout()
plt.show()
# 生成可视化报告
visualize_data_quality(df1, df1_final, "表单1")
visualize_data_quality(df2, df2_final, "表单2")
visualize_data_quality(df3, df3_final, "表单3")
```
### 6. 数据标准化与特征工程
```python
def feature_engineering(df):
"""
特征工程:创建衍生特征和数据标准化
"""
df_engineered = df.copy()
numeric_cols = df_engineered.select_dtypes(include=[np.number]).columns
# 1. 数据标准化
scaler = StandardScaler()
scaled_features = scaler.fit_transform(df_engineered[numeric_cols])
df_engineered[numeric_cols] = scaled_features
# 2. 创建化学成分比例特征(如果适用)
# 示例:计算主要氧化物比例
oxide_columns = [col for col in numeric_cols if '氧化' in col or 'O' in col]
if len(oxide_columns) >= 2:
# 创建总氧化物含量特征
df_engineered['总氧化物含量'] = df_engineered[oxide_columns].sum(axis=1)
# 创建主要氧化物比例特征
for i, col in enumerate(oxide_columns[:3]): # 只处理前3个主要氧化物
df_engineered[f'{col}_占比'] = df_engineered[col] / df_engineered['总氧化物含量']
print(f"特征工程完成,新增特征: {set(df_engineered.columns) - set(df.columns)}")
return df_engineered, scaler
# 应用特征工程
df1_engineered, scaler1 = feature_engineering(df1_final)
df2_engineered, scaler2 = feature_engineering(df2_final)
df3_engineered, scaler3 = feature_engineering(df3_final)
```
### 7. 完整预处理流水线
```python
class GlassDataPreprocessor:
"""
古代玻璃数据预处理流水线
"""
def __init__(self):
self.scaler = None
self.preprocessing_info = {}
def fit_transform(self, df, sheet_name='data'):
"""
完整的预处理流程
"""
print(f"开始处理 {sheet_name}...")
# 1. 缺失值处理
df_step1 = handle_missing_values(df)
# 2. 异常值处理
df_step2, outlier_report = detect_and_handle_outliers(df_step1)
# 3. 特征工程
df_final, self.scaler = feature_engineering(df_step2)
# 保存处理信息
self.preprocessing_info[sheet_name] = {
'original_shape': df.shape,
'final_shape': df_final.shape,
'outlier_report': outlier_report,
'scaler': self.scaler
}
print(f"{sheet_name} 预处理完成!")
return df_final
def transform(self, df, sheet_name='new_data'):
"""
对新数据应用相同的预处理
"""
# 应用相同的缺失值处理和异常值检测
df_step1 = handle_missing_values(df)
df_step2, _ = detect_and_handle_outliers(df_step1)
# 使用训练时的scaler进行标准化
if self.scaler is not None:
numeric_cols = df_step2.select_dtypes(include=[np.number]).columns
df_step2[numeric_cols] = self.scaler.transform(df_step2[numeric_cols])
return df_step2
# 使用预处理流水线
preprocessor = GlassDataPreprocessor()
# 处理训练数据(表单1和2)
df1_processed = preprocessor.fit_transform(df1, '表单1')
df2_processed = preprocessor.fit_transform(df2, '表单2')
# 用相同的预处理处理测试数据(表单3)
df3_processed = preprocessor.transform(df3, '表单3')
```
## 关键技术要点解析
### 1. 缺失值处理策略选择
在古代玻璃成分数据分析中,不同的化学成分具有不同的物理意义,因此需要针对性地选择缺失值处理方法:
- **关键主量元素**(如SiO₂、Na₂O、K₂O等):使用中位数填充,避免极端值影响
- **微量元素**:根据缺失比例决定,少量缺失使用均值,大量缺失可考虑删除该特征
- **分类特征**:使用众数填充或创建"未知"类别
### 2. 异常值处理的专业性考虑
玻璃成分数据中的"异常值"可能包含重要信息:
- **真实异常**:测量误差或数据录入错误,需要处理
- **特殊样本**:代表特殊的玻璃配方或工艺,应保留但标记
- **边界情况**:正常分布的极端值,使用缩尾处理而非直接删除
### 3. 数据标准化的必要性
化学成分数据通常具有不同的量纲和数量级,标准化可以:
- 消除量纲影响,使模型更公平地对待所有特征
- 提高聚类和分类算法的收敛速度和性能
- 便于后续的数据可视化和分析
## 实践建议与注意事项
1. **分批验证**:每完成一个预处理步骤后,检查数据分布和统计特征的变化
2. **文档记录**:详细记录每个处理决策的理由和参数,便于复现和调整
3. **多次迭代**:预处理不是一次性的,可能需要根据模型反馈进行调整
4. **领域知识结合**:充分利用古代玻璃制作的化学知识指导预处理策略
通过这套完整的Python预处理方案,能够为后续的玻璃分类、聚类分析和未知样品鉴别提供高质量、规范化的数据基础,显著提升数学建模竞赛作品的质量和竞争力。