Saltar al contenido principal

El generador timeseries

Se usa cuando hacen falta valores que se muevan como una señal real en el tiempo: ventas diarias, lecturas de un sensor, tráfico web. Las series reales no son ruido plano ni una sola distribución: son capas — una tendencia general (que sube o baja), una estación que se repite (semanal, anual) y ruido aleatorio encima. timeseries arma el valor de una fila exactamente así:

value(i) = base + trend·i + amplitude·sin(2π·i / period) + noise·random

donde i es el número de fila (el eje del tiempo), contado desde cero — así que la primera fila (i = 0) es exactamente base.

Las salidas de ejemplo de abajo son ilustrativas: los dígitos exactos pueden variar según la versión del núcleo y el seed, pero lo que importa es la forma — la tendencia, la onda, el temblor.

El mismo generador, cuatro veces, agregando un atributo cada vez — 120 filas por panel.
  • Asolo base: una línea plana
  • Bse agrega trend: la línea empieza a subir
  • Cse agregan period y amplitude: una onda cabalga sobre la tendencia
  • Dse agrega noise: la onda deja de ser perfecta

Por qué no simplemente números aleatorios

Un generador number común produce ruido blanco: valores que brincan alrededor de una media sin ninguna memoria. Aquí está number con una distribución normal centrada en 1000:

<sequence name="Noise"><gen type="number" distribution="normal" mean="1000" sd="120"/></sequence>
./run noise.tdc
Day 1     841
Day 2     1341
Day 3     1047
Day 4     1010
Day 5     1086
Day 6     1077
Day 7     862
Day 8     1072
Day 9     1114
Day 10    782
Day 11    979
Day 12    1014

Ni subida, ni bajada, ni repetición: cada día simplemente da vueltas alrededor de 1000. Las métricas reales no se ven así: las ventas tienen una tendencia (el negocio crece), un ritmo semanal (los fines de semana no son como los días hábiles) y solo encima de eso algo de desviación aleatoria. Esas tres capas son justamente lo que agrega timeseries.

Atributos

<gen type="timeseries" base="1000" trend="20" period="7" amplitude="150" noise="30"/>
AtributoQué define
baseNivel inicial (por omisión 0)
trendPendiente: cuánto sube el valor en cada paso
periodLargo de la onda estacional, en filas (p. ej. 7 = una semana) — o varias, 7,365
amplitudeAltura de la onda estacional — una por cada period
peak_atEn qué fila alcanza su máximo la onda (por defecto: a un cuarto del período) — una por cada period
noiseFuerza del ruido aleatorio (desviación estándar)
noise_correlationCuánto ruido de una fila pasa a la siguiente, -1..1 (por omisión 0)
decimalsDígitos después del punto decimal (por omisión 0 — entero)

Todas las capas son opcionales. Las secciones siguientes las toman una por una: qué hace cada una y cuándo conviene echarle mano.

base — el nivel inicial

base fija el valor de la primerísima fila (i = 0) y el nivel desde el que se mide todo lo demás. Por sí solo — sin trend, sin onda, sin noise — es apenas una línea plana.

<gen type="timeseries" base="500"/>
./run base.tdc
500
500
500
500
500

Sirve para anclar una métrica en un nivel realista — una tienda que promedia 500 pedidos al día, un sensor que reposa en 20 grados — antes de agregarle movimiento.

trend — la dirección

trend es la pendiente: cada fila le suma trend a la anterior. Positiva sube, negativa baja. Con solo base + trend sale una línea perfectamente recta.

<gen type="timeseries" base="1000" trend="20"/>
./run trend.tdc
1000
1020
1040
1060
1080

Sirve para crecimientos o caídas que se quieren ver a simple vista: una cuenta de suscriptores que gana 20 al día, una batería que se descarga una cantidad fija por ciclo.

period y amplitude — la onda estacional

Estos dos trabajan en pareja, y ninguno hace nada sin el otro. period es cuántas filas dura un ciclo completo (7 = ritmo semanal, 365 = anual); amplitude es cuánto se aleja la onda por arriba y por abajo de la línea de tendencia. Juntos recuestan una onda sin que se repite sobre lo que dé base + trend.

<gen type="timeseries" base="1000" trend="20" period="7" amplitude="150"/>
./run season.tdc
1000
1137
1186
1125
1015
954
1003

Dentro de cada ventana de 7 filas el valor sube hasta un pico y cae hasta un valle, y luego se repite. Como la tendencia sigue levantando toda la línea, cada ciclo queda más alto que el anterior: la onda cabalga cuesta arriba. Sirve para cualquier cosa con ritmo de calendario: tráfico entre semana contra fin de semana, demanda de verano contra invierno.

peak_at — en qué fila la onda está más alta

