Spaces:
Sleeping
Sleeping
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
""" | |
Скрипт для агрегации и анализа результатов множества запусков pipeline.py. | |
Читает все CSV-файлы из директории промежуточных результатов, | |
объединяет их и вычисляет агрегированные метрики: | |
- Weighted (усредненные по всем вопросам, взвешенные по количеству пунктов/чанков/документов) | |
- Macro (усредненные по вопросам - сначала считаем метрику для каждого вопроса, потом усредняем) | |
- Micro (считаем общие TP, FP, FN по всем вопросам, потом вычисляем метрики) | |
Результаты сохраняются в один Excel-файл с несколькими листами. | |
""" | |
import argparse | |
import glob | |
# Импорт для обработки JSON строк | |
import os | |
import pandas as pd | |
from openpyxl import Workbook | |
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side | |
from openpyxl.utils import get_column_letter | |
from openpyxl.utils.dataframe import dataframe_to_rows | |
# Прогресс-бар | |
from tqdm import tqdm | |
# --- Настройки --- | |
DEFAULT_INTERMEDIATE_DIR = "data/intermediate" # Откуда читать CSV | |
DEFAULT_OUTPUT_DIR = "data/output" # Куда сохранять итоговый Excel | |
DEFAULT_OUTPUT_FILENAME = "aggregated_results.xlsx" | |
# --- Маппинг названий столбцов на русский язык --- | |
COLUMN_NAME_MAPPING = { | |
# Параметры запуска из pipeline.py | |
'run_id': 'ID Запуска', | |
'model_name': 'Модель', | |
'chunking_strategy': 'Стратегия Чанкинга', | |
'strategy_params': 'Параметры Стратегии', | |
'process_tables': 'Обраб. Таблиц', | |
'top_n': 'Top N', | |
'use_injection': 'Сборка Контекста', | |
'use_qe': 'Query Expansion', | |
'neighbors_included': 'Вкл. Соседей', | |
'similarity_threshold': 'Порог Схожести', | |
# Идентификаторы из датасета (для детальных результатов) | |
'question_id': 'ID Вопроса', | |
'question_text': 'Текст Вопроса', | |
# Детальные метрики из pipeline.py | |
'chunk_text_precision': 'Точность (Чанк-Текст)', | |
'chunk_text_recall': 'Полнота (Чанк-Текст)', | |
'chunk_text_f1': 'F1 (Чанк-Текст)', | |
'found_puncts': 'Найдено Пунктов', | |
'total_puncts': 'Всего Пунктов', | |
'relevant_chunks': 'Релевантных Чанков', | |
'total_chunks_in_top_n': 'Всего Чанков в Топ-N', | |
'assembly_punct_recall': 'Полнота (Сборка-Пункт)', | |
'assembled_context_preview': 'Предпросмотр Сборки', | |
# 'top_chunk_ids': 'Индексы Топ-Чанков', # Списки, могут плохо отображаться | |
# 'top_chunk_similarities': 'Схожести Топ-Чанков', # Списки | |
# Агрегированные метрики (добавляются в calculate_aggregated_metrics) | |
'weighted_chunk_text_precision': 'Weighted Точность (Чанк-Текст)', | |
'weighted_chunk_text_recall': 'Weighted Полнота (Чанк-Текст)', | |
'weighted_chunk_text_f1': 'Weighted F1 (Чанк-Текст)', | |
'weighted_assembly_punct_recall': 'Weighted Полнота (Сборка-Пункт)', | |
'macro_chunk_text_precision': 'Macro Точность (Чанк-Текст)', | |
'macro_chunk_text_recall': 'Macro Полнота (Чанк-Текст)', | |
'macro_chunk_text_f1': 'Macro F1 (Чанк-Текст)', | |
'macro_assembly_punct_recall': 'Macro Полнота (Сборка-Пункт)', | |
'micro_text_precision': 'Micro Точность (Текст)', | |
'micro_text_recall': 'Micro Полнота (Текст)', | |
'micro_text_f1': 'Micro F1 (Текст)', | |
} | |
def parse_args(): | |
"""Парсит аргументы командной строки.""" | |
parser = argparse.ArgumentParser(description="Агрегация результатов оценочных пайплайнов") | |
parser.add_argument("--intermediate-dir", type=str, default=DEFAULT_INTERMEDIATE_DIR, | |
help=f"Директория с промежуточными CSV результатами (по умолчанию: {DEFAULT_INTERMEDIATE_DIR})") | |
parser.add_argument("--output-dir", type=str, default=DEFAULT_OUTPUT_DIR, | |
help=f"Директория для сохранения итогового Excel файла (по умолчанию: {DEFAULT_OUTPUT_DIR})") | |
parser.add_argument("--output-filename", type=str, default=DEFAULT_OUTPUT_FILENAME, | |
help=f"Имя выходного Excel файла (по умолчанию: {DEFAULT_OUTPUT_FILENAME})") | |
parser.add_argument("--latest-batch-only", action="store_true", | |
help="Агрегировать результаты только для последнего batch_id") | |
return parser.parse_args() | |
def load_intermediate_results(intermediate_dir: str) -> pd.DataFrame: | |
"""Загружает все CSV файлы из указанной директории.""" | |
print(f"Загрузка промежуточных результатов из: {intermediate_dir}") | |
csv_files = glob.glob(os.path.join(intermediate_dir, "results_*.csv")) | |
if not csv_files: | |
print(f"ВНИМАНИЕ: В директории {intermediate_dir} не найдено файлов 'results_*.csv'.") | |
return pd.DataFrame() | |
all_data = [] | |
for f in csv_files: | |
try: | |
df = pd.read_csv(f) | |
all_data.append(df) | |
print(f" Загружен файл: {os.path.basename(f)} ({len(df)} строк)") | |
except Exception as e: | |
print(f"Ошибка при чтении файла {f}: {e}") | |
if not all_data: | |
print("Не удалось загрузить ни одного файла с результатами.") | |
return pd.DataFrame() | |
combined_df = pd.concat(all_data, ignore_index=True) | |
print(f"Всего загружено строк: {len(combined_df)}") | |
print(f"Найденные колонки: {combined_df.columns.tolist()}") | |
# Преобразуем типы данных для надежности | |
numeric_cols = [ | |
'chunk_text_precision', 'chunk_text_recall', 'chunk_text_f1', | |
'found_puncts', 'total_puncts', 'relevant_chunks', | |
'total_chunks_in_top_n', | |
'assembly_punct_recall', | |
'similarity_threshold', 'top_n', | |
] | |
for col in numeric_cols: | |
if col in combined_df.columns: | |
combined_df[col] = pd.to_numeric(combined_df[col], errors='coerce') | |
boolean_cols = [ | |
'use_injection', | |
'process_tables', | |
'use_qe', | |
'neighbors_included' | |
] | |
for col in boolean_cols: | |
if col in combined_df.columns: | |
# Пытаемся конвертировать в bool, обрабатывая строки 'True'/'False' | |
if combined_df[col].dtype == 'object': | |
combined_df[col] = combined_df[col].astype(str).str.lower().map({'true': True, 'false': False}).fillna(False) | |
combined_df[col] = combined_df[col].astype(bool) | |
# Заполним пропуски в числовых колонках нулями (например, если метрики не посчитались) | |
combined_df[numeric_cols] = combined_df[numeric_cols].fillna(0) | |
# --- Обработка batch_id --- | |
if 'batch_id' in combined_df.columns: | |
# Приводим к строке и заполняем NaN | |
combined_df['batch_id'] = combined_df['batch_id'].astype(str).fillna('unknown_batch') | |
else: | |
# Если колонки нет, создаем ее | |
print("Предупреждение: Колонка 'batch_id' отсутствует в загруженных данных. Добавлена со значением 'unknown_batch'.") | |
combined_df['batch_id'] = 'unknown_batch' | |
# -------------------------- | |
# Переименовываем столбцы в русские названия ДО возврата | |
# Отбираем только те колонки, для которых есть перевод | |
columns_to_rename = {eng: rus for eng, rus in COLUMN_NAME_MAPPING.items() if eng in combined_df.columns} | |
combined_df = combined_df.rename(columns=columns_to_rename) | |
print(f"Столбцы переименованы. Новые колонки: {combined_df.columns.tolist()}") | |
return combined_df | |
def calculate_aggregated_metrics(df: pd.DataFrame) -> pd.DataFrame: | |
""" | |
Вычисляет агрегированные метрики (Weighted, Macro, Micro) | |
для каждой уникальной комбинации параметров запуска. | |
Ожидает DataFrame с русскими названиями колонок. | |
""" | |
if df.empty: | |
return pd.DataFrame() | |
# Определяем параметры, по которым будем группировать (ИСПОЛЬЗУЕМ РУССКИЕ НАЗВАНИЯ) | |
grouping_params_rus = [ | |
COLUMN_NAME_MAPPING.get('model_name', 'Модель'), | |
COLUMN_NAME_MAPPING.get('chunking_strategy', 'Стратегия Чанкинга'), | |
COLUMN_NAME_MAPPING.get('strategy_params', 'Параметры Стратегии'), | |
COLUMN_NAME_MAPPING.get('process_tables', 'Обраб. Таблиц'), | |
COLUMN_NAME_MAPPING.get('top_n', 'Top N'), | |
COLUMN_NAME_MAPPING.get('use_injection', 'Сборка Контекста'), | |
COLUMN_NAME_MAPPING.get('use_qe', 'Query Expansion'), | |
COLUMN_NAME_MAPPING.get('neighbors_included', 'Вкл. Соседей'), | |
COLUMN_NAME_MAPPING.get('similarity_threshold', 'Порог Схожести') | |
] | |
# Проверяем наличие всех колонок для группировки (с русскими именами) | |
missing_cols = [col for col in grouping_params_rus if col not in df.columns] | |
if missing_cols: | |
print(f"Ошибка: Отсутствуют необходимые колонки для группировки (русские): {missing_cols}") | |
# Удаляем отсутствующие колонки из списка группировки | |
grouping_params_rus = [col for col in grouping_params_rus if col not in missing_cols] | |
if not grouping_params_rus: | |
print("Невозможно выполнить группировку.") | |
return pd.DataFrame() | |
print(f"Группировка по параметрам (русские): {grouping_params_rus}") | |
# Используем grouping_params_rus для группировки | |
grouped = df.groupby(grouping_params_rus) | |
aggregated_results = [] | |
# Итерируемся по каждой группе (комбинации параметров) | |
for params, group_df in tqdm(grouped, desc="Расчет агрегированных метрик"): | |
# Начинаем со словаря параметров (уже с русскими именами) | |
agg_result = dict(zip(grouping_params_rus, params)) | |
# --- Метрики для усреднения/взвешивания (РУССКИЕ НАЗВАНИЯ) --- | |
chunk_prec_col = COLUMN_NAME_MAPPING.get('chunk_text_precision', 'Точность (Чанк-Текст)') | |
chunk_rec_col = COLUMN_NAME_MAPPING.get('chunk_text_recall', 'Полнота (Чанк-Текст)') | |
chunk_f1_col = COLUMN_NAME_MAPPING.get('chunk_text_f1', 'F1 (Чанк-Текст)') | |
assembly_rec_col = COLUMN_NAME_MAPPING.get('assembly_punct_recall', 'Полнота (Сборка-Пункт)') | |
total_chunks_col = COLUMN_NAME_MAPPING.get('total_chunks_in_top_n', 'Всего Чанков в Топ-N') | |
total_puncts_col = COLUMN_NAME_MAPPING.get('total_puncts', 'Всего Пунктов') | |
found_puncts_col = COLUMN_NAME_MAPPING.get('found_puncts', 'Найдено Пунктов') # Для micro | |
relevant_chunks_col = COLUMN_NAME_MAPPING.get('relevant_chunks', 'Релевантных Чанков') # Для micro | |
# Колонки, которые должны существовать для расчетов | |
required_metric_cols = [chunk_prec_col, chunk_rec_col, chunk_f1_col, assembly_rec_col] | |
required_count_cols = [total_chunks_col, total_puncts_col, found_puncts_col, relevant_chunks_col] | |
existing_metric_cols = [m for m in required_metric_cols if m in group_df.columns] | |
existing_count_cols = [c for c in required_count_cols if c in group_df.columns] | |
# --- Macro метрики (Простое усреднение метрик по вопросам) --- | |
if existing_metric_cols: | |
macro_metrics = group_df[existing_metric_cols].mean().rename( | |
# Генерируем имя 'Macro Имя Метрики' | |
lambda x: COLUMN_NAME_MAPPING.get(f"macro_{{key}}".format(key=next((k for k, v in COLUMN_NAME_MAPPING.items() if v == x), None)), f"Macro {x}") | |
).to_dict() | |
agg_result.update(macro_metrics) | |
else: | |
print(f"Предупреждение: Пропуск Macro метрик для группы {params}, нет колонок метрик.") | |
# --- Weighted метрики (Взвешенное усреднение) --- | |
weighted_chunk_precision = 0.0 | |
weighted_chunk_recall = 0.0 | |
weighted_assembly_recall = 0.0 | |
weighted_chunk_f1 = 0.0 | |
# Проверяем наличие необходимых колонок для взвешенного расчета | |
can_calculate_weighted = True | |
if chunk_prec_col not in existing_metric_cols or total_chunks_col not in existing_count_cols: | |
print(f"Предупреждение: Пропуск Weighted Точность (Чанк-Текст) для группы {params}, отсутствуют {chunk_prec_col} или {total_chunks_col}.") | |
can_calculate_weighted = False | |
if chunk_rec_col not in existing_metric_cols or total_puncts_col not in existing_count_cols: | |
print(f"Предупреждение: Пропуск Weighted Полнота (Чанк-Текст) для группы {params}, отсутствуют {chunk_rec_col} или {total_puncts_col}.") | |
can_calculate_weighted = False | |
if assembly_rec_col not in existing_metric_cols or total_puncts_col not in existing_count_cols: | |
print(f"Предупреждение: Пропуск Weighted Полнота (Сборка-Пункт) для группы {params}, отсутствуют {assembly_rec_col} или {total_puncts_col}.") | |
# Не сбрасываем can_calculate_weighted, т.к. другие weighted могут посчитаться | |
if can_calculate_weighted: | |
total_chunks_sum = group_df[total_chunks_col].sum() | |
total_puncts_sum = group_df[total_puncts_col].sum() | |
# Weighted Precision (Chunk-Text) | |
if total_chunks_sum > 0 and chunk_prec_col in existing_metric_cols: | |
weighted_chunk_precision = (group_df[chunk_prec_col] * group_df[total_chunks_col]).sum() / total_chunks_sum | |
# Weighted Recall (Chunk-Text) | |
if total_puncts_sum > 0 and chunk_rec_col in existing_metric_cols: | |
weighted_chunk_recall = (group_df[chunk_rec_col] * group_df[total_puncts_col]).sum() / total_puncts_sum | |
# Weighted Recall (Assembly-Punct) | |
if total_puncts_sum > 0 and assembly_rec_col in existing_metric_cols: | |
weighted_assembly_recall = (group_df[assembly_rec_col] * group_df[total_puncts_col]).sum() / total_puncts_sum | |
# Weighted F1 (Chunk-Text) - на основе weighted precision и recall | |
if weighted_chunk_precision + weighted_chunk_recall > 0: | |
weighted_chunk_f1 = (2 * weighted_chunk_precision * weighted_chunk_recall) / (weighted_chunk_precision + weighted_chunk_recall) | |
# Добавляем рассчитанные Weighted метрики в результат | |
agg_result[COLUMN_NAME_MAPPING.get('weighted_chunk_text_precision', 'Weighted Точность (Чанк-Текст)')] = weighted_chunk_precision | |
agg_result[COLUMN_NAME_MAPPING.get('weighted_chunk_text_recall', 'Weighted Полнота (Чанк-Текст)')] = weighted_chunk_recall | |
agg_result[COLUMN_NAME_MAPPING.get('weighted_chunk_text_f1', 'Weighted F1 (Чанк-Текст)')] = weighted_chunk_f1 | |
agg_result[COLUMN_NAME_MAPPING.get('weighted_assembly_punct_recall', 'Weighted Полнота (Сборка-Пункт)')] = weighted_assembly_recall | |
# --- Micro метрики (На основе общих TP, FP, FN, ИСПОЛЬЗУЕМ РУССКИЕ НАЗВАНИЯ) --- | |
# Колонки уже определены выше | |
if not all(col in group_df.columns for col in [found_puncts_col, total_puncts_col, relevant_chunks_col, total_chunks_col]): | |
print(f"Предупреждение: Пропуск расчета micro-метрик для группы {params}, т.к. отсутствуют необходимые колонки.") | |
agg_result[COLUMN_NAME_MAPPING.get('micro_text_precision', 'Micro Точность (Текст)')] = 0.0 | |
agg_result[COLUMN_NAME_MAPPING.get('micro_text_recall', 'Micro Полнота (Текст)')] = 0.0 | |
agg_result[COLUMN_NAME_MAPPING.get('micro_text_f1', 'Micro F1 (Текст)')] = 0.0 | |
# Добавляем результат группы в общий список | |
aggregated_results.append(agg_result) | |
# Создаем итоговый DataFrame (уже с русскими именами) | |
final_df = pd.DataFrame(aggregated_results) | |
print(f"Рассчитаны агрегированные метрики для {len(final_df)} комбинаций параметров.") | |
# Возвращаем DataFrame с русскими названиями колонок | |
return final_df | |
# --- Функции для форматирования Excel (адаптированы из combine_results.py) --- | |
def apply_excel_formatting(workbook: Workbook): | |
"""Применяет форматирование ко всем листам книги Excel.""" | |
header_font = Font(bold=True) | |
header_fill = PatternFill(start_color="D9D9D9", end_color="D9D9D9", fill_type="solid") | |
center_alignment = Alignment(horizontal='center', vertical='center') | |
wrap_alignment = Alignment(horizontal='center', vertical='center', wrap_text=True) | |
thin_border = Border( | |
left=Side(style='thin'), | |
right=Side(style='thin'), | |
top=Side(style='thin'), | |
bottom=Side(style='thin') | |
) | |
thick_top_border = Border(top=Side(style='thick')) | |
for sheet_name in workbook.sheetnames: | |
sheet = workbook[sheet_name] | |
if sheet.max_row <= 1: # Пропускаем пустые листы | |
continue | |
# Форматирование заголовков | |
for cell in sheet[1]: | |
cell.font = header_font | |
cell.fill = header_fill | |
cell.alignment = wrap_alignment | |
cell.border = thin_border | |
# Автоподбор ширины и форматирование ячеек | |
for col_idx, column_cells in enumerate(sheet.columns, 1): | |
max_length = 0 | |
column_letter = get_column_letter(col_idx) | |
is_numeric_metric_col = False | |
header_value = sheet.cell(row=1, column=col_idx).value | |
# Проверяем, является ли колонка числовой метрикой | |
if isinstance(header_value, str) and any(m in header_value for m in ['precision', 'recall', 'f1', 'relevance']): | |
is_numeric_metric_col = True | |
for i, cell in enumerate(column_cells): | |
# Применяем границы ко всем ячейкам | |
cell.border = thin_border | |
# Центрируем все, кроме заголовка | |
if i > 0: | |
cell.alignment = center_alignment | |
# Формат для числовых метрик | |
if is_numeric_metric_col and i > 0 and isinstance(cell.value, (int, float)): | |
cell.number_format = '0.0000' | |
# Расчет ширины | |
try: | |
cell_len = len(str(cell.value)) | |
if cell_len > max_length: | |
max_length = cell_len | |
except: | |
pass | |
adjusted_width = (max_length + 2) * 1.1 | |
sheet.column_dimensions[column_letter].width = min(adjusted_width, 60) # Ограничиваем макс ширину | |
# Автофильтр | |
sheet.auto_filter.ref = sheet.dimensions | |
# Группировка строк (опционально, можно добавить логику из combine_results, если нужна) | |
# ... (здесь можно вставить apply_group_formatting, если требуется) ... | |
print("Форматирование Excel завершено.") | |
def save_to_excel(data_dict: dict[str, pd.DataFrame], output_path: str): | |
"""Сохраняет несколько DataFrame в один Excel файл с форматированием.""" | |
print(f"Сохранение результатов в Excel: {output_path}") | |
try: | |
workbook = Workbook() | |
workbook.remove(workbook.active) # Удаляем лист по умолчанию | |
for sheet_name, df in data_dict.items(): | |
if df is not None and not df.empty: | |
sheet = workbook.create_sheet(title=sheet_name) | |
for r in dataframe_to_rows(df, index=False, header=True): | |
# Проверяем и заменяем недопустимые символы в ячейках | |
cleaned_row = [] | |
for cell_value in r: | |
if isinstance(cell_value, str): | |
# Удаляем управляющие символы, кроме стандартных пробельных | |
cleaned_value = ''.join(c for c in cell_value if c.isprintable() or c in ' \t\n\r') | |
cleaned_row.append(cleaned_value) | |
else: | |
cleaned_row.append(cell_value) | |
sheet.append(cleaned_row) | |
print(f" Лист '{sheet_name}' добавлен ({len(df)} строк)") | |
else: | |
print(f" Лист '{sheet_name}' пропущен (нет данных)") | |
# Применяем форматирование ко всей книге | |
if workbook.sheetnames: # Проверяем, что есть хотя бы один лист | |
apply_excel_formatting(workbook) | |
workbook.save(output_path) | |
print("Excel файл успешно сохранен.") | |
else: | |
print("Нет данных для сохранения в Excel.") | |
except Exception as e: | |
print(f"Ошибка при сохранении Excel файла: {e}") | |
# --- Основная функция --- | |
def main(): | |
"""Основная функция скрипта.""" | |
args = parse_args() | |
# 1. Загрузка данных | |
combined_df_eng = load_intermediate_results(args.intermediate_dir) | |
if combined_df_eng.empty: | |
print("Нет данных для агрегации. Завершение.") | |
return | |
# --- Фильтрация по последнему batch_id (если флаг установлен) --- | |
target_df = combined_df_eng # По умолчанию используем все данные | |
if args.latest_batch_only: | |
print("Фильтрация по последнему batch_id...") | |
if 'batch_id' not in combined_df_eng.columns: | |
print("Предупреждение: Колонка 'batch_id' не найдена. Агрегация будет выполнена по всем данным.") | |
else: | |
# Находим последний batch_id (сортируем строки по batch_id) | |
# Сначала отфильтруем 'unknown_batch' | |
valid_batches = combined_df_eng[combined_df_eng['batch_id'] != 'unknown_batch']['batch_id'].unique() | |
if len(valid_batches) > 0: | |
# Сортируем уникальные валидные ID и берем последний | |
latest_batch_id = sorted(valid_batches)[-1] | |
print(f"Используется последний валидный batch_id: {latest_batch_id}") | |
target_df = combined_df_eng[combined_df_eng['batch_id'] == latest_batch_id].copy() | |
if target_df.empty: | |
# Это не должно произойти, если latest_batch_id валидный, но на всякий случай | |
print(f"Предупреждение: Не найдено данных для batch_id {latest_batch_id}. Агрегация будет выполнена по всем данным.") | |
target_df = combined_df_eng | |
else: | |
print(f"Оставлено строк после фильтрации: {len(target_df)}") | |
else: | |
print("Предупреждение: Не найдено валидных batch_id для фильтрации. Агрегация будет выполнена по всем данным.") | |
# target_df уже равен combined_df_eng, так что ничего не делаем | |
# latest_batch_id = combined_df_eng['batch_id'].astype(str).sort_values().iloc[-1] | |
# print(f"Используется последний batch_id: {latest_batch_id}") | |
# target_df = combined_df_eng[combined_df_eng['batch_id'] == latest_batch_id].copy() | |
# if target_df.empty: | |
# print(f"Предупреждение: Нет данных для batch_id {latest_batch_id}. Агрегация будет выполнена по всем данным.") | |
# target_df = combined_df_eng # Возвращаемся ко всем данным, если фильтр дал пустоту | |
# else: | |
# print(f"Оставлено строк после фильтрации: {len(target_df)}") | |
# --- Заполнение NaN и переименование ПОСЛЕ возможной фильтрации --- | |
# Определяем числовые колонки еще раз (используя английские названия из маппинга) | |
numeric_cols_eng = [eng for eng, rus in COLUMN_NAME_MAPPING.items() \ | |
if 'recall' in eng or 'precision' in eng or 'f1' in eng or 'puncts' in eng \ | |
or 'chunks' in eng or 'threshold' in eng or 'top_n' in eng] | |
numeric_cols_in_df = [col for col in numeric_cols_eng if col in target_df.columns] | |
target_df[numeric_cols_in_df] = target_df[numeric_cols_in_df].fillna(0) | |
# Переименовываем | |
columns_to_rename_detailed = {eng: rus for eng, rus in COLUMN_NAME_MAPPING.items() if eng in target_df.columns} | |
target_df_rus = target_df.rename(columns=columns_to_rename_detailed) | |
# 2. Расчет агрегированных метрик | |
# Передаем DataFrame с русскими названиями колонок, calculate_aggregated_metrics теперь их ожидает | |
aggregated_df_rus = calculate_aggregated_metrics(target_df_rus) | |
# Переименовываем столбцы агрегированного DF уже внутри calculate_aggregated_metrics | |
# aggregated_df_rus = pd.DataFrame() # Инициализируем на случай, если aggregated_df_eng пуст | |
# if not aggregated_df_eng.empty: | |
# columns_to_rename_agg = {eng: rus for eng, rus in COLUMN_NAME_MAPPING.items() if eng in aggregated_df_eng.columns} | |
# aggregated_df_rus = aggregated_df_eng.rename(columns=columns_to_rename_agg) | |
# 3. Подготовка данных для сохранения (с русскими названиями) | |
data_to_save = { | |
"Детальные результаты": target_df_rus, # Используем переименованный DF | |
"Агрегированные метрики": aggregated_df_rus, # Используем переименованный DF | |
} | |
# 4. Сохранение в Excel | |
os.makedirs(args.output_dir, exist_ok=True) | |
output_file_path = os.path.join(args.output_dir, args.output_filename) | |
save_to_excel(data_to_save, output_file_path) | |
if __name__ == "__main__": | |
main() | |