python chapter5:降水过程筛选(时间筛选)

对于 Zhu 等的方法重新界定降水过程:夏季用 Nw>0 连续12分钟;总时长>60分钟:

第1步:找到所有 Nw > 0 的分钟

第2步:找到整场雨的边界

- 开始 = 第一个 Nw > 0 的分钟

- 结束 = 最后一个 Nw > 0 的分钟

第3步:计算总时长 = 结束 - 开始 + 1(包括断档)

如果总时长 < 60 分钟 → 丢弃

第4步:检查在开始到结束之间,是否存在至少一个连续≥12分钟的Nw>0窗口

如果没有 → 丢弃

第5步:如果有,保留从开始到结束的整段数据(包括断档)

数据情况:

image

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: Suyue
@file: filter_by_Nw.py
@time: 2026/06/29
@desc: 根据Nw筛选降水事件
      条件1:总时长(第一个Nw>0 到 最后一个Nw>0)≥ 60分钟
      条件2:存在至少一个连续≥12分钟的Nw>0窗口
      满足条件则保留整段数据(包括断档)
"""
import numpy as np
import pandas as pd
import re
import os


# ==================== 读取CSV参数文件 ====================
def load_csv(csv_path):
    """读取参数CSV文件"""
    df = pd.read_csv(csv_path)
    # 把time列转为datetime
    df['time'] = pd.to_datetime(df['time'])
    # Nw列转为数值,'NaN'字符串转为真正的NaN
    df['Nw'] = pd.to_numeric(df['Nw'], errors='coerce')
    return df


# ==================== 筛选有效时间区间 ====================
def find_valid_ranges(df, min_continuous=12, min_duration=60):
    """
    找到所有符合条件的降水时间区间

    参数:
        df: 包含time和Nw列的DataFrame
        min_continuous: 最小连续分钟数(默认12)
        min_duration: 最小总时长分钟数(默认60)

    返回:
        valid_ranges: 列表,每个元素为 (开始时间, 结束时间)
    """
    # 标记 Nw > 0 的分钟
    df['is_rain'] = (df['Nw'] > 0) & (~df['Nw'].isna())

    # 如果没有任何有效分钟,直接返回空
    if not df['is_rain'].any():
        print("  没有 Nw > 0 的分钟")
        return []

    # 找到第一个和最后一个 Nw > 0 的分钟
    rain_indices = df[df['is_rain']].index
    start_idx = rain_indices[0]
    end_idx = rain_indices[-1]

    start_time = df.loc[start_idx, 'time']
    end_time = df.loc[end_idx, 'time']
    total_duration = (end_time - start_time).total_seconds() / 60 + 1

    print(f"  整场雨边界: {start_time} -> {end_time}")
    print(f"  总时长: {total_duration:.0f} 分钟")

    # 条件1:总时长 >= 60分钟
    if total_duration < min_duration:
        print(f"  ❌ 总时长 {total_duration:.0f} 分钟 < {min_duration} 分钟,丢弃")
        return []

    # 条件2:检查是否存在连续 >= 12 分钟的 Nw > 0
    # 只看从start_idx到end_idx这一段
    sub_df = df.loc[start_idx:end_idx]

    max_consecutive = 0
    current_consecutive = 0
    consecutive_start = None

    for idx, row in sub_df.iterrows():
        if row['is_rain']:
            current_consecutive += 1
            if current_consecutive > max_consecutive:
                max_consecutive = current_consecutive
                # 记录连续段的开始时间
                if current_consecutive == 1:
                    consecutive_start = row['time']
        else:
            current_consecutive = 0

    print(f"  最大连续Nw>0分钟数: {max_consecutive}")

    if max_consecutive < min_continuous:
        print(f"  ❌ 最大连续 {max_consecutive} 分钟 < {min_continuous} 分钟,丢弃")
        return []

    print(f"  ✅ 满足条件!保留从 {start_time} 到 {end_time} 的全部数据")
    return [(start_time, end_time)]


# ==================== 从原始txt提取数据 ====================
def extract_by_timerange(input_txt, output_txt, valid_ranges):
    """
    根据时间区间从原始txt中提取数据

    参数:
        input_txt: 原始txt文件路径
        output_txt: 输出txt文件路径
        valid_ranges: 有效时间区间列表 [(start, end), ...]
    """
    if not valid_ranges:
        print("  没有有效区间,跳过")
        return

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

    print(f"\n正在读取原始文件: {input_txt}")

    with open(input_txt, 'r', encoding='utf-8') as f:
        lines = f.readlines()

    # 构建时间区间集合,用于快速判断
    # 把每个区间内的所有分钟展开成一个集合
    keep_times = set()
    for start, end in valid_ranges:
        current = start
        while current <= end:
            keep_times.add(current.strftime("%Y-%m-%d %H:%M:%S"))
            current += pd.Timedelta(minutes=1)

    print(f"  需要保留 {len(keep_times)} 分钟的数据")

    # 遍历原始文件,提取需要保留的数据
    output_lines = []
    i = 0
    kept_count = 0
    total_minutes = 0

    while i < len(lines):
        line = lines[i].strip()

        if timestamp_pattern.match(line):
            total_minutes += 1
            timestamp = line

            if timestamp in keep_times:
                # 保留这一分钟:时间戳 + 32行数据
                output_lines.append(line + '\n')
                for j in range(1, 33):
                    if i + j < len(lines):
                        output_lines.append(lines[i + j])
                output_lines.append('\n')  # 分钟间空行
                kept_count += 1
                i += 33
            else:
                # 跳过这一分钟
                i += 33
        else:
            i += 1

    print(f"  原始总分钟数: {total_minutes}")
    print(f"  保留分钟数: {kept_count}")

    # 写入输出文件
    with open(output_txt, 'w', encoding='utf-8') as f:
        f.writelines(output_lines)

    print(f"  输出文件: {output_txt}")


# ==================== 主函数 ====================
def main():
    # ===== 配置路径 =====
    csv_path = "D:/lianxi/50425-20240824073200-20240824084559-0-clean-DSD.csv"  # 参数CSV文件
    input_txt = "D:/lianxi/50425-20240824073200-20240824084559-0-clean.txt"  # 原始txt
    output_txt = "D:/lianxi/50425-20240824073200-20240824084559-0-clean_DSD_filtered.txt"  # 输出txt

    # ===== 筛选参数 =====
    MIN_CONTINUOUS = 12  # 连续Nw>0的最小分钟数
    MIN_DURATION = 60  # 总时长最小分钟数

    print("=" * 70)
    print("雨滴谱数据筛选程序")
    print("=" * 70)
    print(f"条件1: 总时长 >= {MIN_DURATION} 分钟")
    print(f"条件2: 存在连续 >= {MIN_CONTINUOUS} 分钟的 Nw > 0")
    print("=" * 70)

    # 1. 读取CSV
    print("\n[1] 读取参数CSV...")
    if not os.path.exists(csv_path):
        print(f"  错误: CSV文件不存在: {csv_path}")
        return
    df = load_csv(csv_path)
    print(f"  共 {len(df)} 分钟数据")

    # 2. 筛选有效区间
    print("\n[2] 筛选有效降水区间...")
    valid_ranges = find_valid_ranges(df, MIN_CONTINUOUS, MIN_DURATION)

    if not valid_ranges:
        print("\n❌ 未找到符合条件的降水事件,程序结束")
        return

    # 3. 从原始txt提取数据
    print("\n[3] 从原始文件提取数据...")
    if not os.path.exists(input_txt):
        print(f"  错误: 原始文件不存在: {input_txt}")
        return
    extract_by_timerange(input_txt, output_txt, valid_ranges)

    print("\n" + "=" * 70)
    print("✅ 筛选完成!")
    print("=" * 70)


if __name__ == "__main__":
    main()

 

就是不对原始数据做任何修改,只是"复制"符合条件的分钟到一个新文件。原始文件完好无损。

image

 

 批量处理数据代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@author: Suyue
@file: batch_filter_by_Nw.py
@time: 2026/07/06
@desc: 批量根据Nw筛选降水事件
      条件1:总时长(第一个Nw>0 到 最后一个Nw>0)≥ 60分钟
      条件2:存在至少一个连续≥12分钟的Nw>0窗口
      满足条件则保留整段数据(包括断档)
"""
import numpy as np
import pandas as pd
import re
import os


