Files
NLP/lab1/stylometry/metadata.py

100 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Анализ ошибок по метаданным персонажей."""
from __future__ import annotations
from pathlib import Path
from typing import Sequence
import pandas as pd
from .evaluation import per_class_report
DEFAULT_METADATA_PATH = Path(__file__).resolve().parents[1] / "data" / "metadata" / "characters.csv"
METADATA_COLUMNS = ("gender", "age_group", "species", "role")
def load_character_metadata(path: str | Path | None = None) -> pd.DataFrame:
"""Читает таблицу пола, возраста, вида и роли персонажей."""
path = Path(path) if path is not None else DEFAULT_METADATA_PATH
if not path.exists():
raise FileNotFoundError(f"Нет файла метаданных: {path}")
frame = pd.read_csv(path)
required = {"character", *METADATA_COLUMNS}
missing = required - set(frame.columns)
if missing:
raise ValueError(f"В метаданных нет колонок: {sorted(missing)}")
frame = frame.copy()
frame["character"] = frame["character"].astype(str).str.strip()
return frame
def subgroup_report(
y_true: Sequence,
y_pred: Sequence,
labels: Sequence,
metadata: pd.DataFrame,
group_column: str = "gender",
) -> pd.DataFrame:
"""Сводка F-меры по подгруппам персонажей."""
if group_column not in metadata.columns:
raise ValueError(f"Нет колонки {group_column!r} в метаданных.")
per_class = per_class_report(y_true, y_pred, labels)
per_class = per_class.merge(
metadata[["character", group_column]],
left_on="class",
right_on="character",
how="left",
)
rows = []
for value, group in per_class.groupby(group_column, dropna=False):
support = int(group["support"].sum())
weighted = float((group["f1"] * group["support"]).sum() / max(support, 1))
rows.append(
{
"group_column": group_column,
"group": str(value),
"n_characters": int(len(group)),
"n_samples": support,
"weighted_f1": weighted,
"mean_class_f1": float(group["f1"].mean()),
"min_class_f1": float(group["f1"].min()),
"max_class_f1": float(group["f1"].max()),
}
)
return pd.DataFrame(rows).sort_values(["weighted_f1"], ascending=False)
def group_confusions(
y_true: Sequence,
y_pred: Sequence,
metadata: pd.DataFrame,
group_column: str = "gender",
) -> pd.DataFrame:
"""Считает, как часто ошибки происходят внутри и между группами."""
frame = pd.DataFrame({"true_character": list(y_true), "pred_character": list(y_pred)})
frame = frame.merge(
metadata[["character", group_column]].rename(
columns={"character": "true_character", group_column: "true_group"}
),
on="true_character",
how="left",
).merge(
metadata[["character", group_column]].rename(
columns={"character": "pred_character", group_column: "pred_group"}
),
on="pred_character",
how="left",
)
errors = frame[frame["true_character"] != frame["pred_character"]]
table = (
errors.groupby(["true_group", "pred_group"], dropna=False)
.size()
.reset_index(name="count")
.sort_values("count", ascending=False)
)
total = max(int(table["count"].sum()), 1)
table["share_of_errors"] = table["count"] / total
table["same_group"] = table["true_group"] == table["pred_group"]
return table