using CSV, DataFrames, DataFramesMeta, MLJ, MLJLinearModels, JSON
# Carga de datos
df = CSV.read("data/benchmark_tabular.csv", DataFrame)
# Transformación de datos
df_clean = @chain df begin
@transform(
:income_per_age = :income ./ :age,
:high_credit = :credit_score .> 700
)
@rsubset(!ismissing(:income))
end
df_clean.target = coerce(df_clean.target, Multiclass)
df_clean.education = coerce(df_clean.education, Multiclass)
y, X = unpack(df_clean, ==(:target))
(Xtrain, Xtest), (ytrain, ytest) = partition((X, y), 0.8, shuffle=true, rng=42)
# Definición e ingeniería del Pipeline MLJ
RegresionLogistica = @load LogisticClassifier pkg=MLJLinearModels verbosity=0
pipe = ContinuousEncoder(drop_last=true) |> Standardizer() |> RegresionLogistica(lambda=1.0)
mach = machine(pipe, Xtrain, ytrain)
fit!(mach, verbosity=0)
ŷ = predict_mode(mach, Xtest)
acc = accuracy(ŷ, ytest)Apéndice A — Comparativa Tabular (Julia vs. Python)
En este apéndice comparamos el desempeño, tiempo de ejecución, sobrecarga de primera ejecución (Time To First Execution o TTFX) y consumo de memoria entre los entornos de Julia y Python al ejecutar un pipeline completo de ciencia de datos sobre datos tabulares.
La meta no es demostrar la absoluta superioridad de un lenguaje sobre otro en abstracto, sino medir objetivamente los costes y beneficios de cada entorno en fases clave: carga de paquetes, ingesta, preprocesado, entrenamiento y convergencia.
A.1 1. Diseño del Benchmark
El pipeline implementado en ambos lenguajes ejecuta los siguientes pasos sobre una muestra sintética representativa (\(N = 100.000\) filas):
- Ingesta y Transformación: Carga desde CSV e ingeniería de características (cálculo de ratios de ingresos por edad y filtrado condicional).
- Preprocesado y Split: Imputación de faltantes, estandarización de variables numéricas y codificación One-Hot de variables categóricas, dividiendo el conjunto en entrenamiento (\(80\%\)) y test (\(20\%\)).
- Ajuste del Modelo: Entrenamiento de una Regresión Logística regularizada (
MLJLinearModels.jlen Julia yscikit-learnen Python). - Evaluación: Predicción sobre test y cálculo de la precisión (Accuracy).
A.2 2. Código del Benchmark en Julia
En Julia utilizamos la infraestructura de MLJ.jl, DataFrames.jl y DataFramesMeta.jl.
A.3 3. Código Equivalente en Python
En Python empleamos pandas y scikit-learn (Pipeline + ColumnTransformer).
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
df = pd.read_csv("data/benchmark_tabular.csv")
df["income_per_age"] = df["income"] / df["age"]
df["high_credit"] = df["credit_score"] > 700
df_clean = df.dropna(subset=["income"]).copy()
X = df_clean[["age", "income", "credit_score", "debt_ratio", "education", "income_per_age", "high_credit"]]
y = df_clean["target"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
num_cols = ["age", "income", "credit_score", "debt_ratio", "income_per_age"]
cat_cols = ["education"]
preprocessor = ColumnTransformer(
transformers=[
("num", Pipeline([("imputer", SimpleImputer(strategy="mean")), ("scaler", StandardScaler())]), num_cols),
("cat", OneHotEncoder(drop="first", handle_unknown="ignore"), cat_cols),
],
remainder="passthrough"
)
pipeline = Pipeline([
("preprocessor", preprocessor),
("classifier", LogisticRegression(max_iter=1000, random_state=42))
])
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
acc = accuracy_score(y_test, y_pred)A.4 4. Resultados Comparativos y Escalabilidad
Una vez aislado el tiempo de compilación JIT inicial (Warm-up), se analizó el comportamiento de ambos lenguajes al escalar el volumen del conjunto de datos desde \(100.000\) hasta \(2.000.000\) de filas:
A.4.1 Tiempo de Preprocesado e Ingeniería de Características
| Tamaño (\(N\)) | Julia (DataFrames.jl / MLJ) |
Python (pandas / scikit-learn) |
Diferencia / Ventaja |
|---|---|---|---|
| 100.000 filas | 17.64 ms | 490.09 ms | Julia ~27.7x más rápido |
| 500.000 filas | 76.12 ms | 2.35 s | Julia ~30.9x más rápido |
| 1.000.000 filas | 372.91 ms | 4.70 s | Julia ~12.6x más rápido |
| 2.000.000 filas | 558.33 ms (0.55 s) | 9.37 s | Julia ~16.7x más rápido |
A.4.2 Tiempo de Entrenamiento del Modelo (Fit)
| Tamaño (\(N\)) | Julia (MLJLinearModels) |
Python (scikit-learn) |
Diferencia / Ventaja |
|---|---|---|---|
| 100.000 filas | 0.080 s | 0.118 s | Julia ~1.5x más rápido |
| 500.000 filas | 0.428 s | 0.408 s | Prácticamente idénticos |
| 1.000.000 filas | 0.774 s | 0.829 s | Julia ~1.07x más rápido |
| 2.000.000 filas | 1.082 s | 1.522 s | Julia ~1.41x más rápido |
A.4.3 Resumen Global a 2.000.000 de Filas
- Tiempo Total de Pipeline en Caliente: Julia completa el preprocesado y entrenamiento en 1.64 segundos, mientras que Python requiere 10.89 segundos (Julia ~6.6x más rápido globalmente).
- Arranque Frío vs. Caliente: Python mantiene la ventaja en scripts de un solo uso por tiempo de carga de módulos (0.9s vs 2.9s + JIT). Sin embargo, en entonos de servidor, APIs o pipelines iterativos (Warm Execution), Julia supera drásticamente a Python a medida que escala el volumen de datos.
A medida que el volumen de datos crece hacia millones de filas, la ventaja de velocidad de Julia se multiplica (alcanzando velocidades hasta 30 veces superiores en manipulación de datos), ya que las transformaciones se ejecutan mediante código vectorial nativo optimizado con SIMD y multihilo en lugar de pasar por bucles interpretados de Python.