period y amplitude dicen qué largo tiene la onda y cuánto oscila. No dicen cuándo alcanza su máximo, y la respuesta por defecto sorprende: la onda arranca en la mitad de su oscilación y sube, así que su máximo cae a un cuarto del período.

Sobre doce filas mensuales eso es la fila 3 — abril. El config se escribió para «más calor en verano», y abril no lo es:

<gen type="timeseries" base="15" amplitude="10" period="12" decimals="1"/>
./run temp.tdc — la cuarta fila es la más alta
15.0
20.0
23.7
25.0
23.7
20.0
15.0
10.0
6.3
5.0
6.3
10.0

peak_at nombra la fila directamente. Las filas se cuentan desde cero, así que julio es la fila 6:

<gen type="timeseries" base="15" amplitude="10" period="12" peak_at="6" decimals="1"/>
./run temp.tdc — la séptima fila es la más alta
5.0
6.3
10.0
15.0
20.0
23.7
25.0
23.7
20.0
15.0
10.0
6.3

Nada más se movió: el mismo base, la misma amplitude, el mismo ciclo de doce filas. Solo cambió el mes en el que cae el máximo.

Es una fila, no un ángulo. period ya se cuenta en filas, así que peak_at también: 182 de 365 es el primero de julio, y ese número sale de un calendario y no de los radianes. Un valor más allá del período da la vuelta, así que peak_at="18" con period="12" es lo mismo que 6, y se admite una fracción cuando el máximo cae entre dos filas.

peak_at="0" merece conocerse aparte: hace que la onda empiece en su máximo, una forma que un seno simple no produce con ninguna amplitud.

Si buscaste phase

Ese es el nombre del procesamiento de señales, y TDC no lo tiene. peak_at hace el mismo trabajo en la unidad que usa el resto del generador. Escribir phase= es un error que lo dice.

peak_at necesita un period: una onda tiene que tener largo antes de poder tener un punto más alto. Sin él es TDC253.

noise — la aspereza del mundo real

noise es la desviación estándar de un bamboleo aleatorio que se suma a cada fila. Es la diferencia entre una curva de libro de texto y una medición real.

<gen type="timeseries" base="1000" trend="20" period="7" amplitude="150" noise="30"/>
./run noise-layer.tdc
1048
1152
1210
1093
1017
978
991

Compare con la onda limpia de arriba: la forma es la misma, pero cada punto tiembla un poco (1000 → 985). Súbalo para un sensor ruidoso, bájelo para un agregado suave. El temblor es reproducible — vea Detalles.

decimals — valores fraccionarios

Por defecto la salida se redondea a un número entero. decimals conserva esa cantidad de dígitos después del punto — para temperaturas, precios o cualquier magnitud medida.

<gen type="timeseries" base="20" trend="0.5" noise="0.3" decimals="1"/>
./run decimals.tdc
20.5
20.6
21.2
21.2
22.0

Ármelo capa por capa

La forma más clara de sentir el generador es prender las capas de a una. Abajo, tres columnas <sequence> corren sobre los mismos «días»: trend (solo trend), +season (agrega period + amplitude) y +noise (agrega noise).

<sequence name="A"><gen type="timeseries" base="1000" trend="20"/></sequence>
<sequence name="B"><gen type="timeseries" base="1000" trend="20" period="7" amplitude="150"/></sequence>
<sequence name="C"><gen type="timeseries" base="1000" trend="20" period="7" amplitude="150" noise="30"/></sequence>
...
<data>Day ${{Day}} trend=${{A}} +season=${{B}} +noise=${{C}}</data>
./run series.tdc
Day   trend   +season   +noise
01    1000     1000      985
02    1020     1137      1087
03    1040     1186      1192
04    1060     1125      1107
05    1080     1015      936
06    1100     954       966
07    1120     1003      1031
08    1140     1140      1087
09    1160     1277      1311
10    1180     1326      1347
11    1200     1265      1261
12    1220     1155      1126

Leyendo las columnas:

  • trend — una línea perfectamente recta: +20 cada día, 1000, 1020, 1040 …. Hay dirección, pero nada de vida.
  • +season — una onda semanal (period="7") recostada sobre la línea. Dentro de cada semana el valor sube a un pico y cae a un valle: los picos caen en los días 3 y 10 (1186 → 1326), el valle en el día 6 (954). Picos y valles se repiten cada 7 filas, y cada uno queda más alto que el anterior por exactamente trend · period = 20 · 7 = 140 — la onda cabalga cuesta arriba sobre la tendencia.
  • +noise — la misma forma, pero tiembla un poco (1000 → 985), como lo hacen las mediciones reales.

Cada columna es la anterior más un atributo: dirección, luego ritmo, luego aspereza del mundo real. Así se arma una serie realista.

