Conceptos¶
Particionado temporal¶
Jano modela la evaluación como un problema de particionado temporal, no como uno de muestreo aleatorio.
Ese enfoque también es útil cuando querés evidenciar drift en resultados de simulación, porque los cambios a través del tiempo permanecen visibles en lugar de quedar diluidos por splits aleatorios.
La distinción importante es que Jano no trata el particionado como un paso de preprocesamiento de una sola vez. Lo trata como un proceso temporal. Una política de partición define:
cuánta historia entra en train
qué tan grande es el horizonte de evaluación
cuánto se mueve la simulación en cada paso
qué gaps temporales deben existir para evitar leakage
En ese sentido, una simulación se entiende mejor como una secuencia de folds causalmente válidos que como una única descomposición train/test.
Por eso Jano es útil tanto para backtesting como para preguntas operativas sobre retraining, estabilidad temporal y cambios de régimen.
Internamente, el motor trabaja sobre pandas. En el borde público, sin embargo, Jano acepta:
pandas.DataFramecon columnas nombradasnumpy.ndarraycon referencias enteras comotime_col=0polars.DataFrameconvertido internamente antes de generar folds
En lugar de pedir un share aleatorio de filas, definís una política de partición:
qué tan grande es train
qué tan grandes son validation o test
si debe haber gaps temporales
y cómo debe moverse el split a lo largo del tiempo
Workflow composicional¶
Jano está pensado como una herramienta composicional.
La progresión buscada es:
empezar con una partición temporal simple
agregar movimiento con
single,rollingoexpandinginspeccionar la geometría con
plan()subir a policies high-level cuando la pregunta ya está encapsulada
y bajar al modo manual completo cuando necesitás control total
En otras palabras, Jano ofrece tres niveles de uso:
una superficie recomendada y chica como
WalkForwardPolicy,TrainHistoryPolicyoDriftMonitoringPolicyworkflows explícitos de nivel más bajo como
TemporalSimulation,TrainGrowthPolicyoPerformanceDecayPolicyuna capa intermedia de planning con
plan()un modo manual a través de
TemporalBacktestSplittereiter_splits()cuando querés componer a gusto particiones, gaps, historia de features y loops externos de entrenamiento
Ese último nivel importa porque no toda evaluación productiva entra en una clase predefinida. Jano debería ayudarte cuando el caso común alcanza, pero también dejarte componer tu propia lógica temporal cuando el problema lo exige.
Estrategias¶
singleProduce una sola partición. Es el equivalente temporal de un split único, pero respetando el orden cronológico.
rollingMueve una ventana fija de entrenamiento y evalúa repetidamente a medida que el tiempo avanza.
expandingHace crecer la historia de entrenamiento mientras validation y test siguen avanzando hacia adelante.
Layouts¶
train_testProduce un segmento de train y uno de test.
train_val_testProduce train, validation y test en ese orden.
Tamaños de segmento¶
Jano acepta hoy tres familias de unidades:
duraciones como
"30D"o"12H"conteos de filas como
5000fracciones como
0.7
Dentro de una misma partición, tamaños y gaps deben pertenecer a la misma familia de unidades.
Salidas¶
Jano expone dos vistas complementarias:
plan()precalcula la geometría de la simulación como un objeto inspeccionable antes de materializar foldsTemporalSimulation.run()materializa una simulación completa y devuelve un resultado reusablesplit()entrega tuplas de índices, útil para integración livianaiter_splits()entrega objetosTimeSplitcon metadata y helpersdescribe_simulation()entregaSimulationSummary, HTML oSimulationChartDatapara plots custom
Planificación antes de materializar¶
Jano ahora expone una capa de planning entre la configuración y la ejecución.
Eso significa que primero podés calcular la geometría de todas las particiones futuras, inspeccionarla, filtrarla y recién después materializar los folds que realmente te interesan.
plan() es útil cuando querés:
inspeccionar la lista completa de iteraciones antes de entrenar nada
entender cuántas filas tendría cada segmento
arrancar desde la iteración
Nen vez del comienzoexcluir folds cuyo train o test caen sobre fechas especiales
trabajar sobre un plan precomputado en vez de slice del dataset inmediatamente
A nivel low-level:
plan = splitter.plan(frame)
print(plan.to_frame().head())
A nivel high-level:
plan = simulation.plan(frame, title="Vista previa de la policy")
filtered = plan.exclude_windows(
train=[("2025-12-20", "2026-01-05")],
).select_from_iteration(10)
result = filtered.materialize()
El frame del plan incluye una columna explícita iteration, boundaries por segmento y conteos de filas. Eso permite razonar sobre la simulación como objeto de primer nivel, no solo como generador de folds.
Hipótesis temporales¶
Las secciones anteriores describen la mecánica de particionado temporal. Encima de esa base, Jano también puede codificar hipótesis de evaluación sobre cómo se comporta un modelo en el tiempo.
La progresión está pensada para ser incremental:
primero particiones explícitas
luego simulaciones walk-forward
finalmente hipótesis operativas sobre suficiencia de historia o degradación temporal
Dos policies centrales ya forman parte del paquete, y cada una además tiene un wrapper recomendado más chico.
TrainHistoryPolicy/TrainGrowthPolicyMantiene fijo el mismo test y expande train hacia atrás en el tiempo.
Responde preguntas como:
¿agregar más historia mejora el mismo test?
¿puede una muestra más chica igualar la mejor calidad observada?
¿dónde deja de ser útil seguir sumando historia?
DriftMonitoringPolicy/PerformanceDecayPolicyMantiene train fijo y desplaza test hacia adelante.
Responde preguntas como:
¿cuánto tiempo puede permanecer el modelo en producción antes de degradarse materialmente?
¿cuándo empieza a ser un problema práctico el drift?
¿cada cuánto conviene reentrenar si reentrenar es costoso?
Estas policies no son sólo variaciones visuales del splitter. Encapsulan preguntas temporales distintas sobre el sistema que estás evaluando:
la simulación walk-forward pregunta cómo se habría comportado el sistema bajo una política de retraining
el crecimiento de train pregunta si realmente vale la pena usar más historia
la degradación temporal pregunta cuánto tiempo sigue siendo operativamente seguro el train actual
También hay una hipótesis compuesta construida encima de esas piezas.
RollingTrainHistoryPolicyEjecuta un loop walk-forward externo y elige el tamaño óptimo de train dentro de cada iteración.
Esto responde preguntas como:
¿cuánta historia de entrenamiento necesito en promedio a lo largo del tiempo?
¿el tamaño óptimo de train se mantiene estable o cambia entre iteraciones?
¿se puede bajar costo de entrenamiento adaptando la profundidad histórica en lugar de usar siempre la ventana máxima?
Policies de lookback por features¶
Algunos problemas temporales necesitan una capa adicional de realismo: no todos los grupos de features usan la misma profundidad histórica.
Por ejemplo:
features de comportamiento reciente pueden necesitar sólo
15Dfeatures con lags largos o estacionalidad pueden necesitar
65Do más
Eso no significa necesariamente que la ventana supervisada de train deba ser más grande. Significa que el pipeline de features necesita distintas cantidades de contexto histórico para distintos grupos de variables.
Jano modela eso con FeatureLookbackSpec sobre un fold ya definido:
from jano import FeatureLookbackSpec
lookbacks = FeatureLookbackSpec(
default_lookback="15D",
group_lookbacks={"lag_features": "65D"},
feature_groups={"lag_features": ["lag_30", "lag_60"]},
)
split = next(splitter.iter_splits(frame))
history = split.slice_feature_history(
frame,
lookbacks,
time_col="timestamp",
segment_name="train",
)
recent_context = history["__default__"]
lag_context = history["lag_features"]
Esto mantiene fija la geometría del fold, pero hace explícito el contexto histórico requerido por cada grupo de features. Es útil cuando el cómputo de features y el entrenamiento supervisado no comparten la misma profundidad temporal.