python Chapter 2:雨滴/雪花数据清洗

一、雨滴数据清洗

为了确保深度学习训练的有效性,数据清洗至关重要。因此,在将这些数据加入数据集之前,放弃了那些不合格的降水滴谱(DSD)数据,处理顺序(行为直径,列为速度):

(1)实测粒子下落速度与经典值之间的差异大于5米每秒的数据

(2)进行粒子轴比订正,剔除前两个尺度档和订正后雨滴直径大于8 mm 的 观测数据;(删除前2个尺度档和后10个尺度档)

(3)某个直径区间内的粒子数少于2个

(4)每分钟内的粒子数量少于10个的数据;(这个得放最后一步,要不然会存在粒子总数少于10,u巨大的情况)

(因为我做过轴比订正的excel,发现倒数第11行原始直径6.5处对应轴比订正7.320611223 ,倒数第10行7.5处对应轴比订正8.446859103,所以直接剔除剔除前2个尺度档和后10档。)

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: Suyue
@file: process_raindrop_data_v9_fixed_modified.py
@time: 2026/08/31
@desc: 雨滴数据清洗
       1. 速度差>5m/s(用现成的掩膜) -> 剔除对应粒子
       2. 剔除前2个直径档和后10个直径档(订正后直径)
       3. 行粒子数<2 -> 该行全部置0(按直径区间统计)
       4. 总粒子数<10 -> 剔除整分钟(最后一步)