Varias estaciones a la vez

Una serie real rara vez lleva una sola estación. Una tienda vende más los sábados y más en diciembre; una red eléctrica tiene un ciclo diario y uno anual. Escriba los dos: period, amplitude y peak_at aceptan listas separadas por comas, una entrada por onda, y las ondas se suman.

<gen type="timeseries" base="1000" trend="2"
period="7,365" amplitude="120,400" peak_at="5,182" decimals="0"/>

Eso es una onda semanal de 120 de alto con su máximo el día 5, y una anual de 400 con su máximo el día 182 — en la misma columna.

./run shop.tdc (count=16, seed=shop)
day 0: 573
day 1: 494
day 2: 496
day 3: 580
day 4: 684
day 5: 732
day 6: 689
day 7: 591
day 8: 512
day 9: 515
day 10: 600
day 11: 705
day 12: 753
day 13: 712
day 14: 614
day 15: 536

Los máximos semanales se ven a simple vista — días 5, 12 y 19 — y cada semana queda un poco más alta que la anterior, porque la onda anual todavía sube hacia el día 182.

Las tres listas describen las mismas ondas, posición por posición, así que tienen que cuadrar: period="7,365" necesita dos amplitudes. La única abreviatura es una sola amplitude para ondas de igual altura. Cualquier otra cosa es un error que el motor no va a adivinar (TDC304).

Ruido con memoria — noise_correlation

El noise normal es independiente: cada fila se sacude por su cuenta, así que una lectura alta no dice nada de la siguiente. El error de medición real casi nunca se comporta así. Un sensor que se va hacia arriba se queda arriba un rato; una cola que empieza a acumularse sigue acumulándose. El código probado solo contra ruido independiente nunca vio el caso con el que va a fallar de verdad.

noise_correlation dice cuánto ruido de una fila pasa a la siguiente: 0 es el ruido independiente que este generador siempre produjo, 0.9 es fuertemente correlacionado. Es el modelo AR(1), si le suena el nombre; si no, léalo como «qué tan pegajoso es el ruido».

<sequence name="White"> <gen type="timeseries" base="20" noise="2" decimals="1"/></sequence>
<sequence name="Drifty"> <gen type="timeseries" base="20" noise="2" noise_correlation="0.9" decimals="1"/></sequence>
./run sensor.tdc (count=16, seed=sensor)
independent 17.8   correlated 22.4
independent 18.5   correlated 21.0
independent 20.8   correlated 21.8
independent 21.1   correlated 20.3
independent 19.3   correlated 22.6
independent 18.4   correlated 23.9
independent 20.4   correlated 23.2
independent 18.7   correlated 21.5
independent 21.6   correlated 20.9
independent 23.1   correlated 20.7
independent 20.0   correlated 20.6
independent 23.2   correlated 20.2
independent 22.4   correlated 18.9
independent 20.0   correlated 18.7
independent 18.5   correlated 18.4
independent 18.5   correlated 18.5

La columna izquierda salta alrededor de 20 al azar. La derecha deambula: sube a 23.9 y en diez filas baja a 18.4. Las dos tienen la misma dispersión — la correlación cambia cómo se mueve el ruido, no cuán grande es.

La letra pequeña

  • Necesita noise=. Si no, ¿correlación de qué? Se rechaza en vez de ignorarse (TDC305).
  • Entre −1 y 1, y no 1: en 1 la serie se iría y no volvería, lo que es una caminata aleatoria y no ruido.
  • Un valor negativo alterna — cada fila se apoya contra la anterior, que es como se ven los lazos de control sobrecorregidos.
  • Los dos motores, cualquier tamaño. El valor correlacionado se sigue calculando a partir del número de fila, así que una corrida en streaming de mil millones de filas produce el mismo archivo que una en memoria, y la memoria no crece. Recuerda las últimas 64 filas: lo bastante lejos para que el peso de lo más viejo sea despreciable, y lo bastante cerca para que cueste unos 20 nanosegundos por fila.

Detalles

  • Determinista: el mismo seed da la misma serie. El ruido también es reproducible: se calcula a partir del número de fila, no se tira al vuelo.
  • Cualquier tamaño, cualquier motor: un valor se calcula a partir de su número de fila, así que la memoria no crece (vea Salidas grandes). Mil millones de puntos no son problema.
  • El eje del tiempo es el número de fila. Se lleva de forma natural con un increment (un contador de días) o una columna date al lado, para que cada valor cargue una fecha real.

Vea también

  • Number — valores aleatorios sueltos, con distribuciones estadísticas.
  • Pattern — cuando la forma no se describe con tendencia + estación.
  • Contadores / Date — un índice de día o una fecha real para poner junto a la serie.