# ==================== 读取CSV参数文件 ====================
def load_csv(csv_path):
    """读取参数CSV文件"""
    df = pd.read_csv(csv_path)
    # 把time列转为datetime
    df['time'] = pd.to_datetime(df['time'])
    # Nw列转为数值,'NaN'字符串转为真正的NaN
    df['Nw'] = pd.to_numeric(df['Nw'], errors='coerce')
    return df


# ==================== 筛选有效时间区间 ====================
def find_valid_ranges(df, min_continuous=12, min_duration=60):
    """
    找到所有符合条件的降水时间区间

    参数:
        df: 包含time和Nw列的DataFrame
        min_continuous: 最小连续分钟数(默认12)
        min_duration: 最小总时长分钟数(默认60)

    返回:
        valid_ranges: 列表,每个元素为 (开始时间, 结束时间)
    """
    # 标记 Nw > 0 的分钟
    df['is_rain'] = (df['Nw'] > 0) & (~df['Nw'].isna())

    # 如果没有任何有效分钟,直接返回空
    if not df['is_rain'].any():
        print("  没有 Nw > 0 的分钟")
        return []

    # 找到第一个和最后一个 Nw > 0 的分钟
    rain_indices = df[df['is_rain']].index
    start_idx = rain_indices[0]
    end_idx = rain_indices[-1]

    start_time = df.loc[start_idx, 'time']
    end_time = df.loc[end_idx, 'time']
    total_duration = (end_time - start_time).total_seconds() / 60 + 1

    print(f"  整场雨边界: {start_time} -> {end_time}")
    print(f"  总时长: {total_duration:.0f} 分钟")

    # 条件1:总时长 >= 60分钟
    if total_duration < min_duration:
        print(f"  ❌ 总时长 {total_duration:.0f} 分钟 < {min_duration} 分钟,丢弃")
        return []

    # 条件2:检查是否存在连续 >= 12 分钟的 Nw > 0
    sub_df = df.loc[start_idx:end_idx]

    max_consecutive = 0
    current_consecutive = 0

    for idx, row in sub_df.iterrows():
        if row['is_rain']:
            current_consecutive += 1
            if current_consecutive > max_consecutive:
                max_consecutive = current_consecutive
        else:
            current_consecutive = 0

    print(f"  最大连续Nw>0分钟数: {max_consecutive}")

    if max_consecutive < min_continuous:
        print(f"  ❌ 最大连续 {max_consecutive} 分钟 < {min_continuous} 分钟,丢弃")
        return []

    print(f"  ✅ 满足条件!保留从 {start_time} 到 {end_time} 的全部数据")
    return [(start_time, end_time)]


