添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/

1. 问题陈述

这是一个懒人创造世界的一个例子。在科研实验、机器学习项目或自动化测试中,我们经常会遇到这样的场景:一个主文件夹下有多个子文件夹,每个子文件夹中都有类似结构的CSV文件,我们需要从中提取特定列的数据,然后将所有数据整合到一个Excel文件中进行对比分析。

想象一下这样一个典型的工作流:

  • 有5个不同的实验设置,每个设置在独立的文件夹中运行
  • 每个实验生成一个progress_eval.csv文件记录进度与实验结果
  • 你需要从每个文件中提取"accuracy"这一列
  • 最后将所有数据放在一个Excel表格中对比,并进行后续处理(如取平均值、标准差、标准误)
  • 传统的手动操作需要:逐个打开5个CSV文件 → 找到目标列 → 复制数据 → 粘贴到Excel → 调整格式 → 用Excel函数(如AVERAGE()、STDEV.S())处理数据... 这个过程不仅耗时,而且容易出错。 本文使用Python来一键完成这个繁琐的过程!

    2. Python代码

    这个脚本主要包含以下几个核心功能:

  • 批量提取数据:从指定数量的子文件夹中提取CSV文件的特定列
  • 智能列匹配:支持按列名或列索引提取数据
  • 数据对齐:自动将不同长度的数据对齐到相同行数
  • 交互式操作:提供友好的命令行交互界面
  • # Python代码:从多文件夹提取指定列并整合
    # 这段代码的主要功能是:
    # 从多个实验文件夹中批量提取指定CSV文件的特定列数据,
    # 并自动对齐整合为统一的Excel表格,便于后续分析比较
    # 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/
    import pandas as pd
    from pathlib import Path
    from typing import Optional, Union
    def extract_data(
            base_path: str,
            num_folders: int,
            column_spec: Union[str, int],
            csv_name: str = "progress_eval.csv"
    ) -> None:
        从文件夹中提取指定CSV文件的列数据
            base_path: 主文件夹路径
            num_folders: 处理的子文件夹数量
            column_spec: 列名(str)或列索引(int)
            csv_name: 已有的需要处理的CSV文件名
        path = Path(base_path)
        if not path.exists():
            print(f"文件夹 {base_path} 不存在")
            return
        # 获取子文件夹
        folders = sorted([f for f in path.iterdir() if f.is_dir()])[:num_folders]
        if not folders:
            print("未找到子文件夹")
            return
        data = {}
        print(f"处理 {csv_name} 文件:")
        for folder in folders:
            csv_file = folder / csv_name
            print(f"  {folder.name}", end="")
            if not csv_file.exists():
                print(" -> 文件不存在")
                continue
            try:
                df = pd.read_csv(csv_file)
                col = _get_column(df, column_spec)
                if col:
                    data[folder.name] = df[col]
                    print(f" -> 提取 {len(df)} 行")
            except Exception as e:
                print(f" -> 错误: {e}")
        if not data:
            print("没有提取到数据")
            return
        # 对齐并保存数据
        aligned_df = _align_to_max(data)
        output_file = f"{path.name}_seed{num_folders}_{_col_str(column_spec)}.xlsx" # 可以自行改输出的表格文件名
        aligned_df.to_excel(output_file, index=False)
        print(f"\n数据已保存到 {output_file}")
        print(f"包含 {len(aligned_df.columns)} 列, {len(aligned_df)} 行")
    def _get_column(df: pd.DataFrame, spec: Union[str, int]) -> Optional[str]:
        """获取目标列名"""
        if isinstance(spec, str):
            # 精确匹配
            if spec in df.columns:
                return spec
            # 模糊匹配
            matches = [col for col in df.columns if spec.lower() in col.lower()]
            return matches[0] if matches else None
        if isinstance(spec, int):
            return df.columns[spec] if 0 <= spec < len(df.columns) else None
        return None
    def _align_to_max(data: dict) -> pd.DataFrame:
        """对齐数据到最大行数"""
        max_len = max(len(d) for d in data.values())
        aligned_data = {}
        for name, series in data.items():
            if len(series) < max_len:
                # 明确指定dtype为object,避免警告
                padding = pd.Series([None] * (max_len - len(series)), dtype=object)
                aligned_data[name] = pd.concat([series, padding], ignore_index=True)
            else:
                aligned_data[name] = series.reset_index(drop=True)
        return pd.DataFrame(aligned_data)
    def _col_str(spec: Union[str, int]) -> str:
        """生成列标识字符串"""
        if isinstance(spec, str):
            return spec.replace('/', '_').replace('\\', '_')[:20]
        return f"col{spec}"
    def show_columns(
            base_path: str,
            folder_idx: int = 0,
            csv_name: str = "progress_eval.csv"
    ) -> None:
        """显示列信息"""
        path = Path(base_path)
        folders = sorted([f for f in path.iterdir() if f.is_dir()])
        if folder_idx < len(folders):
            csv_file = folders[folder_idx] / csv_name
            if csv_file.exists():
                df = pd.read_csv(csv_file)
                print(f"\n{folders[folder_idx].name} 的列信息 ({csv_name}):")
                for i, col in enumerate(df.columns):
                    print(f"  [{i}] {col}")
            else:
                print(f"文件 {csv_name} 不存在")
    def select_column_interactive(
            base_path: str,
            folder_idx: int = 0,
            csv_name: str = "progress_eval.csv"
    ) -> Optional[Union[str, int]]:
        """交互式选择列"""
        show_columns(base_path, folder_idx, csv_name)
        while True:
            choice = input("\n输入列名/索引 (q退出): ").strip()
            if choice.lower() == 'q':
                return
    
    
    
    
        
     None
            if choice.isdigit():
                return int(choice)
            return choice
    # 使用示例
    if __name__ == "__main__":
        base_path = "Algorithm" # 主文件夹
        num_folders = 5 # 子文件夹的个数(前num_folders个子文件夹)
        csv_name = "progress_eval.csv" # 已有的需要处理的CSV文件名
        # 显示列信息
        # show_columns(base_path, 0, csv_name)
        # 交互式选择
        selected = select_column_interactive(base_path, 0, csv_name)
        if selected is not None:
            extract_data(base_path, num_folders, selected, csv_name)
        # 或直接指定
        # extract_data(base_path, num_folders, 3, csv_name)
        # extract_data(base_path, num_folders, "acc", csv_name)
        # extract_data(base_path, num_folders, "accuracy", csv_name)

    3. 使用实例

    假设我们有一个名为Algorithm的文件夹,结构如下:

    Algorithm/
    ├── seed1/
    │   └── progress_eval.csv
    ├── seed2/
    │   └── progress_eval.csv
    ├── ...
    └── seed5/
    └── progress_eval.csv

    每个CSV文件包含类似的数据列: epoch, train_loss, val_loss, accuracy, learning_rate, total_time

    Algorithm文件夹:

    每个子文件夹:

    每个CSV文件内容(随便举的例子):

    先显示所有列信息: show_columns(base_path, 0, csv_name)

    seed0 的列信息 (progress_eval.csv):
      [0] epoch
      [1] train_loss
      [2] val_loss
      [3] accuracy
      [4] learning_rate
      [5] total_time

    示例1:交互式使用

    selected = select_column_interactive(base_path, 0, csv_name)
        if selected is not None:
            extract_data(base_path, num_folders, selected, csv_name)

    键入3,得到:

    seed0 的列信息 (progress_eval.csv):
      [0] epoch
      [1] train_loss
      [2] val_loss
      [3] accuracy
      [4] learning_rate
      [5] total_time
    输入列名/索引 (q退出): 3
    处理 progress_eval.csv 文件:
      seed0 -> 提取 11 行
      seed1 -> 提取 11 行
      seed2 -> 提取 11 行
      seed3 -> 提取 11 行
      seed4 -> 提取 11 行
    数据已保存到 Algorithm_seed5_col3.xlsx
    包含 5 列, 11 行

    键入acc,得到:

    seed0 的列信息 (progress_eval.csv):
      [0] epoch
      [1] train_loss
      [2] val_loss
      [3] accuracy
      [4] learning_rate
      [5] total_time
    输入列名/索引 (q退出): acc
    处理 progress_eval.csv 文件:
      seed0 -> 提取 11 行
      seed1 -> 提取 11 行
      seed2 -> 提取 11 行
      seed3 -> 提取 11 行
      seed4 -> 提取 11 行
    数据已保存到 Algorithm_seed5_acc.xlsx
    包含 5 列, 11 行

    示例2:直接指定

    (1) extract_data(base_path, num_folders, 3, csv_name)

    处理 progress_eval.csv 文件:
      seed0 -> 提取 11 行
      seed1 -> 提取 11 行
      seed2 -> 提取 11 行
      seed3 -> 提取 11 行
      seed4 -> 提取 11 行
    数据已保存到 Algorithm_seed5_col3.xlsx
    包含 5 列, 11 行

    (2) extract_data(base_path, num_folders, " acc " , csv_name) 或者 extract_data(base_path, num_folders, " accuracy " , csv_name)

    处理 progress_eval.csv 文件:
      seed0 -> 提取 11 行
      seed1 -> 提取 11 行
      seed2 -> 提取 11 行
      seed3 -> 提取 11 行
      seed4 -> 提取 11 行
    数据已保存到 Algorithm_seed5_acc.xlsx
    包含 5 列, 11 行

    得到的Excel表格内容就是将seed子文件夹下面的CSV中的精度那一列整合到一个表格中:

    4. 将上述得到的Excel表格每一列取平均值、标准差、标准误(可选)

    # 在上述基础上计算生成的Excel文件的前n列的平均值、标准差、标准误差
    # 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/
    import pandas as pd
    import numpy as np
    from pathlib import Path
    def add_statistics_columns(excel_file: str, num_columns: int, output_suffix: str = "_with_stats") -> None:
        处理Excel文件,计算前num_columns列的均值、标准差和标准误
            excel_file: Excel文件路径
            num_columns: 计算前几列的统计量
            output_suffix: 输出文件名的后缀
        # 检查文件是否存在
        file_path = Path(excel_file)
        if not file_path.exists():
            print(f"错误: 文件 {excel_file} 不存在")
            return
        # 检查文件格式
        if file_path.suffix.lower() not in ['.xlsx', '.xls']:
            print(f"错误: {excel_file} 不是Excel文件")
            return
        try:
            # 读取Excel文件
            df = pd.read_excel(excel_file)
            # 检查列数是否足够
            if len(df.columns) < num_columns:
                print(f"错误: 表格只有 {len(df.columns)} 列,无法计算前 {num_columns} 列的统计量")
                return
            # 计算前num_columns列的统计量
            columns_to_analyze = df.columns[:num_columns]
            # 计算均值
            df['mean'] = df[columns_to_analyze].mean(axis=1, skipna=True)
            # 计算标准差
            df['std'] = df[columns_to_analyze].std(axis=1, skipna=True)
            # 计算标准误(标准差除以根号n)
            # 先计算每行的有效数据个数(排除NaN)
            valid_counts = df[columns_to_analyze].count(axis=1)
            df['sem'] = df['std'] / np.sqrt(valid_counts)
            # 可选:格式化显示,保留4位小数
            df['mean'] = df['mean'].round(4)
            df['std'] = df['std'].round(4)
            df['sem'] = df['sem'].round(4)
            # 生成输出文件名
            stem = file_path.stem
            suffix = file_path.suffix
            output_file = f"{stem}{output_suffix}{suffix}"
            # 保存到新文件
            df.to_excel(output_file, index=False)
            print(f"原始文件: {excel_file}")
            print(f"处理后的文件: {output_file}")
            print(f"已添加三列统计量:mean(均值)、std(标准差)、sem(标准误)")
            print(f"处理了 {len(df)} 行数据")
        except Exception as e:
            print(f"处理文件时发生错误: {e}")
    if __name__ == "__main__":
        excel_file = "Algorithm_seed5_acc.xlsx"  # 需要处理的Excel文件名
        num_columns = 5  # 计算前num_columns列的统计量
        # 调用处理函数
        add_statistics_columns(excel_file, num_columns)
    原始文件: Algorithm_seed5_acc.xlsx
    处理后的文件: Algorithm_seed5_acc_with_stats.xlsx
    已添加三列统计量:mean(均值)、std(标准差)、sem(标准误)
    处理了 11 行数据

    得到的新Excel文件在原先基础上多了三列:平均值、标准差、标准误。

    5. 实际应用场景

    (1)机器学习实验分析

    当你在不同随机种子下运行实验时,这个工具可以帮助你:比较不同随机种子的训练曲线、提取关键指标进行统计分析、生成整齐的数据表格用于论文图表

    (2)科学研究数据整理

    处理重复实验数据时,可以:批量提取实验测量值、统一数据格式、准备数据用于进一步统计分析

    这个脚本是一个实用的数据提取工具,特别适合需要处理多个相似数据文件的场景。通过自动化繁琐的文件操作,它能够:大幅提高数据处理效率、减少人为错误、提供一致的输出格式、支持灵活的列选择方式。

    作者: 凯鲁嘎吉
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。