Python Data Science: los 9 pasos clave para pasar de cero a operativo
Python Data Science: lo esencial en un artículo — código real, diagramas y pasos concretos, extractos de un curso de 36 lecciones.
Todo el mundo puede aprender Python Data Science — siempre que siga los pasos en el orden correcto. Hemos condensado un curso completo de 36 lecciones en un recorrido claro, con los extractos de código más útiles.
- Introducción e Instalación
- NumPy esencial
- Pandas Series y DataFrames
- Lectura y escritura de datos
- Limpieza de datos
Duplicados e inconsistencias
Objetivos pedagógicos
Por qué los duplicados son peligrosos
Un duplicado es una misma realidad contada dos veces. Consecuencias: cifras de negocio infladas, medias sesgadas, modelos sobreentrenados. Un duplicado puede provenir de una importación repetida, una unión mal hecha, un formulario enviado dos veces…
Detectar los duplicados
import pandas as pd df = pd.DataFrame({ "id": [1, 2, 3, 2, 4, 1], "nom": ["Alice", "Bob", "Chloe", "Bob", "David", "Alice"], "ville": ["Paris", "Lyon", "Nice", "Lyon", "Lille", "Paris"] }) # Máscara booleana: True si la fila es un duplicado df.duplicated() # ¿Cuántos duplicados en total? df.duplicated().sum() # Mostrar solo los duplicados df[df.duplicated()] # Duplicados basándose únicamente en ciertas columnas df.duplicated(subset=["id"]) df.duplicated(subset=["nom", "ville"])
duplicated() marca True a partir de la 2.ª ocurrencia. El primer ejemplar se considera el original.Eliminar los duplicados
# Conservar la primera ocurrencia (por defecto) df.drop_duplicates() # Conservar la última ocurrencia df.drop_duplicates(keep="last") # Eliminar todas las ocurrencias duplicadas df.drop_duplicates(keep=False) # Solo en ciertas columnas (útil si una columna es una marca temporal) df.drop_duplicates(subset=["id"]) # Inplace para modificar el DataFrame directamente df.drop_duplicates(inplace=True)
keep — "first" para conservar el registro más antiguo, "last" para el más reciente (a menudo la mejor opción si los datos se han actualizado), False para eliminar todo e investigar.La trampa de los duplicados aproximados
« París » y « parís » son diferentes para Pandas, aunque para ti sean la misma ciudad. Antes de buscar duplicados, normaliza.
df = pd.DataFrame({
"email": ["Alice@MAIL.com", " alice@mail.com ", "bob@mail.com"],
"ville": ["Paris", "PARIS", "Lyon"]
})
# Normalizar los emails (espacios + minúsculas)
df["email"] = df["email"].str.strip().str.lower()
df["ville"] = df["ville"].str.strip().str.title()
# AHORA podemos detectar los duplicados reales
df.duplicated(subset=["email"])
print(df)Caja de herramientas .str
El accesor .str da acceso a todos los métodos de cadenas de Python, vectorizados:
s = pd.Series([" Alice DUPONT ", "bob martin", "CHLOE.LEROY"]) s.str.strip() # elimina espacios s.str.lower() # minúsculas s.str.upper() # MAYÚSCULAS s.str.title() # Primera Letra De Cada Palabra En Mayúscula s.str.replace(".", " ") # reemplazo s.str.contains("alice", case=False) # filtro s.str.startswith("A") s.str.len() # número de caracteres s.str.split(" ", expand=True) # separar en columnas
unicodedata o la biblioteca unidecode: df["ville"].apply(unidecode).Inconsistencias categóricas
A menudo se introduce la misma categoría de formas distintas: « H », « Hombre », « M », « Masculino »… Pandas ve 4 categorías distintas.
df = pd.DataFrame({
"sexe": ["H", "Homme", "M", "Masculin", "F", "Femme", "f"]
})
# Antes: ver los valores únicos
print(df["sexe"].value_counts())
# Estrategia: diccionario de mapeo
mapping = {
"H": "Homme", "M": "Homme", "Masculin": "Homme",
"F": "Femme", "f": "Femme", "Femme": "Femme"
}
df["sexe"] = df["sexe"].map(mapping)
print(df["sexe"].value_counts())df["col"].value_counts(). Las inconsistencias saltan inmediatamente a la vista.Mini-proyecto: limpiar una base de empleados
import pandas as pd df = pd.DataFrame({ "id": [1, 2, 3, 2, 4, 5], "nom": ["Alice", " Bob ", "Chloe", "BOB", "David", "emma"], "poste": ["Dev", "DEV", "PM", "dev", "Pm", "Designer"], "ville": ["Paris", "paris", "Lyon", "PARIS", "Lille", "Lyon"] }) # 1. Normalizar las cadenas for col in ["nom", "poste", "ville"]: df[col] = df[col].str.strip().str.title() # 2. Detectar duplicados por id print("Duplicados :", df.duplicated(subset=["id"]).sum()) # 3. Conservar el último registro (el más actualizado) df = df.drop_duplicates(subset=["id"], keep="last") # 4. Verificar print(df) print(df["poste"].value_counts())
Valores ausentes (NaN)
Objetivos pedagógicos
¿Qué es un NaN?
NaN = « Not a Number ». Es la forma en que Pandas (heredado de NumPy) representa un valor ausente en una columna numérica. Para fechas es NaT; para objetos genéricos, None. Pandas los trata a los tres de forma unificada mediante isnull().
import pandas as pd import numpy as np df = pd.DataFrame({ "nom": ["Alice", "Bob", None, "David"], "age": [30, np.nan, 25, 35], "date": pd.to_datetime(["2025-01-01", None, "2025-01-03", "2025-01-04"]) }) print(df)
NaN != NaN en Python puro (es intencional). Por tanto x == np.nan es siempre falso. Usa siempre isnull() o isna() para comprobarlo.Detectar los valores ausentes
# Máscara booleana celda por celda df.isnull() # True si es NaN df.notna() # inverso # Contar por columna df.isnull().sum() # Porcentaje de NaN por columna (df.isnull().sum() / len(df) * 100).round(2) # Total en todo el DataFrame df.isnull().sum().sum() # Filas que tienen al menos un NaN df[df.isnull().any(axis=1)]
df.isnull().sum(). En 2 segundos tendrás tu panel de control de la calidad de los datos.Estrategia 1 — Eliminar (dropna)
Cuando los NaN son raros o el análisis exige datos completos:
# Eliminar cualquier fila que contenga al menos 1 NaN df.dropna() # Eliminar solo si TODOS los valores son NaN df.dropna(how="all") # Eliminar si hay NaN en una columna concreta df.dropna(subset=["age"]) # Conservar una fila si tiene al menos 3 valores no-NaN df.dropna(thresh=3) # Eliminar una columna entera demasiado incompleta df.dropna(axis=1, thresh=100) # columnas < 100 valores eliminadas
df.dropna().shape vs df.shape.Estrategia 2 — Rellenar (fillna)
Con un valor fijo
df["age"].fillna(0) df["ville"].fillna("Desconocido") # En todo el DataFrame con un dict por columna df.fillna({"age": 0, "ville": "Desconocido", "salaire": 2000})
Con una estadística de la columna
df["age"].fillna(df["age"].mean()) # media df["age"].fillna(df["age"].median()) # mediana (robusta) df["ville"].fillna(df["ville"].mode()[0]) # valor más frecuente
Por propagación (útil para series temporales)
# Rellenar con el valor anterior (forward fill) df["prix"].ffill() # Con el valor siguiente (backward fill) df["prix"].bfill() # Limitar el número de rellenos consecutivos df["prix"].ffill(limit=3)
Tipos de datos y conversión
Objetivos pedagógicos
dtypes, convertir a número/fecha/categoría, usar errors="coerce" para valores sucios y reducir la huella de memoria de un DataFrame pasando de object a category.Por qué importa el tipo
Un « 42 » almacenado como texto (object) no se puede sumar ni comparar numéricamente. Una fecha almacenada como texto no permite .dt.year. Mal tipo = errores silenciosos o pérdida de funcionalidades.
import pandas as pd df = pd.DataFrame({ "prix": ["12.50", "8.99", "15.00"], "date": ["2025-01-01", "2025-02-15", "2025-03-10"] }) print(df.dtypes) # prix object <-- ¡texto! # date object <-- ¡texto! df["prix"].sum() # concatena « 12.508.9915.00 »!
object = « cualquier cosa », la mayoría de las veces texto. Si ves object en una columna que debería ser numérica, hay un problema que resolver antes de cualquier análisis.Los tipos Pandas en resumen
| Dtype | Uso | Ejemplo |
|---|---|---|
int64 | Entero | Edades, cantidades |
float64 | Real | Precios, temperaturas |
bool | Verdadero/Falso | Activo, de pago |
object | Genérico (a menudo str) | Nombres, emails |
datetime64[ns] | Fecha/hora | Pedido, nacimiento |
timedelta64 | Duración | Diferencia entre 2 fechas |
category | Categorías repetidas | Sexo, ciudad, estado |
Conversión con astype
# Conversión simple, falla si un valor no es adecuado df["age"] = df["age"].astype(int) df["prix"] = df["prix"].astype(float) df["actif"] = df["actif"].astype(bool) df["sexe"] = df["sexe"].astype("category") # En varias columnas a la vez df = df.astype({"age": int, "prix": float, "ville": "category"})
astype(int) falla en cuanto un valor no se convierte (ej.: "12 EUR"). Para gestionar estos casos, usa pd.to_numeric.pd.to_numeric — el tolerante
s = pd.Series(["12.5", "8.99", "oops", "15"]) # Modo estricto: falla si un valor no es numérico pd.to_numeric(s) # ValueError # Modo tolerante: valores sucios -> NaN pd.to_numeric(s, errors="coerce") # 0 12.50 # 1 8.99 # 2 NaN <-- "oops" se convierte en NaN # 3 15.00 # Elegir el subtipo para ahorrar memoria pd.to_numeric(s, errors="coerce", downcast="float") pd.to_numeric(s, errors="coerce", downcast="integer")
errors="coerce" → cuenta los NaN generados → decide qué hacer (imputar, eliminar, avisar).pd.to_datetime — todas las fechas
# Reconoce la mayoría de formatos automáticamente pd.to_datetime(["2025-01-15", "15/01/2025", "Jan 15, 2025"]) # Formato explícito (más rápido, más seguro) pd.to_datetime(df["date"], format="%%d/%%m/%%Y") # Tolerante a valores sucios pd.to_datetime(df["date"], errors="coerce") # Una vez en datetime, se accede a los componentes df["date"] = pd.to_datetime(df["date"]) df["annee"] = df["date"].dt.year df["mois"] = df["date"].dt.month df["jour_sem"] = df["date"].dt.day_name()
format= puede multiplicar la velocidad por 100. Pandas ya no tiene que adivinar.Tipo category: la magia de la memoria
Para una columna « ciudad » repetida 1 millón de veces, almacenar cada cadena es un desperdicio de espacio. category almacena cada valor único una sola vez y referencia un índice entero.
import pandas as pd df = pd.DataFrame({ "ville": ["Paris", "Lyon", "Paris"] * 1_000_000 }) print(df.memory_usage(deep=True).sum() / 1e6, "Mo") # Aproximadamente 200 Mo df["ville"] = df["ville"].astype("category") print(df.memory_usage(deep=True).sum() / 1e6, "Mo") # Aproximadamente 8 Mo -- ¡ganancia x25!
category? — Cuando el número de valores únicos es pequeño en comparación con el número total de filas (normalmente < 5 %%). De lo contrario, poca ganancia.Mini-proyecto: limpiar una exportación contable
import pandas as pd df = pd.DataFrame({ "montant": ["125,50", "99,00", "N/D", "42,75"], "date": ["01/03/2025", "02/03/2025", "oops", "04/03/2025"], "categorie": ["Achat", "Vente", "Achat", "Vente"], "paye": ["Oui", "Non", "Oui", "Oui"] }) # 1. Importe: coma -> punto, luego numérico df["montant"] = df["montant"].str.replace(","span>, ".") df["montant"] = pd.to_numeric(df["montant"], errors="coerce") # 2. Fecha en formato dd/mm/aaaa df["date"] = pd.to_datetime(df["date"], format="%%d/%%m/%%Y", errors="coerce") # 3. Categoría -> tipo category df["categorie"] = df["categorie"].astype("category") # 4. Sí/No -> bool df["paye"] = df["paye"].map({"Oui": True, "Non": False}) print(df.dtypes) print(df)
Este artículo cubre los extractos más útiles: el curso completo Python Data Science (11 capítulos, 36 lecciones, ejercicios corregidos y proyecto final) te lleva hasta el final.
./acceder-al-curso-completo curso gratuito: Dominar Claude CodeFAQ
¿Cuánto tiempo se tarda en aprender Python Data Science?
¿Se necesitan requisitos previos?
¿Por dónde empezar concretamente?
📬 ¿Quieres recibir este tipo de guía cada semana? Suscríbete gratis — código real, cero palabrería.