# ==================== 从原始txt提取数据 ====================
def extract_by_timerange(input_txt, output_txt, valid_ranges):
    """
    根据时间区间从原始txt中提取数据

    参数:
        input_txt: 原始txt文件路径
        output_txt: 输出txt文件路径
        valid_ranges: 有效时间区间列表 [(start, end), ...]
    """
    if not valid_ranges:
        print("  没有有效区间,跳过")
        return False

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

    with open(input_txt, 'r', encoding='utf-8') as f:
        lines = f.readlines()

    # 构建时间区间集合
    keep_times = set()
    for start, end in valid_ranges:
        current = start
        while current <= end:
            keep_times.add(current.strftime("%Y-%m-%d %H:%M:%S"))
            current += pd.Timedelta(minutes=1)

    print(f"  需要保留 {len(keep_times)} 分钟的数据")

    # 遍历原始文件,提取需要保留的数据
    output_lines = []
    i = 0
    kept_count = 0
    total_minutes = 0

    while i < len(lines):
        line = lines[i].strip()

        if timestamp_pattern.match(line):
            total_minutes += 1
            timestamp = line

            if timestamp in keep_times:
                # 保留这一分钟:时间戳 + 32行数据
                output_lines.append(line + '\n')
                for j in range(1, 33):
                    if i + j < len(lines):
                        output_lines.append(lines[i + j])
                output_lines.append('\n')  # 分钟间空行
                kept_count += 1
                i += 33
            else:
                # 跳过这一分钟
                i += 33
        else:
            i += 1

    print(f"  原始总分钟数: {total_minutes}")
    print(f"  保留分钟数: {kept_count}")

    # 确保输出目录存在
    os.makedirs(os.path.dirname(output_txt), exist_ok=True)

    # 写入输出文件
    with open(output_txt, 'w', encoding='utf-8') as f:
        f.writelines(output_lines)

    print(f"  输出文件: {os.path.basename(output_txt)}")
    return True


