# Python para Data Science: NumPy, Pandas y Scikit-Learn en 2026 > Guia practica de NumPy, Pandas y Scikit-Learn para construir pipelines de machine learning en Python. Incluye ejemplos reales de limpieza de datos, ingenieria de variables y clasificacion con GradientBoosting. - Published: 2026-04-07 - Updated: 2026-04-07 - Author: Anthony Fillion-Maillet - Tags: data-science, python, numpy, pandas, scikit-learn, machine-learning, tutorial - Reading time: 10 min --- El ecosistema cientifico de Python se consolido en 2026 como la columna vertebral de los proyectos de datos en organizaciones de todos los tamanos. NumPy, Pandas y Scikit-Learn forman una triada que cubre desde la manipulacion bruta de arrays hasta el despliegue de modelos de clasificacion con validacion cruzada. Entender como estas tres bibliotecas se articulan entre si no solo acelera el desarrollo, sino que tambien es una exigencia habitual en las [preguntas de entrevista en data science](/technologies/data-science). > **Versiones de referencia para este articulo** > > Los ejemplos de codigo fueron validados con Python 3.12, NumPy 2.1, Pandas 2.2 y Scikit-Learn 1.5. Si se trabaja en un entorno virtual, se recomienda fijar estas versiones en `requirements.txt` para garantizar reproducibilidad. ## NumPy: la base numerica del stack cientifico NumPy proporciona el tipo `ndarray`, un arreglo n-dimensional de tipo homogeneo almacenado en memoria contigua. Esta decision de diseno permite que las operaciones se ejecuten en codigo C compilado en lugar de en el interprete de Python, lo cual resulta en una diferencia de velocidad de uno a dos ordenes de magnitud respecto a las listas nativas. La ventaja practica mas visible es la **aritmetica vectorizada**: en lugar de escribir un bucle `for` para multiplicar dos colecciones de numeros, NumPy aplica la operacion elemento a elemento en una sola instruccion. El siguiente ejemplo ilustra este patron con datos de ventas: ```python # numpy_basics.py import numpy as np # Create arrays from different sources prices = np.array([29.99, 49.99, 19.99, 99.99, 39.99]) quantities = np.arange(1, 6) # [1, 2, 3, 4, 5] # Vectorized arithmetic — no loops needed revenue = prices * quantities print(revenue) # [29.99, 99.98, 59.97, 399.96, 199.95] # Statistical aggregations print(f"Total revenue: ${revenue.sum():.2f}") # $789.85 print(f"Mean price: ${prices.mean():.2f}") # $47.99 print(f"Std deviation: ${prices.std():.2f}") # $27.64 # Boolean indexing — filter without loops premium_mask = prices > 40 premium_items = prices[premium_mask] # [49.99, 99.99] ``` El **boolean indexing** mostrado al final del fragmento es una tecnica fundamental: genera una mascara booleana a partir de una condicion y la usa directamente como indice del array. No hay necesidad de `filter()` ni de comprensiones de lista; el codigo es mas legible y significativamente mas rapido con datasets de gran tamano. ## Operaciones multidimensionales y broadcasting La verdadera potencia de NumPy emerge cuando se trabaja con datos tabulares representados como matrices bidimensionales. El parametro `axis` controla la direccion de agregacion: `axis=0` opera sobre las filas (resultado por columna) y `axis=1` opera sobre las columnas (resultado por fila). ```python # numpy_reshape.py import numpy as np # Monthly sales data: 4 products x 3 months sales = np.array([ [120, 150, 130], # Product A [200, 180, 220], # Product B [90, 110, 95], # Product C [300, 280, 310], # Product D ]) # Column-wise mean (average per month) monthly_avg = sales.mean(axis=0) # [177.5, 180.0, 188.75] # Row-wise sum (total per product) product_totals = sales.sum(axis=1) # [400, 600, 295, 890] # Normalize each product relative to its own max normalized = sales / sales.max(axis=1, keepdims=True) # keepdims=True preserves the shape for broadcasting print(normalized[0]) # [0.8, 1.0, 0.867] — Product A relative to its peak # Reshape for Scikit-Learn (requires 2D input) flat_sales = sales.flatten() # 1D array of 12 values reshaped = flat_sales.reshape(-1, 1) # 12x1 column vector ``` El uso de `keepdims=True` es un detalle que marca la diferencia en entrevistas tecnicas: sin el, `sales.max(axis=1)` devuelve un array unidimensional de forma `(4,)`, y la division con la matriz original falla por incompatibilidad de dimensiones. Con `keepdims=True` el resultado tiene forma `(4, 1)`, lo que activa el broadcasting automatico de NumPy. El metodo `reshape(-1, 1)` al final del fragmento anticipa un patron que se repetira constantemente al trabajar con Scikit-Learn: la mayoria de los estimadores esperan datos de entrada en formato bidimensional `(n_samples, n_features)`, por lo que transformar arrays unidimensionales a columnas vectoriales es una operacion rutinaria. ## Pandas: limpieza y transformacion de datos reales Pandas introduce el `DataFrame`, una estructura tabular con indices etiquetados que funciona como una hoja de calculo programable. Donde NumPy asume datos homogeneos y numericos, Pandas maneja columnas de tipos mixtos, fechas, cadenas de texto y valores ausentes, lo cual lo convierte en la herramienta predilecta para la fase de preparacion de datos. Un conjunto de datos real llega invariablemente con problemas: filas duplicadas, valores faltantes en columnas criticas, tipos de dato incorrectos y outliers que distorsionaran cualquier modelo posterior. La practica recomendada es encadenar transformaciones en un pipeline declarativo usando la API de metodos de Pandas: ```python # pandas_cleaning.py import pandas as pd import numpy as np # Load and inspect raw data df = pd.read_csv("candidates.csv") print(df.shape) # (1500, 8) print(df.dtypes) # Check column types print(df.isna().sum()) # Count missing values per column # Clean in a reproducible chain df_clean = ( df .dropna(subset=["salary", "experience_years"]) # Drop rows missing critical fields .assign( salary=lambda x: x["salary"].clip(lower=20000, upper=500000), # Cap outliers experience_years=lambda x: x["experience_years"].astype(int), hired_date=lambda x: pd.to_datetime(x["hired_date"], errors="coerce"), ) .drop_duplicates(subset=["email"]) # Remove duplicate candidates .query("experience_years >= 0") # Filter invalid entries .reset_index(drop=True) ) print(f"Cleaned: {len(df)} -> {len(df_clean)} rows") ``` Esta cadena de metodos tiene una ventaja sobre el enfoque imperativo de modificar el `DataFrame` paso a paso: es completamente reproducible, facil de auditar y genera un nuevo objeto en lugar de mutar el original. El uso de `lambda` dentro de `.assign()` permite referenciar el estado intermedio del `DataFrame` en ese punto de la cadena, lo que posibilita transformaciones que dependen de otras columnas. > **clip() como alternativa a eliminar outliers** > > El metodo `clip()` recorta los valores fuera del rango especificado al minimo o maximo permitido, en lugar de eliminar las filas. Esto preserva el tamano del dataset a costa de introducir una distribucion truncada. La eleccion entre `clip()` y la eliminacion depende del contexto del negocio y del tipo de modelo que se vaya a entrenar. ## Ingenieria de variables con groupby y transform Una vez limpio el dataset, el siguiente paso es construir variables (features) que capturen patrones relevantes para el modelo. Las operaciones de agregacion por grupo son especialmente productivas: permiten crear metricas contextuales que un modelo de ML aprovecha mucho mejor que los valores crudos. Este proceso es central en las [tareas de aprendizaje supervisado](/technologies/data-science/interview-questions/ml-supervised-classification). ```python # pandas_groupby.py import pandas as pd # Aggregate candidate stats by department dept_stats = ( df_clean .groupby("department") .agg( avg_salary=("salary", "mean"), median_experience=("experience_years", "median"), headcount=("email", "count"), max_salary=("salary", "max"), ) .sort_values("avg_salary", ascending=False) ) print(dept_stats.head()) # Create features for ML: encode categorical + add aggregated stats df_features = ( df_clean .assign( # Ratio of individual salary to department average salary_ratio=lambda x: x["salary"] / x.groupby("department")["salary"].transform("mean"), # Time since hire in days tenure_days=lambda x: (pd.Timestamp.now() - x["hired_date"]).dt.days, # Binary encoding is_senior=lambda x: (x["experience_years"] >= 5).astype(int), ) ) ``` La funcion `.transform()` es la clave en la linea del `salary_ratio`: a diferencia de `.agg()`, que reduce el grupo a una sola fila, `.transform()` devuelve un array del mismo tamano que el grupo original, lo que permite asignar el resultado directamente como una nueva columna en el `DataFrame` completo. Este patron evita la necesidad de un `merge` adicional y es uno de los conceptos mas frecuentes en las [preguntas de entrevista sobre feature engineering](/technologies/data-science/interview-questions/feature-engineering). ## Scikit-Learn: pipelines reproducibles de principio a fin Scikit-Learn es la biblioteca de machine learning de referencia en Python. Su diseno gira en torno a la API `fit/transform/predict`, que unifica la interfaz de preprocesadores, transformadores y estimadores. La clase `Pipeline` encadena estos componentes de forma que el modelo entrenado incluye todas las etapas de transformacion, eliminando la posibilidad de que el preprocesamiento del conjunto de entrenamiento se aplique de forma diferente al conjunto de prueba. El siguiente ejemplo construye un pipeline completo para predecir si un empleado sera promovido, partiendo del `DataFrame` de features generado con Pandas: ```python # sklearn_pipeline.py from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import classification_report import pandas as pd # Define column groups numeric_features = ["salary", "experience_years", "salary_ratio", "tenure_days"] categorical_features = ["department", "role_level"] target = "promoted" # Split before any preprocessing X = df_features[numeric_features + categorical_features] y = df_features[target] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # Build the preprocessing + model pipeline preprocessor = ColumnTransformer( transformers=[ ("num", StandardScaler(), numeric_features), # Scale numeric columns ("cat", OneHotEncoder(handle_unknown="ignore"), categorical_features), # Encode categories ] ) pipeline = Pipeline([ ("preprocessor", preprocessor), ("classifier", GradientBoostingClassifier( n_estimators=200, learning_rate=0.1, max_depth=4, random_state=42, )), ]) # Train and evaluate pipeline.fit(X_train, y_train) y_pred = pipeline.predict(X_test) print(classification_report(y_test, y_pred)) ``` El uso de `stratify=y` en `train_test_split` garantiza que la proporcion de clases en entrenamiento y prueba sea la misma que en el dataset original, lo cual es especialmente importante cuando las clases estan desbalanceadas. `ColumnTransformer` permite aplicar transformaciones distintas a columnas numericas y categoricas en un solo paso, manteniendo la coherencia entre entrenamiento y produccion. > **Nunca preprocesar antes de dividir el dataset** > > Aplicar `StandardScaler` o cualquier otra transformacion al dataset completo antes de dividirlo en entrenamiento y prueba introduce **data leakage**: el escalador aprende estadisticas del conjunto de prueba, lo que hace que las metricas de evaluacion sean optimistas y el modelo se comporte peor en produccion. El `Pipeline` de Scikit-Learn previene este error al ajustar los transformadores unicamente sobre los datos de entrenamiento durante el `.fit()`. ## Validacion cruzada y busqueda de hiperparametros Entrenar un modelo una sola vez con una particion aleatoria no es suficiente para estimar su rendimiento real. La validacion cruzada k-fold evalua el modelo en k subconjuntos distintos del conjunto de entrenamiento, proporcionando una estimacion mas robusta de la generalizacion. Combinada con `GridSearchCV`, permite explorar el espacio de hiperparametros de forma sistematica: ```python # sklearn_tuning.py from sklearn.model_selection import cross_val_score, GridSearchCV import numpy as np # 5-fold cross-validation on the full pipeline scores = cross_val_score(pipeline, X_train, y_train, cv=5, scoring="f1") print(f"F1 scores: {scores}") print(f"Mean F1: {scores.mean():.3f} (+/- {scores.std() * 2:.3f})") # Grid search over hyperparameters param_grid = { "classifier__n_estimators": [100, 200, 300], "classifier__max_depth": [3, 4, 5], "classifier__learning_rate": [0.05, 0.1, 0.2], } grid_search = GridSearchCV( pipeline, param_grid, cv=5, scoring="f1", n_jobs=-1, # Use all CPU cores verbose=1, ) grid_search.fit(X_train, y_train) print(f"Best params: {grid_search.best_params_}") print(f"Best F1: {grid_search.best_score_:.3f}") # Evaluate the best model on held-out test set best_model = grid_search.best_estimator_ print(classification_report(y_test, best_model.predict(X_test))) ``` La notacion `classifier__n_estimators` en `param_grid` es la forma en que Scikit-Learn referencia los parametros de un paso especifico dentro de un `Pipeline`: el nombre del paso seguido de doble guion bajo y el nombre del parametro. El argumento `n_jobs=-1` indica que se deben usar todos los nucleos de CPU disponibles para ejecutar los experimentos en paralelo, lo cual reduce considerablemente el tiempo de busqueda en grids grandes. Comprender estas mecanicas es esencial para responder las [preguntas de entrevista sobre machine learning](/blog/data-science/machine-learning-algorithms-explained) con precision. ## Persistencia del modelo con joblib Un modelo entrenado no tiene valor practico si no puede ser serializado, transportado y cargado en un proceso distinto. Scikit-Learn recomienda `joblib` en lugar de `pickle` para este proposito porque `joblib` maneja eficientemente los arrays de NumPy, que constituyen la mayor parte del estado interno de los modelos: ```python # sklearn_export.py import joblib from pathlib import Path # Save the complete pipeline (preprocessor + model) model_dir = Path("models") model_dir.mkdir(exist_ok=True) joblib.dump(best_model, model_dir / "promotion_model_v1.joblib") # Load and predict in a different process loaded_model = joblib.load(model_dir / "promotion_model_v1.joblib") new_data = pd.DataFrame({ "salary": [75000], "experience_years": [4], "salary_ratio": [1.05], "tenure_days": [730], "department": ["Engineering"], "role_level": ["Mid"], }) prediction = loaded_model.predict(new_data) probability = loaded_model.predict_proba(new_data)[:, 1] print(f"Promoted: {bool(prediction[0])}, Confidence: {probability[0]:.2%}") ``` Guardar el `Pipeline` completo en lugar del estimador aislado es una practica critica: el archivo `.joblib` incluye el `ColumnTransformer` con todas las estadisticas aprendidas durante el entrenamiento (medias, desviaciones estandar, categorias del encoder). Al cargar el modelo en produccion, `loaded_model.predict(new_data)` aplica exactamente el mismo preprocesamiento que se utilizo durante el entrenamiento, sin necesidad de codigo adicional. ## Consideraciones para entornos de produccion en 2026 El workflow descrito en este articulo cubre el ciclo completo de un proyecto de data science de tamano medio. Sin embargo, algunos patrones adicionales son relevantes cuando el modelo pasa de un notebook de exploracion a un servicio activo: - **Versionado de datos**: herramientas como DVC permiten rastrear los datasets junto con el codigo, de modo que cualquier version del modelo puede reproducirse con exactitud. - **Monitoreo de drift**: la distribucion de los datos de entrada en produccion puede desviarse de la del conjunto de entrenamiento con el tiempo. Bibliotecas como `evidently` automatizan la deteccion de este fenomeno. - **Alternativas al GridSearchCV**: para espacios de hiperparametros grandes, `Optuna` o `HalvingGridSearchCV` (incluido en Scikit-Learn desde la version 1.0) ofrecen busquedas mas eficientes mediante estrategias de poda adaptativa. - **Feature stores**: en equipos con multiples proyectos de ML, centralizar las transformaciones de variables en un feature store (Feast, Tecton) evita la duplicacion de logica y garantiza consistencia entre entrenamiento y servicio. - **Tipos estaticos**: desde Pandas 2.0, el uso de `pd.ArrowDtype` y la integracion con PyArrow reduce el consumo de memoria y mejora la interoperabilidad con herramientas como Polars o Apache Arrow. > **Scikit-Learn 1.5: mejoras en la API de pipelines** > > Scikit-Learn 1.5 introdujo mejoras en la representacion HTML de los pipelines (visible en Jupyter con `set_config(display='diagram')`), soporte mejorado para tipos de dato de Pandas en los transformadores y mayor estabilidad en la API de `FeatureUnion`. Vale la pena revisar el changelog antes de migrar proyectos existentes. ## Conclusion NumPy, Pandas y Scikit-Learn comparten una filosofia de diseno coherente que facilita su integracion: todos operan sobre arrays de NumPy como estructura de datos comun, exponen interfaces consistentes y separan claramente las responsabilidades entre la manipulacion de datos y el modelado estadistico. Los puntos clave de este articulo son: - NumPy elimina los bucles explicitos con aritmetica vectorizada y broadcasting, siendo la base sobre la que se construyen las otras dos bibliotecas. - Pandas abstrae las operaciones de limpieza y transformacion en una API declarativa basada en cadenas de metodos; `transform()` es la funcion mas importante para crear variables contextuales sin perder filas. - Scikit-Learn estandariza el ciclo de vida del modelo con la API `fit/transform/predict`; el `Pipeline` con `ColumnTransformer` es el patron correcto para garantizar que el preprocesamiento sea identico en entrenamiento y produccion. - La validacion cruzada y `GridSearchCV` son herramientas inseparables del entrenamiento riguroso; evaluar sobre el conjunto de prueba solo una vez, al final, es la unica forma de obtener metricas honestas. - `joblib.dump()` sobre el pipeline completo es el mecanismo de persistencia recomendado para proyectos en produccion. Dominar esta triada y ser capaz de explicar las decisiones de diseno detras de cada patron es lo que diferencia a un practicante solido de data science en el mercado laboral de 2026. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/es/blog/data-science/python-data-science-numpy-pandas-scikit-learn-2026