Spaces:
Sleeping
Sleeping
File size: 29,721 Bytes
fd485d9 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
#!/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()
|