Saltar al contenido

# manejo-de-datos-en-pandas

Manejo de datos en Pandas

Volver a la portada del curso
Lectura (Markdown)

Preparación de datos para proyecto

Sección 6 · Proyecto Final

Preparación de datos para el proyecto

En las lecciones anteriores cada paso de limpieza se hizo en su propia celda: quitar duplicados, normalizar ciudades, convertir precios y fechas, rellenar faltantes. Funciona, pero tiene un problema: si mañana llega el archivo de otra sucursal, hay que volver a ejecutar quince celdas en el orden correcto. Para el proyecto final la limpieza se empaqueta en una función reutilizable, escrita como una cadena de métodos y vigilada con assert. Esta lectura arma esa función paso a paso; el notebook analisis-pedidos la aplica completa.

1. El checklist de limpieza

Antes de escribir código conviene listar las decisiones. Para el CSV de pedidos de PyShop son estas:

ProblemaDecisiónHerramienta
filas repetidasquitarlasdrop_duplicates()
16 formas de escribir 5 ciudadesnormalizar.str.strip().str.lower() + map
precio como texto con «$» y comasconvertir a número.str.replace + pd.to_numeric
fechas imposiblesconvertir y descartar la filapd.to_datetime(errors="coerce") + dropna(subset=...)
precio vacíomediana del mismo productogroupby(...).transform("median")
cantidad vacíamedianafillna
calificación vacíadejarla vacíanada: es un faltante estructural

La última fila es tan importante como las otras: los pedidos no entregados no se califican, y rellenar ese vacío inventaría opiniones.

2. Encadenamiento de métodos

Casi todos los métodos de pandas devuelven un DataFrame nuevo, así que se pueden encadenar: el resultado de uno es la entrada del siguiente. Entre paréntesis, la cadena se escribe un método por línea y se lee de arriba abajo como una receta.

La pieza clave es assign, que crea o reemplaza columnas y devuelve el DataFrame resultante. Si el valor es una lambda, pandas la llama con el DataFrame en ese punto de la cadena. Por eso lambda d: d["ciudad"]... trabaja sobre los datos ya sin duplicados, aunque el nombre original df nunca exista.

def a_numero(precio):
    sin_simbolos = precio.str.replace(r"[$,]", "", regex=True)
    return pd.to_numeric(sin_simbolos)


def limpiar(texto):
    return (
        pd.read_csv(io.StringIO(texto))
        .drop_duplicates()
        .assign(
            ciudad=lambda d: d["ciudad"].str.strip()
                .str.lower().map(MAPA_CIUDADES),
            precio_unitario=lambda d: a_numero(d["precio_unitario"]),
            fecha=lambda d: pd.to_datetime(d["fecha"], errors="coerce"),
        )
        .assign(
            precio_unitario=lambda d: d["precio_unitario"].fillna(
                d.groupby("producto")["precio_unitario"]
                .transform("median")),
            cantidad=lambda d: d["cantidad"].fillna(d["cantidad"].median()),
        )
        .dropna(subset=["fecha"])
        .assign(total=lambda d: d["cantidad"] * d["precio_unitario"])
    )

El orden importa. Hay dos assign porque la mediana del precio solo se puede calcular cuando el precio ya es número. El total va al final, cuando cantidad y precio ya no tienen huecos. Con el CSV del curso, limpiar devuelve un DataFrame de 991 filas y 11 columnas, y la única columna con nulos es la calificación (369).

3. Validar con assert

Una función de limpieza hace supuestos: que solo hay diez variantes de ciudad, que el precio solo trae «$» y comas. Si el archivo del próximo año rompe un supuesto, conviene que el error aparezca en la limpieza y no en una gráfica equivocada. assert condicion, "mensaje" detiene la ejecución si la condición es falsa.

def validar(df):
    assert df["pedido_id"].is_unique, "hay pedidos repetidos"
    assert df["ciudad"].notna().all(), "hay ciudades sin mapear"
    assert df["ciudad"].nunique() == 5
    columnas = ["cantidad", "precio_unitario", "fecha", "total"]
    assert df[columnas].notna().all().all()
    return True

El segundo assert vigila al map: una ciudad que no está en el diccionario se convierte en NaN sin avisar.

4. Guardar el resultado

to_csv("pedidos_limpios.csv", index=False) guarda el archivo sin la columna del índice. Al volver a leerlo, la fecha llega como texto a menos que se indique parse_dates=["fecha"]. Una buena práctica es releer el archivo y comprobar que la forma y el ingreso coinciden: con el CSV del curso, 991 filas y un ingreso sin cancelados de 1,350,460 MXN.

Trampas comunes

  • Usar df dentro de la cadena. assign(x=df["a"] * 2) toma el DataFrame original, con duplicados; usa lambda d: d["a"] * 2.
  • Rellenar antes de convertir. La mediana de una columna de texto no existe.
  • Calcular el total antes de rellenar. Cada NaN se propaga al total.
  • Cadenas de veinte métodos. Si un paso necesita explicación, sácalo a una función con nombre, como a_numero.
  • Validar a ojo. «Se ve bien» no detecta una ciudad nueva; un assert sí.

Antes de empezar el análisis del proyecto, ten listas tres funciones: una que mida la calidad, limpiar y validar. El resto del trabajo se construye sobre ellas.