跳转到内容
RailWise KB已发布

批量数据处理模板

RailWise批量数据处理标准化代码模板,支持大规模监测数据的批量清洗、转换、计算、分析与归档,实现高效的大数据量处理工作流。

复核 2026-07-09入门公开可引用RailWise 技术团队
template-doc

本模板提供大规模监测数据的批量处理标准化流程,支持:

  • 批量清洗:缺失值处理、异常值检测、重复数据删除
  • 批量转换:格式统一、坐标转换、单位换算
  • 批量计算:累计位移计算、变化速率计算、趋势分析
  • 批量分析:统计分析、预警检测、报告生成
  • 批量归档:数据压缩、分区存储、生命周期管理
场景 数据量 处理模式 性能要求
历史数据迁移 百万级 离线批处理 高吞吐
多项目汇总 十万级 并行批处理 中等
日终数据处理 万级 定时批处理 低延迟
全量数据分析 千万级 分布式处理 高吞吐
数据归档清理 百万级 离线批处理 高吞吐
  • 输入:原始数据文件、数据库查询结果、数据流
  • 输出:清洗后的数据、计算结果、分析报告、归档文件
  • 中间产物:处理日志、错误记录、统计报告

"""
RailWise 批量数据处理引擎
支持大规模监测数据的高效批量处理
作者: RailWise技术部
版本: 1.0.0
"""
import gc
import logging
import multiprocessing as mp
import time
from abc import ABC, abstractmethod
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union
import numpy as np
import pandas as pd
from tqdm import tqdm
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("RailWise.BatchProcessor")
class ProcessingMode(Enum):
"""处理模式枚举"""
SEQUENTIAL = "sequential" # 串行处理
MULTITHREAD = "multithread" # 多线程处理
MULTIPROCESS = "multiprocess" # 多进程处理
DISTRIBUTED = "distributed" # 分布式处理
class DataQualityAction(Enum):
"""数据质量处理动作"""
DROP = "drop" # 删除
FILL_ZERO = "fill_zero" # 填充0
FILL_MEAN = "fill_mean" # 填充均值
FILL_MEDIAN = "fill_median" # 填充中位数
FILL_FORWARD = "fill_forward" # 前向填充
FILL_INTERPOLATE = "interpolate" # 插值填充
FLAG = "flag" # 标记但不处理
@dataclass
class BatchConfig:
"""批处理配置"""
batch_size: int = 10000 # 每批处理记录数
max_workers: int = 4 # 最大工作进程/线程数
processing_mode: ProcessingMode = ProcessingMode.MULTITHREAD
memory_limit_mb: int = 1024 # 内存限制(MB)
checkpoint_interval: int = 100 # 检查点间隔(批次数)
output_dir: Path = field(default_factory=lambda: Path("./batch_output"))
temp_dir: Path = field(default_factory=lambda: Path("./batch_temp"))
enable_progress_bar: bool = True
enable_checkpoint: bool = True
retry_count: int = 3 # 失败重试次数
retry_delay_seconds: int = 5 # 重试延迟(秒)
@dataclass
class ProcessingResult:
"""处理结果"""
success: bool
records_processed: int = 0
records_failed: int = 0
records_skipped: int = 0
processing_time_ms: float = 0.0
output_files: List[Path] = field(default_factory=list)
errors: List[str] = field(default_factory=list)
warnings: List[str] = field(default_factory=list)
statistics: Dict[str, Any] = field(default_factory=dict)
class DataChunk:
"""数据分块容器"""
def __init__(self, chunk_id: int, data: pd.DataFrame):
self.chunk_id = chunk_id
self.data = data
self.metadata = {
"chunk_id": chunk_id,
"record_count": len(data),
"columns": list(data.columns),
"memory_usage_mb": data.memory_usage(deep=True).sum() / 1024 / 1024
}
def __repr__(self):
return f"DataChunk(id={self.chunk_id}, records={self.metadata['record_count']})"
class BatchProcessor(ABC):
"""批处理抽象基类"""
def __init__(self, config: BatchConfig):
self.config = config
self.logger = logging.getLogger(self.__class__.__name__)
self.checkpoint_data: Dict[str, Any] = {}
# 确保目录存在
self.config.output_dir.mkdir(parents=True, exist_ok=True)
self.config.temp_dir.mkdir(parents=True, exist_ok=True)
@abstractmethod
def process_chunk(self, chunk: DataChunk) -> DataChunk:
"""处理单个数据块"""
pass
def pre_process(self, data: pd.DataFrame) -> pd.DataFrame:
"""预处理(可选重写)"""
return data
def post_process(self, data: pd.DataFrame) -> pd.DataFrame:
"""后处理(可选重写)"""
return data
def validate_chunk(self, chunk: DataChunk) -> Tuple[bool, List[str]]:
"""验证数据块"""
errors = []
if chunk.data.empty:
errors.append(f"数据块 {chunk.chunk_id} 为空")
if chunk.metadata["memory_usage_mb"] > self.config.memory_limit_mb:
errors.append(f"数据块 {chunk.chunk_id} 内存使用超限: {chunk.metadata['memory_usage_mb']:.2f}MB")
return len(errors) == 0, errors
def save_checkpoint(self, batch_index: int):
"""保存检查点"""
if not self.config.enable_checkpoint:
return
checkpoint_path = self.config.temp_dir / f"checkpoint_{batch_index}.json"
import json
with open(checkpoint_path, 'w', encoding='utf-8') as f:
json.dump(self.checkpoint_data, f, ensure_ascii=False, default=str)
self.logger.info(f"检查点已保存: {checkpoint_path}")
def load_checkpoint(self) -> Optional[Dict[str, Any]]:
"""加载检查点"""
checkpoint_files = sorted(self.config.temp_dir.glob("checkpoint_*.json"))
if checkpoint_files:
latest = checkpoint_files[-1]
import json
with open(latest, 'r', encoding='utf-8') as f:
return json.load(f)
return None
def process(
self,
data_source: Union[pd.DataFrame, Iterator[pd.DataFrame], Path],
total_records: Optional[int] = None
) -> ProcessingResult:
"""
执行批处理
Args:
data_source: 数据源(DataFrame/迭代器/文件路径)
total_records: 总记录数(用于进度显示)
Returns:
处理结果
"""
start_time = time.time()
result = ProcessingResult(success=False)
try:
# 1. 加载检查点
checkpoint = self.load_checkpoint()
start_batch = checkpoint.get("last_batch", 0) if checkpoint else 0
# 2. 分块处理
chunks = self._create_chunks(data_source)
# 3. 选择处理模式
if self.config.processing_mode == ProcessingMode.SEQUENTIAL:
processed_chunks = self._process_sequential(chunks, start_batch, total_records)
elif self.config.processing_mode == ProcessingMode.MULTITHREAD:
processed_chunks = self._process_multithread(chunks, start_batch, total_records)
elif self.config.processing_mode == ProcessingMode.MULTIPROCESS:
processed_chunks = self._process_multiprocess(chunks, start_batch, total_records)
else:
raise NotImplementedError(f"处理模式未实现: {self.config.processing_mode}")
# 4. 合并结果
final_data = self._merge_chunks(processed_chunks)
final_data = self.post_process(final_data)
# 5. 保存结果
output_path = self._save_result(final_data)
result.output_files.append(output_path)
# 6. 统计
result.records_processed = len(final_data)
result.processing_time_ms = (time.time() - start_time) * 1000
result.success = True
self.logger.info(
f"批处理完成: {result.records_processed}条记录, "
f"耗时: {result.processing_time_ms:.2f}ms"
)
except Exception as e:
result.errors.append(str(e))
self.logger.error(f"批处理失败: {e}")
return result
def _create_chunks(
self,
data_source: Union[pd.DataFrame, Iterator[pd.DataFrame], Path]
) -> Iterator[DataChunk]:
"""创建数据分块"""
if isinstance(data_source, pd.DataFrame):
# DataFrame直接分块
for i, start in enumerate(range(0, len(data_source), self.config.batch_size)):
end = min(start + self.config.batch_size, len(data_source))
chunk = DataChunk(i, data_source.iloc[start:end].copy())
yield chunk
elif isinstance(data_source, Iterator):
# 迭代器逐块处理
chunk_idx = 0
for batch_df in data_source:
chunk = DataChunk(chunk_idx, batch_df)
yield chunk
chunk_idx += 1
elif isinstance(data_source, Path):
# 文件路径,使用pandas分块读取
if data_source.suffix == '.csv':
chunk_idx = 0
for batch_df in pd.read_csv(data_source, chunksize=self.config.batch_size):
chunk = DataChunk(chunk_idx, batch_df)
yield chunk
chunk_idx += 1
elif data_source.suffix in ['.xlsx', '.xls']:
# Excel文件需要特殊处理
df = pd.read_excel(data_source)
yield from self._create_chunks(df)
elif data_source.suffix == '.parquet':
# Parquet文件
import pyarrow.parquet as pq
parquet_file = pq.ParquetFile(data_source)
chunk_idx = 0
for batch in parquet_file.iter_batches(batch_size=self.config.batch_size):
df = batch.to_pandas()
chunk = DataChunk(chunk_idx, df)
yield chunk
chunk_idx += 1
def _process_sequential(
self,
chunks: Iterator[DataChunk],
start_batch: int,
total_records: Optional[int]
) -> List[DataChunk]:
"""串行处理"""
processed = []
chunk_idx = 0
iterator = tqdm(chunks, total=total_records // self.config.batch_size if total_records else None) \
if self.config.enable_progress_bar else chunks
for chunk in iterator:
if chunk_idx < start_batch:
chunk_idx += 1
continue
# 验证
valid, errors = self.validate_chunk(chunk)
if not valid:
self.logger.warning(f"数据块 {chunk.chunk_id} 验证失败: {errors}")
continue
# 处理
try:
processed_chunk = self.process_chunk(chunk)
processed.append(processed_chunk)
except Exception as e:
self.logger.error(f"处理数据块 {chunk.chunk_id} 失败: {e}")
# 检查点
if (chunk_idx + 1) % self.config.checkpoint_interval == 0:
self.save_checkpoint(chunk_idx)
chunk_idx += 1
# 内存管理
if chunk_idx % 10 == 0:
gc.collect()
return processed
def _process_multithread(
self,
chunks: Iterator[DataChunk],
start_batch: int,
total_records: Optional[int]
) -> List[DataChunk]:
"""多线程处理"""
processed = []
chunk_list = list(chunks) # 先转换为列表
with ThreadPoolExecutor(max_workers=self.config.max_workers) as executor:
futures = []
for chunk in chunk_list:
if chunk.chunk_id < start_batch:
continue
future = executor.submit(self._process_chunk_with_retry, chunk)
futures.append(future)
# 收集结果
for future in tqdm(futures, disable=not self.config.enable_progress_bar):
try:
result = future.result()
if result is not None:
processed.append(result)
except Exception as e:
self.logger.error(f"多线程处理失败: {e}")
return processed
def _process_multiprocess(
self,
chunks: Iterator[DataChunk],
start_batch: int,
total_records: Optional[int]
) -> List[DataChunk]:
"""多进程处理"""
processed = []
chunk_list = list(chunks)
with ProcessPoolExecutor(max_workers=self.config.max_workers) as executor:
futures = []
for chunk in chunk_list:
if chunk.chunk_id < start_batch:
continue
future = executor.submit(self._process_chunk_with_retry, chunk)
futures.append(future)
for future in tqdm(futures, disable=not self.config.enable_progress_bar):
try:
result = future.result()
if result is not None:
processed.append(result)
except Exception as e:
self.logger.error(f"多进程处理失败: {e}")
return processed
def _process_chunk_with_retry(self, chunk: DataChunk) -> Optional[DataChunk]:
"""带重试的处理"""
for attempt in range(self.config.retry_count):
try:
return self.process_chunk(chunk)
except Exception as e:
self.logger.warning(f"处理数据块 {chunk.chunk_id}{attempt + 1} 次尝试失败: {e}")
if attempt < self.config.retry_count - 1:
time.sleep(self.config.retry_delay_seconds)
else:
self.logger.error(f"处理数据块 {chunk.chunk_id} 最终失败")
return None
def _merge_chunks(self, chunks: List[DataChunk]) -> pd.DataFrame:
"""合并处理后的数据块"""
if not chunks:
return pd.DataFrame()
dataframes = [chunk.data for chunk in chunks]
return pd.concat(dataframes, ignore_index=True)
def _save_result(self, data: pd.DataFrame) -> Path:
"""保存处理结果"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = self.config.output_dir / f"batch_result_{timestamp}.parquet"
data.to_parquet(output_path, compression='zstd', index=False)
self.logger.info(f"结果已保存: {output_path}")
return output_path
class DataCleaningProcessor(BatchProcessor):
"""数据清洗处理器"""
def __init__(
self,
config: BatchConfig,
missing_action: DataQualityAction = DataQualityAction.FILL_FORWARD,
outlier_method: str = "iqr",
outlier_threshold: float = 3.0
):
super().__init__(config)
self.missing_action = missing_action
self.outlier_method = outlier_method
self.outlier_threshold = outlier_threshold
def process_chunk(self, chunk: DataChunk) -> DataChunk:
"""清洗数据块"""
df = chunk.data.copy()
# 1. 处理缺失值
df = self._handle_missing_values(df)
# 2. 处理异常值
df = self._handle_outliers(df)
# 3. 删除重复数据
df = df.drop_duplicates()
# 4. 数据类型转换
df = self._convert_types(df)
return DataChunk(chunk.chunk_id, df)
def _handle_missing_values(self, df: pd.DataFrame) -> pd.DataFrame:
"""处理缺失值"""
numeric_columns = df.select_dtypes(include=[np.number]).columns
if self.missing_action == DataQualityAction.DROP:
df = df.dropna()
elif self.missing_action == DataQualityAction.FILL_ZERO:
df[numeric_columns] = df[numeric_columns].fillna(0)
elif self.missing_action == DataQualityAction.FILL_MEAN:
df[numeric_columns] = df[numeric_columns].fillna(df[numeric_columns].mean())
elif self.missing_action == DataQualityAction.FILL_MEDIAN:
df[numeric_columns] = df[numeric_columns].fillna(df[numeric_columns].median())
elif self.missing_action == DataQualityAction.FILL_FORWARD:
df = df.fillna(method='ffill')
elif self.missing_action == DataQualityAction.FILL_INTERPOLATE:
df[numeric_columns] = df[numeric_columns].interpolate()
return df
def _handle_outliers(self, df: pd.DataFrame) -> pd.DataFrame:
"""处理异常值"""
numeric_columns = df.select_dtypes(include=[np.number]).columns
for col in numeric_columns:
if self.outlier_method == "iqr":
Q1 = df[col].quantile(0.25)
Q3 = df[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - self.outlier_threshold * IQR
upper = Q3 + self.outlier_threshold * IQR
outlier_mask = (df[col] < lower) | (df[col] > upper)
df.loc[outlier_mask, col] = np.nan
elif self.outlier_method == "zscore":
mean = df[col].mean()
std = df[col].std()
z_scores = np.abs((df[col] - mean) / std)
outlier_mask = z_scores > self.outlier_threshold
df.loc[outlier_mask, col] = np.nan
return df
def _convert_types(self, df: pd.DataFrame) -> pd.DataFrame:
"""转换数据类型"""
# 时间字段转换
if 'observation_time' in df.columns:
df['observation_time'] = pd.to_datetime(df['observation_time'])
# 数值字段优化
for col in df.select_dtypes(include=['float64']).columns:
df[col] = df[col].astype('float32')
for col in df.select_dtypes(include=['int64']).columns:
df[col] = df[col].astype('int32')
# 分类字段优化
for col in df.select_dtypes(include=['object']).columns:
if df[col].nunique() / len(df) < 0.5:
df[col] = df[col].astype('category')
return df
class MonitoringCalculationProcessor(BatchProcessor):
"""监测计算处理器"""
def __init__(
self,
config: BatchConfig,
baseline_date: Optional[datetime] = None,
calculate_rate: bool = True,
rate_window_hours: int = 24
):
super().__init__(config)
self.baseline_date = baseline_date
self.calculate_rate = calculate_rate
self.rate_window_hours = rate_window_hours
def process_chunk(self, chunk: DataChunk) -> DataChunk:
"""计算监测指标"""
df = chunk.data.copy()
# 确保时间字段
if 'observation_time' in df.columns:
df['observation_time'] = pd.to_datetime(df['observation_time'])
df = df.sort_values('observation_time')
# 计算累计位移
if 'value_d' in df.columns and self.baseline_date:
df = self._calculate_cumulative_displacement(df)
# 计算变化速率
if self.calculate_rate and 'value_d' in df.columns:
df = self._calculate_change_rate(df)
# 计算趋势
df = self._calculate_trend(df)
return DataChunk(chunk.chunk_id, df)
def _calculate_cumulative_displacement(self, df: pd.DataFrame) -> pd.DataFrame:
"""计算累计位移"""
if self.baseline_date:
baseline = df[df['observation_time'] <= self.baseline_date]['value_d'].iloc[-1] \
if len(df[df['observation_time'] <= self.baseline_date]) > 0 else df['value_d'].iloc[0]
df['cumulative_displacement'] = df['value_d'] - baseline
return df
def _calculate_change_rate(self, df: pd.DataFrame) -> pd.DataFrame:
"""计算变化速率"""
df = df.sort_values('observation_time')
# 使用rolling window计算速率
window = timedelta(hours=self.rate_window_hours)
rates = []
for idx in range(len(df)):
current_time = df.iloc[idx]['observation_time']
current_value = df.iloc[idx]['value_d']
# 查找窗口内的数据
window_data = df[
(df['observation_time'] >= current_time - window) &
(df['observation_time'] <= current_time)
]
if len(window_data) >= 2:
time_diff = (window_data['observation_time'].iloc[-1] - window_data['observation_time'].iloc[0]).total_seconds() / 86400
value_diff = window_data['value_d'].iloc[-1] - window_data['value_d'].iloc[0]
rate = value_diff / time_diff if time_diff > 0 else 0
rates.append(rate)
else:
rates.append(0)
df['change_rate'] = rates
return df
def _calculate_trend(self, df: pd.DataFrame) -> pd.DataFrame:
"""计算变化趋势"""
if 'change_rate' not in df.columns or len(df) < 3:
df['trend'] = '稳定'
return df
# 使用最近3个点的速率判断趋势
recent_rates = df['change_rate'].tail(3)
avg_rate = recent_rates.mean()
if abs(avg_rate) < 0.1:
trend = '稳定'
elif avg_rate > 0.5:
trend = '快速上升'
elif avg_rate > 0:
trend = '上升'
elif avg_rate < -0.5:
trend = '快速下降'
else:
trend = '下降'
df['trend'] = trend
return df
class AlertDetectionProcessor(BatchProcessor):
"""预警检测处理器"""
def __init__(
self,
config: BatchConfig,
thresholds: Dict[str, Dict[str, float]]
):
super().__init__(config)
self.thresholds = thresholds # {point_type: {yellow: x, orange: y, red: z}}
def process_chunk(self, chunk: DataChunk) -> DataChunk:
"""检测预警"""
df = chunk.data.copy()
# 添加预警等级列
df['alert_level'] = '正常'
for idx, row in df.iterrows():
point_type = row.get('point_type', 'default')
cumulative = row.get('cumulative_displacement', row.get('value_d', 0))
rate = row.get('change_rate', 0)
thresholds = self.thresholds.get(point_type, self.thresholds.get('default', {}))
# 判断预警等级
if abs(cumulative) >= thresholds.get('red', float('inf')) or \
abs(rate) >= thresholds.get('red_rate', float('inf')):
df.at[idx, 'alert_level'] = '红色预警'
elif abs(cumulative) >= thresholds.get('orange', float('inf')) or \
abs(rate) >= thresholds.get('orange_rate', float('inf')):
df.at[idx, 'alert_level'] = '橙色预警'
elif abs(cumulative) >= thresholds.get('yellow', float('inf')) or \
abs(rate) >= thresholds.get('yellow_rate', float('inf')):
df.at[idx, 'alert_level'] = '黄色预警'
return DataChunk(chunk.chunk_id, df)
"""管道组合处理器 - 将多个处理器串联执行"""
from typing import List
class ProcessingPipeline:
"""处理管道"""
def __init__(self, processors: List[BatchProcessor]):
self.processors = processors
self.logger = logging.getLogger("ProcessingPipeline")
def execute(
self,
data_source: Union[pd.DataFrame, Iterator[pd.DataFrame], Path],
total_records: Optional[int] = None
) -> ProcessingResult:
"""
执行处理管道
将多个处理器串联执行,前一个的输出作为后一个的输入
"""
current_data = data_source
final_result = ProcessingResult(success=True)
for i, processor in enumerate(self.processors):
self.logger.info(f"执行处理器 {i+1}/{len(self.processors)}: {processor.__class__.__name__}")
result = processor.process(current_data, total_records)
if not result.success:
final_result.success = False
final_result.errors.extend(result.errors)
self.logger.error(f"处理器 {processor.__class__.__name__} 失败")
break
# 更新统计
final_result.records_processed = result.records_processed
final_result.processing_time_ms += result.processing_time_ms
final_result.output_files.extend(result.output_files)
final_result.warnings.extend(result.warnings)
# 下一个处理器的输入
if result.output_files:
current_data = result.output_files[-1]
self.logger.info(f"处理器 {processor.__class__.__name__} 完成")
return final_result
# 辅助函数:创建标准监测数据处理管道
def create_standard_monitoring_pipeline(
output_dir: Path,
thresholds: Dict[str, Dict[str, float]],
baseline_date: Optional[datetime] = None
) -> ProcessingPipeline:
"""
创建标准监测数据处理管道
处理流程:清洗 -> 计算 -> 预警检测
"""
config = BatchConfig(
output_dir=output_dir,
processing_mode=ProcessingMode.MULTITHREAD,
max_workers=4
)
# 1. 数据清洗
cleaning_processor = DataCleaningProcessor(
config=config,
missing_action=DataQualityAction.FILL_FORWARD,
outlier_method="iqr",
outlier_threshold=3.0
)
# 2. 监测计算
calculation_processor = MonitoringCalculationProcessor(
config=config,
baseline_date=baseline_date,
calculate_rate=True,
rate_window_hours=24
)
# 3. 预警检测
alert_processor = AlertDetectionProcessor(
config=config,
thresholds=thresholds
)
return ProcessingPipeline([
cleaning_processor,
calculation_processor,
alert_processor
])

参数名 类型 必填 默认值 说明
batch_size int 10000 每批处理记录数
max_workers int 4 最大工作进程/线程数
processing_mode ProcessingMode MULTITHREAD 处理模式
memory_limit_mb int 1024 单批内存限制(MB)
checkpoint_interval int 100 检查点间隔(批次数)
output_dir Path ./batch_output 输出目录
temp_dir Path ./batch_temp 临时目录
enable_progress_bar bool True 是否显示进度条
enable_checkpoint bool True 是否启用检查点
retry_count int 3 失败重试次数
retry_delay_seconds int 5 重试延迟(秒)

3.2 DataQualityAction 缺失值处理动作

Section titled “3.2 DataQualityAction 缺失值处理动作”
动作 说明 适用场景
DROP 删除缺失值行 缺失较少
FILL_ZERO 填充0 数值型数据
FILL_MEAN 填充均值 正态分布数据
FILL_MEDIAN 填充中位数 有异常值的数据
FILL_FORWARD 前向填充 时间序列数据
FILL_INTERPOLATE 插值填充 连续数据
FLAG 标记但不处理 需要人工审核

import pandas as pd
from datetime import datetime, timezone
from template_batch_processing import (
BatchConfig, ProcessingMode, DataQualityAction,
DataCleaningProcessor, MonitoringCalculationProcessor,
AlertDetectionProcessor, ProcessingPipeline
)
from pathlib import Path
# 1. 准备模拟数据(实际项目中从数据库读取)
np.random.seed(42)
n_records = 100000
sample_data = pd.DataFrame({
'record_id': [f'REC_{i:06d}' for i in range(n_records)],
'project_id': ['RW-2024-001'] * n_records,
'point_id': np.random.choice(['JC-01', 'JC-02', 'JC-03', 'JC-04', 'JC-05'], n_records),
'point_type': ['水平位移'] * n_records,
'observation_time': pd.date_range(
start='2024-01-01',
periods=n_records,
freq='H',
tz='UTC'
),
'value_d': np.random.normal(0, 2, n_records).cumsum() + np.random.normal(0, 0.5, n_records),
})
# 2. 配置批处理
config = BatchConfig(
batch_size=10000,
max_workers=4,
processing_mode=ProcessingMode.MULTITHREAD,
output_dir=Path("./batch_output"),
enable_progress_bar=True
)
# 3. 创建处理器
# 3.1 数据清洗
cleaning_processor = DataCleaningProcessor(
config=config,
missing_action=DataQualityAction.FILL_FORWARD,
outlier_method="iqr",
outlier_threshold=3.0
)
# 3.2 监测计算
calculation_processor = MonitoringCalculationProcessor(
config=config,
baseline_date=datetime(2024, 1, 1, tzinfo=timezone.utc),
calculate_rate=True,
rate_window_hours=24
)
# 3.3 预警检测
thresholds = {
'水平位移': {
'yellow': 5.0,
'orange': 8.0,
'red': 10.0,
'yellow_rate': 1.0,
'orange_rate': 2.0,
'red_rate': 3.0
}
}
alert_processor = AlertDetectionProcessor(
config=config,
thresholds=thresholds
)
# 4. 创建管道并执行
pipeline = ProcessingPipeline([
cleaning_processor,
calculation_processor,
alert_processor
])
result = pipeline.execute(sample_data, total_records=n_records)
# 5. 查看结果
print(f"处理成功: {result.success}")
print(f"处理记录数: {result.records_processed}")
print(f"处理耗时: {result.processing_time_ms:.2f}ms")
print(f"输出文件: {result.output_files}")
if result.errors:
print(f"错误: {result.errors}")
if result.warnings:
print(f"警告: {result.warnings}")
from pathlib import Path
from template_batch_processing import BatchConfig, DataCleaningProcessor, DataQualityAction
# 1. 创建大型CSV文件
large_file = Path("./large_data.csv")
if not large_file.exists():
# 生成测试数据
df = pd.DataFrame({
'point_id': ['JC-01'] * 1000000,
'observation_time': pd.date_range('2024-01-01', periods=1000000, freq='S'),
'value_d': np.random.normal(0, 1, 1000000).cumsum()
})
df.to_csv(large_file, index=False)
# 2. 配置批处理(使用分块读取)
config = BatchConfig(
batch_size=50000, # 每批5万条
max_workers=4,
output_dir=Path("./batch_output"),
enable_progress_bar=True
)
# 3. 创建处理器
processor = DataCleaningProcessor(
config=config,
missing_action=DataQualityAction.FILL_FORWARD
)
# 4. 从文件处理(自动分块读取)
result = processor.process(large_file, total_records=1000000)
print(f"处理完成: {result.records_processed}条记录")
print(f"输出文件: {result.output_files[0] if result.output_files else ''}")
from template_batch_processing import BatchConfig, DataCleaningProcessor
# 配置启用检查点
config = BatchConfig(
batch_size=10000,
enable_checkpoint=True,
checkpoint_interval=50, # 每50批保存检查点
temp_dir=Path("./batch_temp")
)
processor = DataCleaningProcessor(config=config)
# 模拟处理中断后恢复
# 第一次运行:处理到第150批时中断
# 第二次运行:自动从检查点恢复
result = processor.process(large_file, total_records=1000000)
# 检查点文件会自动加载,从上次中断处继续
print(f"从检查点恢复后处理完成")
from template_batch_processing import BatchProcessor, DataChunk, BatchConfig
class CustomStatisticsProcessor(BatchProcessor):
"""自定义统计处理器"""
def __init__(self, config: BatchConfig, group_by: str = 'point_id'):
super().__init__(config)
self.group_by = group_by
self.statistics = []
def process_chunk(self, chunk: DataChunk) -> DataChunk:
"""计算分组统计"""
df = chunk.data
# 按测点分组统计
stats = df.groupby(self.group_by).agg({
'value_d': ['mean', 'std', 'min', 'max', 'count'],
'change_rate': ['mean', 'max']
}).reset_index()
# 展平列名
stats.columns = ['_'.join(col).strip('_') for col in stats.columns.values]
self.statistics.append(stats)
return chunk # 返回原始数据,不修改
def get_statistics(self) -> pd.DataFrame:
"""获取合并后的统计结果"""
if not self.statistics:
return pd.DataFrame()
return pd.concat(self.statistics, ignore_index=True)
# 使用自定义处理器
config = BatchConfig(output_dir=Path("./stats_output"))
stats_processor = CustomStatisticsProcessor(config, group_by='point_id')
result = stats_processor.process(sample_data)
statistics = stats_processor.get_statistics()
print("分组统计结果:")
print(statistics.head())

处理成功: True
处理记录数: 100000
处理耗时: 3456.78ms
输出文件: [PosixPath('batch_output/batch_result_20240708_143052.parquet')]
警告: []
# 读取处理结果
df = pd.read_parquet("batch_output/batch_result_20240708_143052.parquet")
print(df.head())

输出:

record_id project_id point_id ... value_d cumulative_displacement change_rate alert_level
0 REC_000000 RW-2024-001 JC-01 ... 0.12 0.12 0.00 正常
1 REC_000001 RW-2024-001 JC-01 ... 0.15 0.27 0.15 正常
2 REC_000002 RW-2024-001 JC-01 ... 0.08 0.35 0.08 正常
3 REC_000003 RW-2024-001 JC-01 ... 1.85 2.20 1.85 黄色预警
4 REC_000004 RW-2024-001 JC-01 ... 0.03 2.23 0.03 正常
{
"last_batch": 150,
"processed_records": 1500000,
"timestamp": "2024-07-08T14:30:52.123456",
"processor_state": {
"cleaning_stats": {
"missing_values_filled": 234,
"outliers_detected": 12
}
}
}

from template_batch_processing import BatchProcessor, DataChunk, BatchConfig
class DataValidationProcessor(BatchProcessor):
"""自定义数据验证处理器"""
def __init__(self, config: BatchConfig, rules: List[Dict]):
super().__init__(config)
self.rules = rules
self.validation_errors = []
def process_chunk(self, chunk: DataChunk) -> DataChunk:
"""验证数据块"""
df = chunk.data
for rule in self.rules:
column = rule['column']
condition = rule['condition']
message = rule['message']
# 应用验证规则
invalid_mask = ~df.eval(condition)
invalid_count = invalid_mask.sum()
if invalid_count > 0:
self.validation_errors.append({
'chunk_id': chunk.chunk_id,
'rule': message,
'invalid_count': int(invalid_count)
})
# 标记无效数据
df.loc[invalid_mask, 'validation_status'] = 'INVALID'
return DataChunk(chunk.chunk_id, df)
def get_validation_report(self) -> pd.DataFrame:
"""获取验证报告"""
return pd.DataFrame(self.validation_errors)
# 使用
rules = [
{'column': 'value_d', 'condition': 'value_d >= -100 and value_d <= 100', 'message': '位移值超出合理范围'},
{'column': 'observation_time', 'condition': 'observation_time <= @pd.Timestamp.now()', 'message': '观测时间在未来'}
]
validator = DataValidationProcessor(config, rules)
result = validator.process(data)
report = validator.get_validation_report()
print(report)
# 使用Dask进行分布式处理
import dask.dataframe as dd
class DaskBatchProcessor(BatchProcessor):
"""基于Dask的分布式处理器"""
def process(self, data_source, total_records=None):
"""使用Dask执行分布式处理"""
if isinstance(data_source, pd.DataFrame):
ddf = dd.from_pandas(data_source, npartitions=self.config.max_workers)
elif isinstance(data_source, Path):
ddf = dd.read_parquet(data_source)
else:
raise ValueError("不支持的数据源类型")
# 应用处理函数
result = ddf.map_partitions(self._process_partition)
# 计算并保存
output_path = self.config.output_dir / "dask_result.parquet"
result.to_parquet(output_path, compression='zstd')
return ProcessingResult(
success=True,
records_processed=len(result),
output_files=[output_path]
)
def _process_partition(self, df):
"""处理单个分区"""
chunk = DataChunk(0, df)
processed = self.process_chunk(chunk)
return processed.data
from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime
def daily_batch_process():
"""每日批处理任务"""
logger.info("开始每日批处理")
# 1. 从数据库读取昨日数据
# data = query_yesterday_data()
# 2. 创建管道
pipeline = create_standard_monitoring_pipeline(
output_dir=Path(f"./batch_output/{datetime.now().strftime('%Y%m%d')}"),
thresholds=thresholds,
baseline_date=datetime.now() - timedelta(days=30)
)
# 3. 执行处理
# result = pipeline.execute(data)
logger.info("每日批处理完成")
# 每日凌晨2点执行
scheduler = BackgroundScheduler()
scheduler.add_job(daily_batch_process, 'cron', hour=2, minute=0)
scheduler.start()
class MemoryOptimizedProcessor(BatchProcessor):
"""内存优化处理器"""
def process_chunk(self, chunk: DataChunk) -> DataChunk:
"""内存优化的处理"""
df = chunk.data
# 1. 选择需要的列
needed_columns = ['point_id', 'observation_time', 'value_d', 'change_rate']
df = df[needed_columns]
# 2. 类型优化
df['point_id'] = df['point_id'].astype('category')
df['value_d'] = df['value_d'].astype('float32')
# 3. 处理完成后立即释放内存
import gc
gc.collect()
return DataChunk(chunk.chunk_id, df)
def _merge_chunks(self, chunks: List[DataChunk]) -> pd.DataFrame:
"""增量合并,避免一次性加载所有数据"""
output_path = self.config.output_dir / "merged_result.parquet"
for i, chunk in enumerate(chunks):
if i == 0:
chunk.data.to_parquet(output_path, index=False)
else:
# 追加写入
existing = pd.read_parquet(output_path)
merged = pd.concat([existing, chunk.data], ignore_index=True)
merged.to_parquet(output_path, index=False, compression='zstd')
# 释放内存
del existing, merged
gc.collect()
return pd.read_parquet(output_path)

Q1: 处理大数据量时内存溢出怎么办?

Section titled “Q1: 处理大数据量时内存溢出怎么办?”

A: 采用以下策略:

  1. 减小batch_size
  2. 使用MULTIPROCESS模式
  3. 启用分块读取(从文件)
  4. 优化数据类型(float64→float32)
  5. 只选择需要的列
config = BatchConfig(
batch_size=5000, # 减小批次
processing_mode=ProcessingMode.MULTIPROCESS,
memory_limit_mb=512 # 限制内存
)

Q2: 如何处理处理中断后的恢复?

Section titled “Q2: 如何处理处理中断后的恢复?”

A: 启用检查点功能:

config = BatchConfig(
enable_checkpoint=True,
checkpoint_interval=50, # 每50批保存检查点
temp_dir=Path("./checkpoints")
)

Q3: 多进程模式下数据共享问题?

Section titled “Q3: 多进程模式下数据共享问题?”

A: 多进程模式下每个进程有独立内存空间,避免共享状态:

# 错误:多进程共享状态
class BadProcessor(BatchProcessor):
shared_list = [] # 不要在类级别共享状态
# 正确:每个进程独立处理
class GoodProcessor(BatchProcessor):
def __init__(self, config):
super().__init__(config)
self.local_results = [] # 实例级别状态

A: 使用tqdm进度条和日志:

config = BatchConfig(
enable_progress_bar=True # 显示进度条
)
# 自定义进度回调
from tqdm import tqdm
with tqdm(total=total_records, desc="处理中") as pbar:
for chunk in chunks:
process_chunk(chunk)
pbar.update(len(chunk.data))

A:

  1. 使用Parquet格式(比CSV快10-100倍)
  2. 使用向量化操作(避免循环)
  3. 使用Numba加速计算
  4. 合理设置并行度(通常CPU核心数-1)
import numba
@numba.jit(nopython=True)
def fast_calculation(values):
"""Numba加速计算"""
result = np.empty_like(values)
for i in range(len(values)):
result[i] = values[i] * 2.0
return result


ai_tags:
domain: ["工程监测", "数据处理", "大数据"]
technology: ["Python", "Pandas", "NumPy", "多线程", "多进程"]
standard: ["GB 50497", "CJJ/T 202", "JGJ 8"]
product: ["RAILWISE-TSM", "RAILWISE-CLI", "WorkWise"]
difficulty: "advanced"
role: ["数据工程师", "监测工程师", "系统架构师"]
task_type: ["批处理", "数据清洗", "性能优化"]
引用与复核把知识带回真实工程判断

引用时保留页面与来源线索;涉及标准条文、阈值、频率和项目结论,请回到现行依据与责任人复核。

查看 Agent 使用规则