68 lines
2.1 KiB
Python
68 lines
2.1 KiB
Python
"""Tipos de columna y resumen de análisis."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
TYPE_INTEGER = "integer"
|
|
TYPE_DECIMAL = "decimal"
|
|
TYPE_DATE = "date"
|
|
TYPE_BOOLEAN = "boolean"
|
|
TYPE_CATEGORICAL = "categorical"
|
|
TYPE_TEXT = "text"
|
|
TYPE_MIXED = "mixed"
|
|
TYPE_EMPTY = "empty"
|
|
|
|
|
|
@dataclass
|
|
class ColumnSummary:
|
|
"""Resumen inteligente para una columna del DataFrame."""
|
|
name: str
|
|
inferred_type: str
|
|
example: str = ""
|
|
samples: list[str] = field(default_factory=list)
|
|
null_count: int = 0
|
|
null_pct: float = 0.0
|
|
unique_count: int = 0
|
|
unique_pct: float = 0.0
|
|
min: Optional[float] = None
|
|
max: Optional[float] = None
|
|
mean: Optional[float] = None
|
|
avg_length: Optional[float] = None
|
|
top_values: list[tuple[str, int]] = field(default_factory=list)
|
|
detected_date_format: Optional[str] = None
|
|
applicable_rules: list[str] = field(default_factory=list)
|
|
|
|
def info_text(self) -> str:
|
|
"""Texto humano con información adicional (para subtítulo de la UI)."""
|
|
parts: list[str] = []
|
|
parts.append(f"nulos: {self.null_count} ({self.null_pct:.0f}%)")
|
|
parts.append(f"únicos: {self.unique_count}")
|
|
if self.inferred_type in (TYPE_INTEGER, TYPE_DECIMAL):
|
|
if self.min is not None:
|
|
parts.append(f"min: {_fmt_num(self.min)}")
|
|
if self.max is not None:
|
|
parts.append(f"max: {_fmt_num(self.max)}")
|
|
if self.mean is not None:
|
|
parts.append(f"media: {_fmt_num(self.mean)}")
|
|
elif self.inferred_type == TYPE_DATE:
|
|
if self.detected_date_format:
|
|
parts.append(f"formato: {self.detected_date_format}")
|
|
elif self.inferred_type in (TYPE_TEXT, TYPE_CATEGORICAL):
|
|
if self.avg_length is not None:
|
|
parts.append(f"long. prom: {self.avg_length:.1f}")
|
|
if self.top_values:
|
|
tops = ", ".join(f'"{v}" ({c})' for v, c in self.top_values[:3])
|
|
parts.append(f"top: {tops}")
|
|
return " · ".join(parts)
|
|
|
|
|
|
def _fmt_num(x: float) -> str:
|
|
if x is None:
|
|
return ""
|
|
if isinstance(x, float) and x.is_integer():
|
|
return str(int(x))
|
|
if isinstance(x, float):
|
|
return f"{x:.2f}"
|
|
return str(x)
|