Nivel: Intermedio
Tema: Archivos, CSV, parsing, conversión de tipos, agregaciones, validación de entradas, tests con pytest
Objetivo: Leer un archivo CSV usando solo la librería estándar y calcular métricas básicas (conteo, suma y promedio) de una columna numérica, ignorando filas inválidas.

Enunciado
Crea una función llamada metricas_csv(ruta_csv, columna) que:
- Reciba:
ruta_csv: ruta (string) a un archivo CSV con cabeceracolumna: nombre (string) de una columna numérica
- Si
ruta_csvesNoneocolumnaesNone, lanceTypeError. - Si
ruta_csvocolumnano sonstr, lanceTypeError. - Si el archivo no existe, lance
FileNotFoundError. - Lea el CSV y calcule métricas para la columna indicada:
count: número de filas válidas (con valor numérico convertible)sum: suma de los valores válidosavg: promedio (sicountes 0,avgdebe ser 0)
- Debe ignorar filas donde:
- falte la columna,
- el valor esté vacío,
- el valor no se pueda convertir a
float.
Debe devolver un diccionario con esta forma:
{"count": int, "sum": float, "avg": float}
CSV de ejemplo
ventas.csv
producto,importe
A,10
B,20.5
C,abc
D,
E,5
metricas_csv("ventas.csv", "importe") debería devolver:
{"count": 3, "sum": 35.5, "avg": 11.833333333333334}
Pistas
- Usa
csv.DictReaderpara leer filas como diccionarios. - Convierte a número con
float(valor). - Maneja conversiones fallidas con
try/except ValueError. - Asegura casos borde: archivo vacío, columna inexistente,
count == 0.
Solución explicada (paso a paso)
- Validamos tipos (
ruta_csvycolumnadeben serstr). - Abrimos el archivo en modo texto con
newline="". - Usamos
csv.DictReaderpara obtener cada fila como diccionario. - Para cada fila:
- obtenemos el valor de la columna,
- verificamos que exista y no esté vacío,
- intentamos convertirlo a
float, - si se puede, lo acumulamos en suma y aumentamos
count.
- Calculamos promedio:
- si
countes 0 →avg = 0 - si no →
avg = total / count
- si
- Devolvemos el diccionario con métricas.
import csvdef metricas_csv(ruta_csv: str, columna: str) -> dict: """ Lee un CSV con cabecera y calcula métricas básicas (count, sum, avg) de una columna numérica, ignorando filas inválidas. Reglas: - ruta_csv y columna deben ser str - Si archivo no existe -> FileNotFoundError - Ignora valores vacíos o no numéricos """ if ruta_csv is None or columna is None: raise TypeError("Los parámetros 'ruta_csv' y 'columna' no pueden ser None.") if not isinstance(ruta_csv, str) or not isinstance(columna, str): raise TypeError("Los parámetros 'ruta_csv' y 'columna' deben ser cadenas (str).") total = 0.0 count = 0 with open(ruta_csv, mode="r", encoding="utf-8", newline="") as f: reader = csv.DictReader(f) for fila in reader: if not isinstance(fila, dict): continue if columna not in fila: continue valor_raw = fila.get(columna) if valor_raw is None: continue valor_raw = valor_raw.strip() if valor_raw == "": continue try: valor = float(valor_raw) except ValueError: continue total += valor count += 1 avg = total / count if count > 0 else 0.0 return {"count": count, "sum": total, "avg": avg}
import pytestfrom reto_15_metricas_csv import metricas_csvdef test_metricas_csv_con_datos(tmp_path): contenido = ( "producto,importe\n" "A,10\n" "B,20.5\n" "C,abc\n" "D,\n" "E,5\n" ) archivo = tmp_path / "ventas.csv" archivo.write_text(contenido, encoding="utf-8") resultado = metricas_csv(str(archivo), "importe") assert resultado["count"] == 3 assert resultado["sum"] == 35.5 assert resultado["avg"] == 35.5 / 3def test_archivo_no_existe(): with pytest.raises(FileNotFoundError): metricas_csv("no_existe.csv", "importe")def test_columna_no_existe(tmp_path): contenido = "a,b\n1,2\n3,4\n" archivo = tmp_path / "datos.csv" archivo.write_text(contenido, encoding="utf-8") resultado = metricas_csv(str(archivo), "importe") assert resultado == {"count": 0, "sum": 0.0, "avg": 0.0}def test_none_lanza_typeerror(): with pytest.raises(TypeError): metricas_csv(None, "importe")def test_tipo_incorrecto_lanza_typeerror(): with pytest.raises(TypeError): metricas_csv(123, "importe")
Ejecuta:
pytest -q
Variantes para subir de nivel (opcional)
- Devolver también min/max de la columna
- Permitir separador configurable (
,vs;) - Soportar decimales con coma (locale)
- Calcular métricas por grupo (por ejemplo, por
productoocategoria) - Validar que el CSV tenga cabecera (y manejar caso contrario)
Lo que aprendiste
- Lectura de CSV con
csv.DictReader - Manejo de datos “sucios” y filas inválidas
- Agregaciones básicas (conteo, suma, promedio)
- Tests con archivos temporales (
tmp_path)
Accede al código completo y a los tests en GitHub para ejecutar y modificar la solución localmente.
Continúa con Reto #16 — Merge de dos listas ordenadas para practicar lógica de “dos punteros”, muy útil en algoritmos y rendimiento.