"""
import numpy as np
import re
import os
from datetime import datetime
import pandas as pd

# ==================== 常量定义 ====================
# 32个直径通道的中心直径 (mm) - 这是已经轴比订正后的直径
diameters_corrected = np.array([
    0.062, 0.187, 0.312, 0.437, 0.562, 0.687, 0.812, 0.937,
    1.064, 1.193, 1.388, 1.651, 1.918, 2.188, 2.463, 2.882,
    3.457, 4.050, 4.665, 5.303, 6.194, 7.321, 8.447, 9.573, 10.699,
    12.389, 14.641, 16.894, 19.146, 21.399, 24.214, 27.593
])

# 32个速度通道的中心速度 (m/s)
measured_velocity = np.array([
    0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95,
    1.1, 1.3, 1.5, 1.7, 1.9, 2.2, 2.6, 3, 3.4, 3.8,
    4.4, 5.2, 6, 6.8, 7.6, 8.8, 10.4, 12, 13.6, 15.2, 17.6, 20.8
])

# 需要剔除的直径档位索引(前2个和后10个)
# 索引: 0,1 是前两个档位;22-31 是后10个档位(因为总32个,后10个是索引22到31)
# 注意:现在是行索引,因为行代表直径
INVALID_DIAMETER_BINS = list(range(0, 2)) + list(range(22, 32))


def load_velocity_mask(mask_file):
    """
    加载速度掩膜Excel文件
    注意:掩膜矩阵中,行代表直径,列代表速度
    """
    print(f"正在加载速度掩膜: {mask_file}")

    try:
        df = pd.read_excel(mask_file, header=None)
        mask = df.values

        if mask.shape != (32, 32):
            raise ValueError(f"掩膜形状错误: 期望(32,32),实际{mask.shape}")

        unique_values = np.unique(mask)
        if not np.all(np.isin(unique_values, [0, 1])):
            print(f"警告: 掩膜中包含非0/1的值: {unique_values}")

        print(f"速度掩膜加载成功,形状: {mask.shape}")
        print(f"剔除位置数量: {np.sum(mask)} / 1024 ({np.sum(mask) / 1024 * 100:.1f}%)")
        print(f"掩膜矩阵说明: 行=直径通道({len(diameters_corrected)}个), 列=速度通道({len(measured_velocity)}个)")

        return mask

    except Exception as e:
        print(f"加载速度掩膜失败: {e}")
        raise


def process_one_minute(data_32x32, velocity_mask, minute_number=None):
    """
    严格按照四步规则处理一分钟的32×32雨滴数据

    数组结构:32行(直径)× 32列(速度)

    规则顺序(调整后):
       1. 速度差>5m/s(用现成的掩膜) -> 剔除对应粒子
       2. 剔除前2个直径档和后10个直径档(订正后直径)
       3. 行粒子数<2 -> 该行全部置0(按直径区间统计)
       4. 总粒子数<10 -> 剔除整分钟(最后一步)
    """
    # 创建处理副本
    processed = data_32x32.copy().astype(float)

    # ===== 规则1: 速度差异大于5m/s的数据(用现成的掩膜)=====
    # velocity_mask中标记为1的位置,将雨滴谱数值置为0
    # 掩膜矩阵: 行=直径, 列=速度
    processed = np.where(velocity_mask == 1, 0, processed)

    # ===== 规则2: 剔除前2个直径档和后10个直径档(订正后直径)=====
    # 现在行代表直径,所以需要剔除行
    for row in INVALID_DIAMETER_BINS:
        processed[row, :] = 0

    # ===== 规则3: 行粒子数<2 -> 该行全部置0(按直径区间统计)=====
    # 注意:行代表直径区间,按行求和
    for row in range(32):
        row_sum = np.sum(processed[row, :])
        if row_sum < 2:
            processed[row, :] = 0

    # ===== 规则4: 每分钟内的粒子数量少于10个(最后一步)=====
    total_particles = np.sum(processed)
    if total_particles < 10:
        return None, 'particle_count_lt10'

    return processed, 'kept'


def process_data_file(input_file, output_file, velocity_mask, log_file=None):
    """
    处理整个数据文件
    """
    stats = {
        'total_minutes': 0,
        'kept_minutes': 0,
        'discarded_minutes': 0,
        'discard_reasons': {
            'total_lt10': 0,
            'invalid_data': 0
        }
    }

    timestamp_pattern = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$')

    current_array = []
    current_timestamp = None
    processed_data = []
    minute_count = 0

    if log_file:
        log_dir = os.path.dirname(log_file)
        if log_dir and not os.path.exists(log_dir):
            os.makedirs(log_dir)
        with open(log_file, 'w') as log:
            log.write(f"雨滴数据清洗日志\n")
            log.write(f"开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            log.write(f"输入文件: {input_file}\n")
            log.write(f"输出文件: {output_file}\n")
            log.write(f"速度掩膜形状: {velocity_mask.shape}\n")
            log.write(f"速度掩膜剔除比例: {np.sum(velocity_mask) / 1024 * 100:.1f}%\n\n")
            log.write("清洗规则顺序:\n")
            log.write("  1. 速度差>5m/s -> 剔除对应粒子\n")
            log.write("  2. 剔除前2个直径档和后10个直径档\n")
            log.write("  3. 行粒子数<2 -> 该行全部置0\n")
            log.write("  4. 总粒子数<10 -> 剔除整分钟\n\n")

    print(f"\n开始处理文件: {input_file}")
    print(f"数据矩阵说明: 行=直径通道({len(diameters_corrected)}个), 列=速度通道({len(measured_velocity)}个)")

    with open(input_file, 'r') as f:
        for line in f:
            line = line.strip()

            if timestamp_pattern.match(line):
                if current_array and current_timestamp:
                    stats['total_minutes'] += 1
                    minute_count += 1

                    if len(current_array) != 32:
                        if log_file:
                            with open(log_file, 'a') as log:
                                log.write(f"{current_timestamp}: 跳过 - 数据不是32行\n")
                        current_timestamp = line
                        current_array = []
                        continue

                    try:
                        array_32x32 = np.array(current_array, dtype=float)
                    except Exception as e:
                        if log_file:
                            with open(log_file, 'a') as log:
                                log.write(f"{current_timestamp}: 跳过 - 数据格式错误: {e}\n")
                        current_timestamp = line
                        current_array = []
                        continue

                    processed, reason = process_one_minute(array_32x32, velocity_mask, minute_count)

                    if processed is None:
                        stats['discarded_minutes'] += 1
                        if reason == 'particle_count_lt10':
                            stats['discard_reasons']['total_lt10'] += 1
                        if log_file:
                            with open(log_file, 'a') as log:
                                original_total = np.sum(array_32x32)
                                log.write(f"{current_timestamp}: 删除 - 清洗后粒子数<10 (原始:{original_total:.0f}, 清洗后:<10)\n")
                    else:
                        stats['kept_minutes'] += 1
                        processed_data.append((current_timestamp, processed))

                        if stats['kept_minutes'] == 1:
                            original_total = np.sum(array_32x32)
                            final_total = np.sum(processed)
                            print(f"\n第一个保留分钟:")
                            print(f"  时间戳: {current_timestamp}")
                            print(f"  原始粒子数: {original_total:.0f}")
                            print(f"  清洗后粒子数: {final_total:.0f}")
                            print(f"  数据矩阵形状: {processed.shape} (行=直径, 列=速度)")

                current_timestamp = line
                current_array = []

            else:
                if line:
                    try:
                        row_data = list(map(float, line.split()))
                        if len(row_data) == 32:
                            current_array.append(row_data)
                    except:
                        pass

    # 处理最后一个分钟
    if current_array and current_timestamp:
        stats['total_minutes'] += 1
        minute_count += 1

        if len(current_array) == 32:
            array_32x32 = np.array(current_array, dtype=float)
            processed, reason = process_one_minute(array_32x32, velocity_mask, minute_count)

            if processed is None:
                stats['discarded_minutes'] += 1
                if reason == 'particle_count_lt10':
                    stats['discard_reasons']['total_lt10'] += 1
            else:
                stats['kept_minutes'] += 1
                processed_data.append((current_timestamp, processed))

    # 保存结果
    print(f"\n保存处理结果到: {output_file}")
    with open(output_file, 'w') as out_f:
        for timestamp, data in processed_data:
            out_f.write(f"{timestamp}\n")
            for i in range(32):
                row_int = [int(round(x)) for x in data[i, :]]
                out_f.write(" ".join(map(str, row_int)) + "\n")
            out_f.write("\n")

    # 打印统计
    print("\n" + "=" * 60)
    print("数据预处理统计报告")
    print("=" * 60)
    print(f"总处理分钟数: {stats['total_minutes']}")
    print(f"保留分钟数: {stats['kept_minutes']} ({stats['kept_minutes'] / max(stats['total_minutes'], 1) * 100:.1f}%)")
    print(
        f"删除分钟数: {stats['discarded_minutes']} ({stats['discarded_minutes'] / max(stats['total_minutes'], 1) * 100:.1f}%)")
    print(f"删除原因:")
    print(f"  - 清洗后粒子数<10: {stats['discard_reasons']['total_lt10']}")

    if log_file:
        with open(log_file, 'a') as log:
            log.write(f"\n处理完成!\n")
            log.write(f"总处理分钟数: {stats['total_minutes']}\n")
            log.write(f"保留分钟数: {stats['kept_minutes']}\n")
            log.write(f"删除分钟数: {stats['discarded_minutes']}\n")

    return processed_data, stats


if __name__ == "__main__":
    # ==================== 文件路径配置 ====================
    input_file = 'D:/lianxi/50934-20210507000700-20210507050859-0.txt'
    output_file = 'D:/lianxi/50934-20210507000700-20210507050859-0-clean.txt'
    log_file = 'D:/lianxi/data_processing_log.txt'

    velocity_mask_file = 'D:/lianxi/02-速度差掩膜.xlsx'

    print("=" * 60)
    print("雨滴数据预处理程序")
    print("数据矩阵结构: 行=直径通道, 列=速度通道")
    print("清洗规则顺序(调整后):")
    print("  1. 速度差 > 5 m/s(用掩膜剔除)")
    print("  2. 剔除前2个直径档和后10个直径档(行)")
    print("  3. 行粒子数 < 2 -> 该行全部置0")
    print("  4. 总粒子数 < 10 -> 剔除整分钟(最后一步)")
    print("=" * 60)
    print(f"输入文件: {input_file}")
    print(f"输出文件: {output_file}")
    print(f"日志文件: {log_file}")
    print(f"速度掩膜文件: {velocity_mask_file}")
    print("=" * 60)

    try:
        velocity_mask = load_velocity_mask(velocity_mask_file)
        processed_data, stats = process_data_file(input_file, output_file, velocity_mask, log_file)

        print(f"\n处理完成!结果已保存到: {output_file}")
        print(f"保留分钟数: {stats['kept_minutes']}")

        if processed_data:
            print(f"\n前5个保留的时间戳:")
            for i in range(min(5, len(processed_data))):
                timestamp, data = processed_data[i]
                total_particles = np.sum(data)
                print(f"  {timestamp}: {total_particles:.0f} 个粒子")

    except FileNotFoundError as e:
        print(f"文件未找到: {e}")
        print("请检查文件路径是否正确")
    except Exception as e:
        print(f"处理过程中出现错误: {e}")
        import traceback
        traceback.print_exc()

 批量处理

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: Suyue
@file: batch_process_raindrop_data.py
@time: 2026/09/01
@desc: 雨滴数据批量清洗
       规则顺序:
       1. 速度差>5m/s(用现成的掩膜) -> 剔除对应粒子
       2. 剔除前2个直径档和后10个直径档(订正后直径)
       3. 行粒子数<2 -> 该行全部置0(按直径区间统计)
       4. 总粒子数<10 -> 剔除整分钟(最后一步)
"""
import numpy as np
import re
import os
from datetime import datetime
import pandas as pd
import glob

# ==================== 常量定义 ====================
# 32个直径通道的中心直径 (mm) - 这是已经轴比订正后的直径
diameters_corrected = np.array([
    0.062, 0.187, 0.312, 0.437, 0.562, 0.687, 0.812, 0.937,
    1.064, 1.193, 1.388, 1.651, 1.918, 2.188, 2.463, 2.882,
    3.457, 4.050, 4.665, 5.303, 6.194, 7.321, 8.447, 9.573, 10.699,
    12.389, 14.641, 16.894, 19.146, 21.399, 24.214, 27.593
])