# ==================== 批量处理函数 ====================
def batch_filter(csv_folder, txt_folder, output_folder, min_continuous=12, min_duration=60):
    """
    批量处理文件夹中的所有文件

    参数:
        csv_folder: CSV文件所在文件夹 (存放 *-clean-DSD.csv)
        txt_folder: TXT文件所在文件夹 (存放 *-clean.txt)
        output_folder: 输出文件夹
        min_continuous: 最小连续分钟数
        min_duration: 最小总时长分钟数

    文件名格式要求:
        CSV: XXX-clean-DSD.csv (在csv_folder中)
        TXT: XXX-clean.txt (在txt_folder中)
        输出: XXX-clean_DSD_filtered.txt (在output_folder中)
    """
    print("=" * 70)
    print("雨滴谱数据批量筛选程序")
    print("=" * 70)
    print(f"条件1: 总时长 >= {min_duration} 分钟")
    print(f"条件2: 存在连续 >= {min_continuous} 分钟的 Nw > 0")
    print("=" * 70)
    print(f"CSV文件夹: {csv_folder}")
    print(f"TXT文件夹: {txt_folder}")
    print(f"输出文件夹: {output_folder}")
    print("=" * 70)

    # 检查文件夹是否存在
    if not os.path.exists(csv_folder):
        print(f"错误: CSV文件夹不存在: {csv_folder}")
        return

    if not os.path.exists(txt_folder):
        print(f"错误: TXT文件夹不存在: {txt_folder}")
        return

    # 创建输出文件夹
    os.makedirs(output_folder, exist_ok=True)

    # 查找所有CSV文件 (以-clean-DSD.csv结尾)
    csv_files = []
    for file in os.listdir(csv_folder):
        if file.endswith('-clean-DSD.csv'):
            csv_files.append(file)

    if not csv_files:
        print(f"\n错误: 在 {csv_folder} 中没有找到任何 -clean-DSD.csv 文件")
        print("请确认CSV文件命名格式为: XXX-clean-DSD.csv")
        return

    print(f"\n找到 {len(csv_files)} 个CSV文件")

    # 处理每个CSV文件
    success_count = 0
    fail_count = 0
    failed_files = []

    for i, csv_file in enumerate(csv_files, 1):
        # 构造对应的txt文件名: XXX-clean-DSD.csv -> XXX-clean.txt
        txt_file = csv_file.replace('-clean-DSD.csv', '-clean.txt')
        txt_path = os.path.join(txt_folder, txt_file)

        # 检查txt文件是否存在
        if not os.path.exists(txt_path):
            print(f"\n[{i}/{len(csv_files)}] ❌ 跳过: {csv_file}")
            print(f"  找不到对应的txt文件: {txt_file}")
            print(f"  请确认txt文件在: {txt_folder}")
            fail_count += 1
            failed_files.append(f"{csv_file} (缺少对应的txt)")
            continue

        # 构造输出文件名: XXX-clean-DSD.csv -> XXX-clean_DSD_filtered.txt
        base_name = csv_file.replace('-clean-DSD.csv', '')
        output_txt = os.path.join(output_folder, f"{base_name}-clean_DSD_filtered.txt")

        print(f"\n[{i}/{len(csv_files)}] 处理: {base_name}")
        print("-" * 50)

        try:
            # 1. 读取CSV
            csv_path = os.path.join(csv_folder, csv_file)
            print(f"  读取CSV: {csv_file}")
            df = load_csv(csv_path)
            print(f"  CSV数据: {len(df)} 分钟")

            # 2. 筛选有效区间
            valid_ranges = find_valid_ranges(df, min_continuous, min_duration)

            if not valid_ranges:
                print(f"  ❌ 未找到符合条件的降水事件")
                fail_count += 1
                failed_files.append(f"{csv_file} (不符合条件)")
                continue

            # 3. 从原始txt提取数据
            print(f"  读取TXT: {txt_file}")
            success = extract_by_timerange(txt_path, output_txt, valid_ranges)

            if success:
                print(f"  ✅ 处理成功")
                success_count += 1
            else:
                print(f"  ❌ 提取数据失败")
                fail_count += 1
                failed_files.append(f"{csv_file} (提取失败)")

        except Exception as e:
            print(f"  ❌ 处理出错: {str(e)}")
            fail_count += 1
            failed_files.append(f"{csv_file} (错误: {str(e)})")

    # 输出统计
    print("\n" + "=" * 70)
    print("批量处理完成!")
    print("=" * 70)
    print(f"总文件数: {len(csv_files)}")
    print(f"成功处理: {success_count}")
    print(f"处理失败: {fail_count}")

    if failed_files:
        print("\n失败的文件列表:")
        for f in failed_files:
            print(f"  ❌ {f}")

    print(f"\n输出文件夹: {output_folder}")
    print("=" * 70)


# ==================== 主函数 ====================
def main():
    # ===== 配置路径 =====
    csv_folder = "D:/雨滴谱/数据处理/03-DSD/夏季"  # CSV文件所在文件夹 (存放 *-clean-DSD.csv)
    txt_folder = "D:/雨滴谱/数据处理/02-数据清洗/新清洗法/夏季"  # TXT文件所在文件夹 (存放 *-clean.txt)
    output_folder = "D:/雨滴谱/数据处理/04-时间连续/夏季"  # 输出文件夹

    # ===== 筛选参数 =====
    MIN_CONTINUOUS = 12  # 连续Nw>0的最小分钟数
    MIN_DURATION = 60  # 总时长最小分钟数

    # ===== 开始批量处理 =====
    batch_filter(csv_folder, txt_folder, output_folder, MIN_CONTINUOUS, MIN_DURATION)


if __name__ == "__main__":
    main()

 

posted @ 2026-06-29 10:26  SuYue2990  Views(9)  Comments(0)    收藏  举报