Indice / Cierre / 10.1
Recetario consolidado
Una tabla por unidad, sin prosa. Es la página para tener abierta al lado mientras se resuelve un ejercicio, y la que conviene imprimir.
Antes: 5.7 Pipeline reproducible 6.5 pivot_table y reshape 7.4 Escribir el resultado 7.5 El puente con SQL 9.6 La imagen que sale del script
Despues: cierra su rama
U1 · Archivos, excepciones y formatos
| Expresión | Qué hace | Ojo con |
|---|---|---|
with open(r, encoding="utf-8", newline="") as f: | La apertura correcta | Los dos argumentos van siempre |
csv.DictReader(f) | Filas como dict | Todo llega como str |
csv.DictWriter(f, fieldnames=COLS, extrasaction="ignore") | Escribe por nombre | Falta el writeheader() |
json.load(f) / json.dump(o, f, ensure_ascii=False, indent=2) | JSON | Sin ensure_ascii=False quedan los escapes |
except ValueError as error: | Atrapa la esperada | except Exception tapa tus propios bugs |
raise Propia(...) from error | Traduce sin perder la causa | Solo dentro de un except |
NULOS = {"", "-", "N/A", "NaN", "null"} | Los cinco tokens del corpus | float("NaN") no falla |
log.warning("%s de %s", a, b) | Mensaje perezoso | Argumentos por coma, no f-string |
assert len(ok) + len(no) == len(entrada) | Que no se pierda nada | Las dos listas se llenan por separado |
Path(__file__).resolve().parent | La carpeta del programa | Una ruta relativa se resuelve contra el cwd |
U2 · Tablas, claves y calidad
| Expresión | Contesta |
|---|---|
len({f["id"] for f in filas}) == len(filas) | ¿Sirve como clave primaria? |
Counter(f["clave"] for f in filas) | ¿Cuál se repite y cuántas veces? |
{(f["a"], f["b"]) for f in filas} | Los valores de una clave compuesta |
hijos - {p["id"] for p in padres} | Las referencias huérfanas |
{p["id"] for p in padres} - referenciados | Los padres sin hijos - que no es un error |
| Cardinalidad: 1:1 mantiene las filas · 1:N deja el grano del lado N · N:M multiplica | |
Vocabulario: ausente · no_numerico · fuera_de_rango · fecha_invalida · dominio_desconocido · duplicado · huerfano · negativo_incoherente · atipico · restriccion_violada | |
| Capas: 1 lectura · 2 parseo · 3 validación · 4 negocio. Las dos primeras avisan solas; de las otras dos, nadie | |
U3 · NumPy
| Expresión | Qué hace | Ojo con |
|---|---|---|
a.shape / a.dtype | Forma y tipo | Lo primero, siempre |
np.genfromtxt(r, delimiter=",", skip_header=1, usecols=…) | CSV → array | Los huecos llegan como NaN |
a.astype(np.int64) | Copia convertida | Trunca; y un NaN da el entero mínimo |
a.reshape(-1, 1) | Vector a columna | Devuelve una vista |
a.T | Intercambia los ejes | No es lo mismo que reshape |
a.mean(axis=1) | Un promedio por fila | El axis es el que desaparece |
(a > x).sum(axis=1) | Cuenta por fila | Sumar booleanos es contar |
(a > 1) & (a < 5) | Combinar máscaras | Paréntesis obligatorios; and falla |
a[mask] = valor | Reemplaza en el original | sub = a[mask] devuelve una copia |
np.where(cond, si, no) | Reemplaza sin filtrar | Conserva el shape |
np.nanmean(a, axis=1, keepdims=True) | (n, 1) para restar por fila | Sin keepdims, ValueError |
np.nanpercentile(a, [25, 75]) | Los cuartiles | La base del IQR |
np.clip(a, bajo, alto) | Recorta | Inventa valores en el límite |
np.argsort(v) | Las posiciones en orden | otro[np.argsort(v)] reordena otro |
U4 · pandas: fundamentos
| Expresión | Qué hace | Ojo con |
|---|---|---|
pd.read_csv(r) | CSV → DataFrame | - no es un token de ausente |
df.head() / df.shape / df.dtypes / df.info() | Las cuatro primeras miradas | El texto es str, no object |
df["c"] vs df[["a", "b"]] | Series vs DataFrame | Un corchete o dos |
df.loc[filas, columnas] | Por etiqueta | El corte incluye el final |
df.iloc[filas, columnas] | Por posición | El corte excluye el final |
df[(df.a > 1) & (df.b < 2)] | Filtrar | Paréntesis; and falla |
df["c"].isin([...]) / .between(a, b) | Pertenencia y rango | NaN no cumple ninguna |
df["c"].str.contains(x, na=False) | Texto | Sin na=False, el NaN propaga |
df.set_index("id") / .reset_index() | Índice | df.index.is_unique antes de nada |
df.assign(nueva=…) | Columna derivada, encadenable | Devuelve una copia |
df.loc[mask, "col"] = 0 | Asignar filtrado | df[mask]["col"] = 0 no hace nada |
df.sort_values(by, ascending, na_position) | Ordenar | Terminar en una columna única |
U5 · pandas: calidad y limpieza
| Expresión | Qué hace | Ojo con |
|---|---|---|
df.isna().sum() | Ausentes por columna | Depende de na_values |
df.duplicated(subset=).sum() | Duplicados | Definir por qué columnas |
df["c"].value_counts(dropna=False) | El dominio real | dropna=False o los huecos no se ven |
pd.to_numeric(s, errors="coerce") | Texto → número, lo malo a NaN | Contar los NaN nuevos |
pd.to_datetime(s, errors="coerce", dayfirst=) | Texto → fecha | Una fecha imposible da NaT en silencio |
s.str.strip().str.lower() | Normalizar texto | Antes de deduplicar |
read_csv(r, na_values=["-", "N/A"]) | Declarar los tokens | El guion no viene por defecto |
df.fillna(valor) / df.dropna(subset=[...]) | Imputar / descartar | Sin subset, dropna borra de más |
df.drop_duplicates(subset=, keep="last") | Deduplicar | keep="first" conserva la primera del archivo, que en catalogo_sistema_a es la versión vieja |
df["c"].clip(bajo, alto) | Recortar | Inventa valores en el límite |
df.copy().rename(...).assign(...) | Pipeline encadenado | Empezar por .copy() |
df.pipe(funcion) | Un paso con nombre | Funciones puras: reciben y devuelven |
U6 · pandas: combinar y resumir
| Expresión | Qué hace | Ojo con |
|---|---|---|
pd.concat([a, b], ignore_index=True) | Apila filas | Sin ignore_index, el índice se repite |
a.merge(b, on="id", how="inner") | Cruce | inner pierde filas en silencio |
how="left" | Conserva todo lo de la izquierda | Lo que no cruza queda en NaN |
indicator=True | Columna _merge | El anti-join es _merge == "left_only" |
validate="m:1" | MergeError si la clave no es única | Ponerlo siempre |
df.groupby("c").agg(...) | Split-apply-combine | dropna=True borra la categoría nula |
as_index=False | La clave queda como columna | Evita un reset_index() |
df.pivot_table(index, columns, values, aggfunc, fill_value) | Largo → ancho | Sin aggfunc promedia por defecto |
df.melt(id_vars, var_name, value_name) | Ancho → largo | Es el inverso del anterior |
df.sort_values("fecha").groupby("u")["fecha"].diff() | Hueco con el anterior | Ordenar antes, o los huecos son basura |
df.set_index("fecha").resample("D").sum() | Serie diaria | Incluye los días sin datos; groupby no |
(df["hueco"] > umbral).cumsum() | Número de sesión | El corte por hueco, en una línea |
U7 · Entrada y salida
| Expresión | Qué hace | Ojo con |
|---|---|---|
read_csv(r, sep=";", decimal=",") | CSV «europeo» | Sin thousands="." queda como texto |
read_csv(r, usecols=, dtype=, parse_dates=) | Leer solo lo necesario | dtype evita inferencias sorpresa |
read_csv(r, on_bad_lines="warn") | Filas irregulares | El default es fallar |
pd.json_normalize(datos, record_path, meta) | JSON anidado → tabla | Las claves que faltan quedan en NaN |
pd.read_excel(r, sheet_name=None) | Todas las hojas, como dict | Necesita openpyxl |
with pd.ExcelWriter(r) as w: | Varias hojas | df.to_excel(w, sheet_name=…) |
df.to_csv(r, index=False, encoding="utf-8") | Escribir | index=False casi siempre |
df["c"] = df["c"].round(2) | Redondear | Columna por columna, no float_format |
pd.read_sql_query(sql, con, params=(x,)) | SQL → DataFrame | Nunca f-strings en el SQL |
df.to_sql(t, con, if_exists="replace", index=False) | DataFrame → SQL | replace borra la tabla |
Las seis reglas de contrato
| Regla | |
|---|---|
| R1 | Orden total: terminar siempre en una columna única de la salida |
| R2 | Vocabulario cerrado de motivos de rechazo, el mismo en las 39 prácticas |
| R3 | Redondear en el DataFrame, columna por columna, no al escribir |
| R4 | Nada de nan en el archivo: el ausente escrito es campo vacío |
| R5 | El objetivo nunca se escribe a mano: lo produce el código |
| R6 | La definición de correcto es que comparar.py no imprima nada |
Los cinco números de todo informe
- Cuántas filas entraron.
- Cuántas salieron, y qué porcentaje es eso.
- El desglose por motivo, con el vocabulario cerrado.
- Dónde está el detalle: el archivo de rechazos, fila por fila.
- El invariante:
válidas + rechazadas == entrada.