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.

Python Data Science: los 9 pasos clave para pasar de cero a operativo

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.

tl;dr
  • Introducción e Instalación
  • NumPy esencial
  • Pandas Series y DataFrames
  • Lectura y escritura de datos
  • Limpieza de datos
~$ cat ./parcours.md # Python Data Science — 10 capítulos
01
Introducción e Instalación
→ Presentación del curso→ Instalar el entorno Data Science+ 1 más lecciones
02
NumPy esencial
→ Arrays NumPy vs listas Python→ Operaciones vectorizadas+ 1 más lecciones
03
Pandas Series y DataFrames
→ Crear una Series→ Crear un DataFrame+ 2 más lecciones
04
Lectura y escritura de datos
→ CSV con read_csv y to_csv→ Excel, JSON y Parquet+ 1 más lecciones
05
Limpieza de datos
→ Valores faltantes (NaN)→ Duplicados e inconsistencias+ 1 más lecciones
06
Agregaciones y groupby
→ groupby básico→ agg en varias columnas+ 1 más lecciones
07
Uniones y fusiones
→ merge como en SQL→ concat vs merge+ 1 más lecciones
08
Visualizaciones rápidas
→ plot Pandas directo→ Matplotlib para DS+ 1 más lecciones
🏁
Proyecto final (+ 2 capítulos en camino)
→ Te vas con un proyecto concreto y demostrable

Duplicados e inconsistencias

NOTEObjetivo — Aprender a detectar y eliminar duplicados exactos o aproximados, y a uniformizar las cadenas de caracteres (mayúsculas, espacios, acentos) que envenenan silenciosamente tus análisis.

Objetivos pedagógicos

TIPAl finalizar este módulo — Sabrás detectar filas duplicadas, decidir qué copia conservar, normalizar columnas de texto y corregir las famosas inconsistencias del tipo « París », « parís », « PARÍS ».

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…

WARNINGHistoria real — Una empresa calculaba su facturación a partir de un archivo Excel concatenado cada mes… sin eliminar los duplicados. Su facturación estaba sobreestimada en un 12 %% desde hacía dos años.

Detectar los duplicados

output
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"])
NOTEPor defectoduplicated() marca True a partir de la 2.ª ocurrencia. El primer ejemplar se considera el original.

Eliminar los duplicados

output
# 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)
TIPElegir 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.

output
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:

output
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
NOTETruco acentos — Para normalizar los acentos (é → e), usa 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.

output
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())
TIPReflejo de oro — Antes de cualquier análisis categórico, ejecuta df["col"].value_counts(). Las inconsistencias saltan inmediatamente a la vista.

Mini-proyecto: limpiar una base de empleados

output
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)

NOTEObjetivo — Comprender qué es un valor ausente en Pandas, cómo detectarlo, cómo analizarlo y elegir la estrategia correcta: eliminar o imputar (rellenar). Ningún análisis limpio se realiza sin este paso.

Objetivos pedagógicos

TIPAl finalizar este módulo — Sabrás contar los NaN por columna, eliminar las filas incompletas o rellenarlas de forma inteligente (media, mediana, valor fijo, propagación). También sabrás cuándo es apropiada cada estrategia.

¿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().

output
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)
WARNINGTrampa clásicaNaN != 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

output
# 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)]
TIPReflejo de oro — En cuanto llega un dataset, ejecuta 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:

output
# 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
WARNING¿Cuándo evitar dropna? — En un dataset de 100 000 filas donde cada columna tiene algunos %% de NaN, dropna puede eliminar todo. Comprueba antes: df.dropna().shape vs df.shape.

Estrategia 2 — Rellenar (fillna)

Con un valor fijo

output
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

output
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)

output
# 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

NOTEObjetivo — Comprender los tipos de Pandas (int, float, object, datetime, category, bool), por qué importan enormemente y cómo convertir correctamente sin que fallen por valores sucios.

Objetivos pedagógicos

TIPAl finalizar este módulo — Sabrás inspeccionar 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.

output
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 »!
WARNINGTrampa clásicaobject = « 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

DtypeUsoEjemplo
int64EnteroEdades, cantidades
float64RealPrecios, temperaturas
boolVerdadero/FalsoActivo, de pago
objectGenérico (a menudo str)Nombres, emails
datetime64[ns]Fecha/horaPedido, nacimiento
timedelta64DuraciónDiferencia entre 2 fechas
categoryCategorías repetidasSexo, ciudad, estado

Conversión con astype

output
# 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"})
WARNINGLímiteastype(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

output
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")
TIPFlujo de trabajo limpioerrors="coerce" → cuenta los NaN generados → decide qué hacer (imputar, eliminar, avisar).

pd.to_datetime — todas las fechas

output
# 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()
NOTEFormato explícito — En grandes volúmenes, especificar 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.

output
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!
TIP¿Cuándo usar 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

output
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)
va-plus-loin

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 Code

FAQ

¿Cuánto tiempo se tarda en aprender Python Data Science?
Con una progresión estructurada (11 capítulos, 36 lecciones cortas y prácticas), se alcanza un nivel operativo en unas semanas dedicando 30-60 minutos al día. Lo importante es practicar cada concepto de inmediato.
¿Se necesitan requisitos previos?
Con nociones básicas de informática basta. Si sabes usar un terminal y leer código sencillo, estás listo.
¿Por dónde empezar concretamente?
Reproduce los comandos de este artículo y sigue el curso completo Python Data Science: encadena las 36 lecciones en orden, con ejercicios y proyecto final.

📬 ¿Quieres recibir este tipo de guía cada semana? Suscríbete gratis — código real, cero palabrería.