# 32个速度通道的中心速度 (m/s)
measured_velocity = np.array([
    0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95,
    1.1, 1.3, 1.5, 1.7, 1.9, 2.2, 2.6, 3, 3.4, 3.8,
    4.4, 5.2, 6, 6.8, 7.6, 8.8, 10.4, 12, 13.6, 15.2, 17.6, 20.8
])

# 需要剔除的直径档位索引(前2个和后10个)
# 索引: 0,1 是前两个档位;22-31 是后10个档位(因为总32个,后10个是索引22到31)
# 注意:行代表直径
INVALID_DIAMETER_BINS = list(range(0, 2)) + list(range(22, 32))


def load_velocity_mask(mask_file):
    """
    加载速度掩膜Excel文件
    注意:掩膜矩阵中,行代表直径,列代表速度
    """
    print(f"正在加载速度掩膜: {mask_file}")

    try:
        df = pd.read_excel(mask_file, header=None)
        mask = df.values

        if mask.shape != (32, 32):
            raise ValueError(f"掩膜形状错误: 期望(32,32),实际{mask.shape}")

        unique_values = np.unique(mask)
        if not np.all(np.isin(unique_values, [0, 1])):
            print(f"警告: 掩膜中包含非0/1的值: {unique_values}")

        print(f"速度掩膜加载成功,形状: {mask.shape}")
        print(f"剔除位置数量: {np.sum(mask)} / 1024 ({np.sum(mask) / 1024 * 100:.1f}%)")
        print(f"掩膜矩阵说明: 行=直径通道({len(diameters_corrected)}个), 列=速度通道({len(measured_velocity)}个)")

        return mask

    except Exception as e:
        print(f"加载速度掩膜失败: {e}")
        raise


def process_one_minute(data_32x32, velocity_mask, minute_number=None):
    """
    严格按照四步规则处理一分钟的32×32雨滴数据

    数组结构:32行(直径)× 32列(速度)

    规则顺序:
       1. 速度差>5m/s(用现成的掩膜) -> 剔除对应粒子
       2. 剔除前2个直径档和后10个直径档(订正后直径)
       3. 行粒子数<2 -> 该行全部置0(按直径区间统计)
       4. 总粒子数<10 -> 剔除整分钟(最后一步)
    """
    # 创建处理副本
    processed = data_32x32.copy().astype(float)

    # ===== 规则1: 速度差异大于5m/s的数据(用现成的掩膜)=====
    # velocity_mask中标记为1的位置,将雨滴谱数值置为0
    # 掩膜矩阵: 行=直径, 列=速度
    processed = np.where(velocity_mask == 1, 0, processed)

    # ===== 规则2: 剔除前2个直径档和后10个直径档(订正后直径)=====
    # 现在行代表直径,所以需要剔除行
    for row in INVALID_DIAMETER_BINS:
        processed[row, :] = 0

    # ===== 规则3: 行粒子数<2 -> 该行全部置0(按直径区间统计)=====
    # 注意:行代表直径区间,按行求和
    for row in range(32):
        row_sum = np.sum(processed[row, :])
        if row_sum < 2:
            processed[row, :] = 0

    # ===== 规则4: 每分钟内的粒子数量少于10个(最后一步)=====
    total_particles = np.sum(processed)
    if total_particles < 10:
        return None, 'particle_count_lt10'

    return processed, 'kept'


def process_data_file_with_stats(input_file, output_file, velocity_mask):
    """
    处理整个数据文件,并返回统计信息(不生成单独的log文件)
    """
    stats = {
        'total_minutes': 0,
        'kept_minutes': 0,
        'discarded_minutes': 0,
        'discard_reasons': {
            'total_lt10': 0,
            'invalid_data': 0
        },
        'mask_removed_particles': 0,  # 掩膜剔除的粒子数
        'total_original_particles': 0,  # 原始总粒子数
        'mask_remove_rate': 0  # 掩膜剔除比例
    }

    timestamp_pattern = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$')

    current_array = []
    current_timestamp = None
    processed_data = []
    minute_count = 0

    with open(input_file, 'r') as f:
        for line in f:
            line = line.strip()

            if timestamp_pattern.match(line):
                if current_array and current_timestamp:
                    stats['total_minutes'] += 1
                    minute_count += 1

                    if len(current_array) != 32:
                        current_timestamp = line
                        current_array = []
                        continue

                    try:
                        array_32x32 = np.array(current_array, dtype=float)
                    except Exception as e:
                        current_timestamp = line
                        current_array = []
                        continue

                    # 统计原始粒子数
                    original_total = np.sum(array_32x32)
                    stats['total_original_particles'] += original_total

                    # 统计掩膜剔除的粒子数
                    mask_removed = np.sum(array_32x32[velocity_mask == 1])
                    stats['mask_removed_particles'] += mask_removed

                    processed, reason = process_one_minute(array_32x32, velocity_mask, minute_count)

                    if processed is None:
                        stats['discarded_minutes'] += 1
                        if reason == 'particle_count_lt10':
                            stats['discard_reasons']['total_lt10'] += 1
                    else:
                        stats['kept_minutes'] += 1
                        processed_data.append((current_timestamp, processed))

                current_timestamp = line
                current_array = []

            else:
                if line:
                    try:
                        row_data = list(map(float, line.split()))
                        if len(row_data) == 32:
                            current_array.append(row_data)
                    except:
                        pass

    # 处理最后一个分钟
    if current_array and current_timestamp:
        stats['total_minutes'] += 1
        minute_count += 1

        if len(current_array) == 32:
            array_32x32 = np.array(current_array, dtype=float)

            # 统计原始粒子数和掩膜剔除
            original_total = np.sum(array_32x32)
            stats['total_original_particles'] += original_total
            mask_removed = np.sum(array_32x32[velocity_mask == 1])
            stats['mask_removed_particles'] += mask_removed

            processed, reason = process_one_minute(array_32x32, velocity_mask, minute_count)

            if processed is None:
                stats['discarded_minutes'] += 1
                if reason == 'particle_count_lt10':
                    stats['discard_reasons']['total_lt10'] += 1
            else:
                stats['kept_minutes'] += 1
                processed_data.append((current_timestamp, processed))

    # 计算掩膜剔除比例
    if stats['total_original_particles'] > 0:
        stats['mask_remove_rate'] = stats['mask_removed_particles'] / stats['total_original_particles'] * 100

    # 只有有数据时才保存文件
    if processed_data:
        with open(output_file, 'w') as out_f:
            for timestamp, data in processed_data:
                out_f.write(f"{timestamp}\n")
                for i in range(32):
                    row_int = [int(round(x)) for x in data[i, :]]
                    out_f.write(" ".join(map(str, row_int)) + "\n")
                out_f.write("\n")
    else:
        # 如果没有数据,删除可能存在的空文件
        if os.path.exists(output_file):
            os.remove(output_file)

    return processed_data, stats


def get_file_time_range(input_file):
    """
    从文件中提取时间范围
    """
    timestamp_pattern = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$')
    timestamps = []

    try:
        with open(input_file, 'r') as f:
            for line in f:
                line = line.strip()
                if timestamp_pattern.match(line):
                    timestamps.append(line)
                    if len(timestamps) >= 2:
                        break
        # 找最后一个时间戳
        with open(input_file, 'r') as f:
            lines = f.readlines()
            for line in reversed(lines):
                line = line.strip()
                if timestamp_pattern.match(line):
                    if len(timestamps) < 2 or line != timestamps[-1]:
                        timestamps.append(line)
                    break
    except:
        pass

    if len(timestamps) >= 2:
        start = timestamps[0]
        end = timestamps[-1]
        return f"{start} ~ {end}"
    elif len(timestamps) == 1:
        return timestamps[0]
    else:
        return "未知"


def batch_process_files(input_dir, output_dir, velocity_mask_file, log_file=None):
    """
    批量处理目录下所有txt文件

    参数:
        input_dir: 输入文件目录
        output_dir: 输出文件目录
        velocity_mask_file: 速度掩膜文件路径
        log_file: 汇总日志文件路径(可选)
    """
    # 加载掩膜
    velocity_mask = load_velocity_mask(velocity_mask_file)
    mask_remove_rate_global = np.sum(velocity_mask) / 1024 * 100

    # 创建输出目录
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # 查找所有txt文件
    txt_files = glob.glob(os.path.join(input_dir, "*.txt"))

    if not txt_files:
        print(f"在 {input_dir} 中没有找到txt文件")
        return

    print(f"\n找到 {len(txt_files)} 个txt文件")
    print("=" * 60)

    # 汇总数据
    summary = []
    total_kept = 0
    total_all = 0
    total_files = 0
    total_original_particles = 0
    total_mask_removed = 0

    for idx, input_file in enumerate(txt_files, 1):
        # 生成输出文件名
        base_name = os.path.basename(input_file)
        name_without_ext = os.path.splitext(base_name)[0]
        output_file = os.path.join(output_dir, f"{name_without_ext}-clean.txt")

        try:
            print(f"\n[{idx}/{len(txt_files)}] 处理: {base_name}")

            # 获取文件时间范围
            time_range = get_file_time_range(input_file)

            # 处理文件(不生成单独的log)
            processed_data, stats = process_data_file_with_stats(
                input_file, output_file, velocity_mask
            )

            # 记录汇总信息
            summary.append({
                'filename': base_name,
                'time_range': time_range,
                'total_minutes': stats['total_minutes'],
                'kept_minutes': stats['kept_minutes'],
                'discarded_minutes': stats['discarded_minutes'],
                'keep_rate': stats['kept_minutes'] / max(stats['total_minutes'], 1) * 100,
                'mask_remove_rate': stats['mask_remove_rate'],
                'total_particles': stats['total_original_particles'],
                'mask_removed': stats['mask_removed_particles']
            })

            total_kept += stats['kept_minutes']
            total_all += stats['total_minutes']
            total_files += 1
            total_original_particles += stats['total_original_particles']
            total_mask_removed += stats['mask_removed_particles']

            print(f"  总分钟: {stats['total_minutes']}")
            print(f"  保留: {stats['kept_minutes']} 分钟 ({summary[-1]['keep_rate']:.1f}%)")
            print(f"  删除: {stats['discarded_minutes']} 分钟")
            print(f"  速度掩膜剔除比例: {stats['mask_remove_rate']:.1f}%")

        except Exception as e:
            print(f"  处理 {base_name} 失败: {e}")
            summary.append({
                'filename': base_name,
                'time_range': '处理失败',
                'total_minutes': 0,
                'kept_minutes': 0,
                'discarded_minutes': 0,
                'keep_rate': 0,
                'mask_remove_rate': 0,
                'total_particles': 0,
                'mask_removed': 0,
                'error': str(e)
            })

    # 计算全局掩膜剔除比例
    global_mask_rate = total_mask_removed / max(total_original_particles, 1) * 100

    # 打印汇总
    print("\n" + "=" * 60)
    print("批量处理完成!")
    print("=" * 60)
    print(f"处理文件数: {total_files}")
    print(f"总共处理: {total_all} 分钟")
    print(f"总共保留: {total_kept} 分钟")
    print(f"总保留率: {total_kept / max(total_all, 1) * 100:.1f}%")
    print(f"总原始粒子数: {total_original_particles:.0f}")
    print(f"掩膜剔除粒子数: {total_mask_removed:.0f}")
    print(f"掩膜剔除总比例: {global_mask_rate:.1f}%")
    print(f"输出目录: {output_dir}")

    # 生成汇总日志(如果有指定log_file)
    if log_file:
        log_dir = os.path.dirname(log_file)
        if log_dir and not os.path.exists(log_dir):
            os.makedirs(log_dir)

        with open(log_file, 'w', encoding='utf-8') as f:
            f.write("=" * 120 + "\n")
            f.write("雨滴数据批量处理汇总日志\n")
            f.write("=" * 120 + "\n")
            f.write(f"处理时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"输入目录: {input_dir}\n")
            f.write(f"输出目录: {output_dir}\n")
            f.write(f"速度掩膜文件: {velocity_mask_file}\n")
            f.write(f"速度掩膜剔除比例(全局固定): {mask_remove_rate_global:.1f}%\n")
            f.write(f"处理文件数: {total_files}\n")
            f.write(f"总共处理分钟数: {total_all}\n")
            f.write(f"总共保留分钟数: {total_kept}\n")
            f.write(f"总保留率: {total_kept / max(total_all, 1) * 100:.1f}%\n")
            f.write(f"总原始粒子数: {total_original_particles:.0f}\n")
            f.write(f"掩膜剔除总粒子数: {total_mask_removed:.0f}\n")
            f.write(f"掩膜剔除总比例: {global_mask_rate:.1f}%\n")
            f.write("\n" + "-" * 120 + "\n")
            f.write(f"{'序号':<6} {'文件名':<35} {'时间范围':<35} {'总分钟':<8} {'保留':<8} {'删除':<8} {'保留率':<10} {'掩膜剔除率':<10}\n")
            f.write("-" * 120 + "\n")

            for i, item in enumerate(summary, 1):
                error_info = item.get('error', '')
                if error_info:
                    f.write(
                        f"{i:<6} {item['filename']:<35} {item['time_range']:<35} {item['total_minutes']:<8} {item['kept_minutes']:<8} {item['discarded_minutes']:<8} {'':<10} 错误: {error_info}\n")
                else:
                    f.write(
                        f"{i:<6} {item['filename']:<35} {item['time_range']:<35} {item['total_minutes']:<8} {item['kept_minutes']:<8} {item['discarded_minutes']:<8} {item['keep_rate']:>6.1f}%   {item['mask_remove_rate']:>6.1f}%\n")

            f.write("-" * 120 + "\n")
            f.write(
                f"{'总计':<6} {'':<35} {'':<35} {total_all:<8} {total_kept:<8} {total_all - total_kept:<8} {total_kept / max(total_all, 1) * 100:>6.1f}%   {global_mask_rate:>6.1f}%\n")
            f.write("=" * 120 + "\n")


if __name__ == "__main__":
    # ==================== 批量处理配置 ====================
    input_dir = 'D:/lianxi/'  # 原始txt文件所在目录
    output_dir = 'D:/lianxi/cleaned/'  # 清洗后文件输出目录
    log_file = 'D:/lianxi/batch_processing_summary.txt'  # 汇总日志文件(保留)
    velocity_mask_file = 'D:/lianxi/02-速度差掩膜.xlsx'

    print("=" * 60)
    print("雨滴数据批量预处理程序")
    print("数据矩阵结构: 行=直径通道, 列=速度通道")
    print("清洗规则顺序:")
    print("  1. 速度差 > 5 m/s(用掩膜剔除)")
    print("  2. 剔除前2个直径档和后10个直径档(行)")
    print("  3. 行粒子数 < 2 -> 该行全部置0")
    print("  4. 总粒子数 < 10 -> 剔除整分钟(最后一步)")
    print("=" * 60)
    print(f"输入目录: {input_dir}")
    print(f"输出目录: {output_dir}")
    print(f"汇总日志: {log_file}")
    print(f"速度掩膜: {velocity_mask_file}")
    print("=" * 60)

    try:
        batch_process_files(input_dir, output_dir, velocity_mask_file, log_file)
        print(f"\n汇总日志已保存到: {log_file}")
    except FileNotFoundError as e:
        print(f"文件未找到: {e}")
        print("请检查文件路径是否正确")
    except Exception as e:
        print(f"处理过程中出现错误: {e}")
        import traceback
        traceback.print_exc()

二、雪花数据清洗

  1. 剔除前两个直径通道(信噪比低,基于原始直径)
  2. 轴比订正(把原始直径换算成雪花最大直径)
  3. 某个直径区间内的粒子数少于2个(防止粒子数为1的时候,u是nan)
  4. 每分钟内的粒子数量少于10个的数据;(这个得放后面要不然u算出来巨大无比)

        实测粒子下落速度与经典值之间的差异大于5米每秒的数据(论文:Atlas D, Srivastava R C, Sekhon R S. 1973. Doppler radar characteristics of precipitation at vertical incidence [J]. Rev. Geophys., 11 (1): 1–35, doi:10.1029/RG011i001p00001.)。这句根本不适用于雪花,这是针对雨滴的。也不需要看雪花的经典下落速度,因为雪花的速度量级完全不同,雪花下落速度极慢,“经典值”本身不“经典”:这是更核心的问题。雨滴有像 Gunn-Kinzer 那样被广泛接受的、稳定的“经典”下落速度公式。但雪花完全不同:形状极端多样、密度变化极大、公式不唯一,不存在一个“唯一正确”的雪花下落速度公式。

        轴比订正采用 Battaglia et al. (2010) 的方法,但需注意:对雪花而言,订正后得到的是最大水平宽度(WHD),而非等效体积直径,后续计算质量、反射率等需另用质量-直径关系。

参考文献:

[1]Battaglia A, Rustemeier E, Tokay A, et al. 2010. PARSIVEL snow observations: A critical assessment [J]. J. Atmos. Ocean. Technol., 27 (2): 333-344. doi:10.1175/2009JTECHA1332.1

[2]李遥, 牛生杰, 吕晶晶, 等. 2019. 2018 年冬季南京三次暴雪过程微物理特征分析[J]. 大气科学, 43(5): 1095-1108.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: Suyue
@file: process_snow_data.py
@time: 2026/09/02
@desc: 雪花数据清洗
       规则顺序:
       1. 剔除前2个直径通道(信噪比低,基于原始直径)
       2. 某个直径区间内的粒子数少于2个 -> 该列全部置0(防止粒子数为1时,u是nan)
       3. 每分钟内的粒子数量少于10个 -> 剔除整分钟(防止u算出来巨大无比)
       数据结构:行=直径(32行),列=速度(32列)
"""
import numpy as np
import re
import os
from datetime import datetime

# ==================== 常量定义 ====================
# 32个直径通道的中心直径 (mm) - 这是已经轴比订正后的直径
diameters_corrected = np.array([
    0.062, 0.187, 0.312, 0.437, 0.562, 0.687, 0.812, 0.937,
    1.064, 1.193, 1.388, 1.651, 1.918, 2.188, 2.463, 2.882,
    3.457, 4.050, 4.665, 5.303, 6.194, 7.321, 8.447, 9.573, 10.699,
    12.389, 14.641, 16.894, 19.146, 21.399, 24.214, 27.593
])

# 32个速度通道的中心速度 (m/s)
measured_velocity = np.array([
    0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95,
    1.1, 1.3, 1.5, 1.7, 1.9, 2.2, 2.6, 3, 3.4, 3.8,
    4.4, 5.2, 6, 6.8, 7.6, 8.8, 10.4, 12, 13.6, 15.2, 17.6, 20.8
])

# 需要剔除的直径档位索引(前2个)
INVALID_DIAMETER_BINS = list(range(0, 2))


def process_one_minute(data_32x32, minute_number=None):
    """
    处理一分钟的32×32雪花数据

    数据结构:行=直径(32行),列=速度(32列)

    规则顺序:
       1. 剔除前2个直径通道(信噪比低,基于原始直径)
       2. 某个直径区间内的粒子数少于2个 -> 该行全部置0(防止粒子数为1时,u是nan)
       3. 每分钟内的粒子数量少于10个 -> 剔除整分钟(防止u算出来巨大无比)
    """
    # 创建处理副本
    processed = data_32x32.copy().astype(float)

    # ===== 规则1: 剔除前2个直径档(信噪比低)=====
    # 行=直径,所以剔除前2行
    for row in INVALID_DIAMETER_BINS:
        processed[row, :] = 0

    # ===== 规则2: 行粒子数<2 -> 该行全部置0(防止粒子数为1时,u是nan)=====
    # 行=直径,所以检查每行(每个直径区间)
    for row in range(32):
        row_sum = np.sum(processed[row, :])
        if row_sum < 2:
            processed[row, :] = 0

    # ===== 规则3: 每分钟内的粒子数量少于10个 -> 剔除整分钟(防止u算出来巨大无比)=====
    total_particles = np.sum(processed)
    if total_particles < 10:
        return None, 'particle_count_lt10'

    return processed, 'kept'


def process_data_file(input_file, output_file, log_file=None):
    """
    处理整个数据文件
    """
    stats = {
        'total_minutes': 0,
        'kept_minutes': 0,
        'discarded_minutes': 0,
        'discard_reasons': {
            'total_lt10': 0
        }
    }

    timestamp_pattern = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$')

    current_array = []
    current_timestamp = None
    processed_data = []
    minute_count = 0

    if log_file:
        log_dir = os.path.dirname(log_file)
        if log_dir and not os.path.exists(log_dir):
            os.makedirs(log_dir)
        with open(log_file, 'w') as log:
            log.write(f"雪花数据清洗日志\n")
            log.write(f"开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            log.write(f"输入文件: {input_file}\n")
            log.write(f"输出文件: {output_file}\n\n")
            log.write("数据格式: 行=直径(32行), 列=速度(32列)\n")
            log.write("清洗规则顺序:\n")
            log.write("  1. 剔除前2个直径通道(信噪比低)\n")
            log.write("  2. 行粒子数<2 -> 该行全部置0(防止粒子数为1时,u是nan)\n")
            log.write("  3. 总粒子数<10 -> 剔除整分钟(防止u算出来巨大无比)\n\n")

    print(f"\n开始处理文件: {input_file}")
    print("数据格式: 行=直径(32行), 列=速度(32列)")

    with open(input_file, 'r') as f:
        for line in f:
            line = line.strip()

            if timestamp_pattern.match(line):
                if current_array and current_timestamp:
                    stats['total_minutes'] += 1
                    minute_count += 1

                    if len(current_array) != 32:
                        if log_file:
                            with open(log_file, 'a') as log:
                                log.write(f"{current_timestamp}: 跳过 - 数据不是32行(当前{len(current_array)}行)\n")
                        current_timestamp = line
                        current_array = []
                        continue

                    try:
                        # 原始数据:行=直径,列=速度
                        array_32x32 = np.array(current_array, dtype=float)
                    except Exception as e:
                        if log_file:
                            with open(log_file, 'a') as log:
                                log.write(f"{current_timestamp}: 跳过 - 数据格式错误: {e}\n")
                        current_timestamp = line
                        current_array = []
                        continue

                    processed, reason = process_one_minute(array_32x32, minute_count)

                    if processed is None:
                        stats['discarded_minutes'] += 1
                        if reason == 'particle_count_lt10':
                            stats['discard_reasons']['total_lt10'] += 1
                        if log_file:
                            with open(log_file, 'a') as log:
                                original_total = np.sum(array_32x32)
                                log.write(f"{current_timestamp}: 删除 - 清洗后粒子数<10 (原始:{original_total:.0f}, 清洗后:<10)\n")
                    else:
                        stats['kept_minutes'] += 1
                        processed_data.append((current_timestamp, processed))

                        if stats['kept_minutes'] == 1:
                            original_total = np.sum(array_32x32)
                            final_total = np.sum(processed)
                            print(f"\n第一个保留分钟:")
                            print(f"  时间戳: {current_timestamp}")
                            print(f"  原始粒子数: {original_total:.0f}")
                            print(f"  清洗后粒子数: {final_total:.0f}")
                            print(f"  清洗说明: 剔除前2个直径档(行) + 行粒子数<2置0 + 总粒子数<10剔除")

                current_timestamp = line
                current_array = []

            else:
                if line:
                    try:
                        row_data = list(map(float, line.split()))
                        if len(row_data) == 32:
                            current_array.append(row_data)
                    except:
                        pass

    # 处理最后一个分钟
    if current_array and current_timestamp:
        stats['total_minutes'] += 1
        minute_count += 1

        if len(current_array) == 32:
            array_32x32 = np.array(current_array, dtype=float)
            processed, reason = process_one_minute(array_32x32, minute_count)

            if processed is None:
                stats['discarded_minutes'] += 1
                if reason == 'particle_count_lt10':
                    stats['discard_reasons']['total_lt10'] += 1
            else:
                stats['kept_minutes'] += 1
                processed_data.append((current_timestamp, processed))

    # 只有有数据时才保存文件
    if processed_data:
        print(f"\n保存处理结果到: {output_file}")
        with open(output_file, 'w') as out_f:
            for timestamp, data in processed_data:
                out_f.write(f"{timestamp}\n")
                # data: 行=直径(32行), 列=速度(32列)
                for i in range(32):
                    row_int = [int(round(x)) for x in data[i, :]]
                    out_f.write(" ".join(map(str, row_int)) + "\n")
                out_f.write("\n")
    else:
        # 如果没有数据,删除可能存在的空文件
        if os.path.exists(output_file):
            os.remove(output_file)
            print(f"\n没有保留的数据,已删除空文件: {output_file}")

    # 打印统计
    print("\n" + "=" * 60)
    print("数据预处理统计报告")
    print("=" * 60)
    print(f"总处理分钟数: {stats['total_minutes']}")
    print(f"保留分钟数: {stats['kept_minutes']} ({stats['kept_minutes'] / max(stats['total_minutes'], 1) * 100:.1f}%)")
    print(
        f"删除分钟数: {stats['discarded_minutes']} ({stats['discarded_minutes'] / max(stats['total_minutes'], 1) * 100:.1f}%)")
    print(f"删除原因:")
    print(f"  - 清洗后粒子数<10: {stats['discard_reasons']['total_lt10']}")
    print(f"清洗规则:")
    print(f"  1. 剔除前2个直径档(行索引0和1)")
    print(f"  2. 行粒子数<2 -> 该行全部置0")
    print(f"  3. 总粒子数<10 -> 剔除整分钟")

    if log_file:
        with open(log_file, 'a') as log:
            log.write(f"\n处理完成!\n")
            log.write(f"总处理分钟数: {stats['total_minutes']}\n")
            log.write(f"保留分钟数: {stats['kept_minutes']}\n")
            log.write(f"删除分钟数: {stats['discarded_minutes']}\n")
            log.write(f"删除原因 - 粒子数<10: {stats['discard_reasons']['total_lt10']}\n")

    return processed_data, stats


if __name__ == "__main__":
    # ==================== 文件路径配置 ====================
    input_file = 'D:/lianxi/50934-20210507000700-20210507050859-0.txt'
    output_file = 'D:/lianxi/50934-20210507000700-20210507050859-0-clean.txt'
    log_file = 'D:/lianxi/data_processing_log-snow.txt'

    print("=" * 60)
    print("雪花数据预处理程序")
    print("数据格式: 行=直径(32行), 列=速度(32列)")
    print("清洗规则顺序:")
    print("  1. 剔除前2个直径通道(信噪比低)")
    print("  2. 行粒子数 < 2 -> 该行全部置0(防止粒子数为1时,u是nan)")
    print("  3. 总粒子数 < 10 -> 剔除整分钟(防止u算出来巨大无比)")
    print("=" * 60)
    print(f"输入文件: {input_file}")
    print(f"输出文件: {output_file}")
    print(f"日志文件: {log_file}")
    print("=" * 60)

    try:
        # 处理数据
        processed_data, stats = process_data_file(input_file, output_file, log_file)

        print(f"\n处理完成!结果已保存到: {output_file}")
        print(f"保留分钟数: {stats['kept_minutes']}")

        if processed_data:
            print(f"\n前5个保留的时间戳:")
            for i in range(min(5, len(processed_data))):
                timestamp, data = processed_data[i]
                total_particles = np.sum(data)
                print(f"  {timestamp}: {total_particles:.0f} 个粒子")

    except FileNotFoundError as e:
        print(f"文件未找到: {e}")
        print("请检查文件路径是否正确")
    except Exception as e:
        print(f"处理过程中出现错误: {e}")
        import traceback

        traceback.print_exc()

 批量处理

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: Suyue
@file: batch_process_snow_data.py
@time: 2026/07/02
@desc: 雪花数据批量清洗
       规则顺序:
       1. 剔除前2个直径通道(信噪比低,基于原始直径)
       2. 某个直径区间内的粒子数少于2个 -> 该行全部置0(防止粒子数为1时,u是nan)
       3. 每分钟内的粒子数量少于10个 -> 剔除整分钟(防止u算出来巨大无比)
       数据结构:行=直径(32行),列=速度(32列)
"""
import numpy as np
import re
import os
from datetime import datetime
import glob

# ==================== 常量定义 ====================
# 32个直径通道的中心直径 (mm) - 这是已经轴比订正后的直径
diameters_corrected = np.array([
    0.062, 0.187, 0.312, 0.437, 0.562, 0.687, 0.812, 0.937,
    1.064, 1.193, 1.388, 1.651, 1.918, 2.188, 2.463, 2.882,
    3.457, 4.050, 4.665, 5.303, 6.194, 7.321, 8.447, 9.573, 10.699,
    12.389, 14.641, 16.894, 19.146, 21.399, 24.214, 27.593
])

# 32个速度通道的中心速度 (m/s)
measured_velocity = np.array([
    0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95,
    1.1, 1.3, 1.5, 1.7, 1.9, 2.2, 2.6, 3, 3.4, 3.8,
    4.4, 5.2, 6, 6.8, 7.6, 8.8, 10.4, 12, 13.6, 15.2, 17.6, 20.8
])

# 需要剔除的直径档位索引(前2个)
INVALID_DIAMETER_BINS = list(range(0, 2))


def process_one_minute(data_32x32, minute_number=None):
    """
    处理一分钟的32×32雪花数据

    数据结构:行=直径(32行),列=速度(32列)

    规则顺序:
       1. 剔除前2个直径通道(信噪比低,基于原始直径)
       2. 某个直径区间内的粒子数少于2个 -> 该行全部置0(防止粒子数为1时,u是nan)
       3. 每分钟内的粒子数量少于10个 -> 剔除整分钟(防止u算出来巨大无比)
    """
    # 创建处理副本
    processed = data_32x32.copy().astype(float)

    # ===== 规则1: 剔除前2个直径档(信噪比低)=====
    # 行=直径,所以剔除前2行
    for row in INVALID_DIAMETER_BINS:
        processed[row, :] = 0

    # ===== 规则2: 行粒子数<2 -> 该行全部置0(防止粒子数为1时,u是nan)=====
    # 行=直径,所以检查每行(每个直径区间)
    for row in range(32):
        row_sum = np.sum(processed[row, :])
        if row_sum < 2:
            processed[row, :] = 0

    # ===== 规则3: 每分钟内的粒子数量少于10个 -> 剔除整分钟(防止u算出来巨大无比)=====
    total_particles = np.sum(processed)
    if total_particles < 10:
        return None, 'particle_count_lt10'

    return processed, 'kept'


def process_data_file(input_file, output_file, log_file=None):
    """
    处理整个数据文件
    """
    stats = {
        'total_minutes': 0,
        'kept_minutes': 0,
        'discarded_minutes': 0,
        'discard_reasons': {
            'total_lt10': 0
        },
        'total_original_particles': 0,
        'total_kept_particles': 0
    }

    timestamp_pattern = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$')

    current_array = []
    current_timestamp = None
    processed_data = []
    minute_count = 0

    if log_file:
        log_dir = os.path.dirname(log_file)
        if log_dir and not os.path.exists(log_dir):
            os.makedirs(log_dir)

    with open(input_file, 'r') as f:
        for line in f:
            line = line.strip()

            if timestamp_pattern.match(line):
                if current_array and current_timestamp:
                    stats['total_minutes'] += 1
                    minute_count += 1

                    if len(current_array) != 32:
                        if log_file:
                            with open(log_file, 'a') as log:
                                log.write(f"{current_timestamp}: 跳过 - 数据不是32行\n")
                        current_timestamp = line
                        current_array = []
                        continue

                    try:
                        array_32x32 = np.array(current_array, dtype=float)
                    except Exception as e:
                        if log_file:
                            with open(log_file, 'a') as log:
                                log.write(f"{current_timestamp}: 跳过 - 数据格式错误: {e}\n")
                        current_timestamp = line
                        current_array = []
                        continue

                    # 统计原始粒子数
                    original_total = np.sum(array_32x32)
                    stats['total_original_particles'] += original_total

                    processed, reason = process_one_minute(array_32x32, minute_count)

                    if processed is None:
                        stats['discarded_minutes'] += 1
                        if reason == 'particle_count_lt10':
                            stats['discard_reasons']['total_lt10'] += 1
                        if log_file:
                            with open(log_file, 'a') as log:
                                log.write(f"{current_timestamp}: 删除 - 清洗后粒子数<10 (原始:{original_total:.0f}, 清洗后:<10)\n")
                    else:
                        stats['kept_minutes'] += 1
                        final_total = np.sum(processed)
                        stats['total_kept_particles'] += final_total
                        processed_data.append((current_timestamp, processed))

                current_timestamp = line
                current_array = []

            else:
                if line:
                    try:
                        row_data = list(map(float, line.split()))
                        if len(row_data) == 32:
                            current_array.append(row_data)
                    except:
                        pass

    # 处理最后一个分钟
    if current_array and current_timestamp:
        stats['total_minutes'] += 1
        minute_count += 1

        if len(current_array) == 32:
            array_32x32 = np.array(current_array, dtype=float)
            original_total = np.sum(array_32x32)
            stats['total_original_particles'] += original_total

            processed, reason = process_one_minute(array_32x32, minute_count)

            if processed is None:
                stats['discarded_minutes'] += 1
                if reason == 'particle_count_lt10':
                    stats['discard_reasons']['total_lt10'] += 1
            else:
                stats['kept_minutes'] += 1
                final_total = np.sum(processed)
                stats['total_kept_particles'] += final_total
                processed_data.append((current_timestamp, processed))

    # 只有有数据时才保存文件
    if processed_data:
        with open(output_file, 'w') as out_f:
            for timestamp, data in processed_data:
                out_f.write(f"{timestamp}\n")
                # data: 行=直径(32行), 列=速度(32列)
                for i in range(32):
                    row_int = [int(round(x)) for x in data[i, :]]
                    out_f.write(" ".join(map(str, row_int)) + "\n")
                out_f.write("\n")
    else:
        # 如果没有数据,删除可能存在的空文件
        if os.path.exists(output_file):
            os.remove(output_file)

    if log_file:
        with open(log_file, 'a') as log:
            log.write(f"\n处理完成!\n")
            log.write(f"总处理分钟数: {stats['total_minutes']}\n")
            log.write(f"保留分钟数: {stats['kept_minutes']}\n")
            log.write(f"删除分钟数: {stats['discarded_minutes']}\n")
            log.write(f"删除原因 - 粒子数<10: {stats['discard_reasons']['total_lt10']}\n")

    return processed_data, stats


def batch_process_files(input_dir, output_dir, log_file=None):
    """
    批量处理目录下所有txt文件

    参数:
        input_dir: 输入文件目录
        output_dir: 输出文件目录
        log_file: 汇总日志文件路径(可选)
    """
    # 创建输出目录
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # 查找所有txt文件
    txt_files = glob.glob(os.path.join(input_dir, "*.txt"))

    if not txt_files:
        print(f"在 {input_dir} 中没有找到txt文件")
        return

    print(f"\n找到 {len(txt_files)} 个txt文件")
    print("数据格式: 行=直径(32行), 列=速度(32列)")
    print("=" * 60)

    # 汇总数据
    summary = []
    total_kept = 0
    total_all = 0
    total_files = 0

    for input_file in txt_files:
        # 生成输出文件名
        base_name = os.path.basename(input_file)
        name_without_ext = os.path.splitext(base_name)[0]
        output_file = os.path.join(output_dir, f"{name_without_ext}-clean.txt")

        try:
            # 获取文件时间范围
            time_range = get_file_time_range(input_file)

            processed_data, stats = process_data_file(input_file, output_file, log_file=None)

            # 计算粒子保留率
            particle_keep_rate = stats['total_kept_particles'] / max(stats['total_original_particles'], 1) * 100

            # 记录汇总信息
            summary.append({
                'filename': base_name,
                'time_range': time_range,
                'total_minutes': stats['total_minutes'],
                'kept_minutes': stats['kept_minutes'],
                'discarded_minutes': stats['discarded_minutes'],
                'keep_rate': stats['kept_minutes'] / max(stats['total_minutes'], 1) * 100,
                'particle_keep_rate': particle_keep_rate
            })

            total_kept += stats['kept_minutes']
            total_all += stats['total_minutes']
            total_files += 1
        except Exception as e:
            summary.append({
                'filename': base_name,
                'time_range': '处理失败',
                'total_minutes': 0,
                'kept_minutes': 0,
                'discarded_minutes': 0,
                'keep_rate': 0,
                'particle_keep_rate': 0,
                'error': str(e)
            })

    # 打印汇总
    print("\n" + "=" * 60)
    print("批量处理完成!")
    print(f"处理文件数: {total_files}")
    print(f"总共处理: {total_all} 分钟")
    print(f"总共保留: {total_kept} 分钟")
    print(f"总保留率: {total_kept / max(total_all, 1) * 100:.1f}%")
    print(f"输出目录: {output_dir}")

    # 生成汇总日志
    if log_file:
        log_dir = os.path.dirname(log_file)
        if log_dir and not os.path.exists(log_dir):
            os.makedirs(log_dir)

        with open(log_file, 'w', encoding='utf-8') as f:
            f.write("=" * 110 + "\n")
            f.write("雪花数据批量处理汇总日志\n")
            f.write("=" * 110 + "\n")
            f.write(f"处理时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"输入目录: {input_dir}\n")
            f.write(f"输出目录: {output_dir}\n")
            f.write(f"数据格式: 行=直径(32行), 列=速度(32列)\n")
            f.write(f"处理文件数: {total_files}\n")
            f.write(f"总共处理分钟数: {total_all}\n")
            f.write(f"总共保留分钟数: {total_kept}\n")
            f.write(f"总保留率: {total_kept / max(total_all, 1) * 100:.1f}%\n")
            f.write("\n" + "=" * 110 + "\n")
            f.write("清洗规则顺序:\n")
            f.write("  1. 剔除前2个直径通道(信噪比低)- 剔除前2行\n")
            f.write("  2. 行粒子数<2 -> 该行全部置0(防止粒子数为1时,u是nan)\n")
            f.write("  3. 总粒子数<10 -> 剔除整分钟(防止u算出来巨大无比)\n")
            f.write("\n" + "-" * 110 + "\n")
            f.write(f"{'文件名':<35} {'时间范围':<30} {'总分钟':<8} {'保留':<8} {'删除':<8} {'分钟保留率':<10} {'粒子保留率':<10}\n")
            f.write("-" * 110 + "\n")

            for item in summary:
                error_info = item.get('error', '')
                if error_info:
                    f.write(
                        f"{item['filename']:<35} {item['time_range']:<30} {item['total_minutes']:<8} {item['kept_minutes']:<8} {item['discarded_minutes']:<8} {'':<10} 错误: {error_info}\n")
                else:
                    f.write(
                        f"{item['filename']:<35} {item['time_range']:<30} {item['total_minutes']:<8} {item['kept_minutes']:<8} {item['discarded_minutes']:<8} {item['keep_rate']:>6.1f}%   {item['particle_keep_rate']:>6.1f}%\n")

            f.write("-" * 110 + "\n")
            f.write(
                f"总计{'':<33} {total_all:<8} {total_kept:<8} {total_all - total_kept:<8} {total_kept / max(total_all, 1) * 100:>6.1f}%\n")
            f.write("=" * 110 + "\n")


def get_file_time_range(input_file):
    """
    从文件中提取时间范围
    """
    timestamp_pattern = re.compile(r'^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$')
    timestamps = []

    try:
        with open(input_file, 'r') as f:
            for line in f:
                line = line.strip()
                if timestamp_pattern.match(line):
                    timestamps.append(line)
                    if len(timestamps) >= 2:
                        break
        # 找最后一个时间戳
        with open(input_file, 'r') as f:
            lines = f.readlines()
            for line in reversed(lines):
                line = line.strip()
                if timestamp_pattern.match(line):
                    if len(timestamps) < 2 or line != timestamps[-1]:
                        timestamps.append(line)
                    break
    except:
        pass

    if len(timestamps) >= 2:
        start = timestamps[0]
        end = timestamps[-1]
        return f"{start} ~ {end}"
    elif len(timestamps) == 1:
        return timestamps[0]
    else:
        return "未知"


if __name__ == "__main__":
    # ==================== 批量处理配置 ====================
    input_dir = 'D:/lianxi/'  # 原始txt文件所在目录
    output_dir = 'D:/lianxi/cleaned/'  # 清洗后文件输出目录
    log_file = 'D:/lianxi/batch_processing_summary_snow.txt'  # 汇总日志文件

    print("=" * 60)
    print("雪花数据批量预处理程序")
    print("数据格式: 行=直径(32行), 列=速度(32列)")
    print("清洗规则顺序:")
    print("  1. 剔除前2个直径通道(信噪比低)- 剔除前2行")
    print("  2. 行粒子数 < 2 -> 该行全部置0(防止粒子数为1时,u是nan)")
    print("  3. 总粒子数 < 10 -> 剔除整分钟(防止u算出来巨大无比)")
    print("=" * 60)

    try:
        batch_process_files(input_dir, output_dir, log_file)
        print(f"\n汇总日志已保存到: {log_file}")
    except FileNotFoundError as e:
        print(f"文件未找到: {e}")
        print("请检查文件路径是否正确")
    except Exception as e:
        print(f"处理过程中出现错误: {e}")
        import traceback

        traceback.print_exc()

 

posted @ 2026-08-31 06:56  SuYue2990  Views(10)  Comments(0)    收藏  举报