---
title: "Calificación de datos: estacionariedad, cruces y picos"
description: "Califica un registro aleatorio antes de analizarlo: los tests de inversiones de orden y de rachas de Bendat y Piersol para la estacionariedad sobre medias cuadráticas por segmento con las regiones de aceptación de la Tabla A.6, y las estadísticas de Rice de cruces por nivel, frecuencia aparente, tasas de picos y el factor de irregularidad que fija la distribución de alturas de pico entre Rayleigh y gaussiana."
references:
  - type: book
    authors: ["Bendat, J. S.", "Piersol, A. G."]
    year: 2010
    title: "Random data: Analysis and measurement procedures"
    edition: "4th ed."
    publisher: "Wiley"
    doi: "10.1002/9781118032428"
    note: "Sección 4.5.2 con la Tabla A.6 (el test no paramétrico de tendencia por inversiones de orden, su media y varianza, los puntos porcentuales tabulados y el Ejemplo 4.4), Sección 10.3 (calificación de datos: clasificación, validación, edición; el procedimiento de estacionariedad por medias cuadráticas de segmento de 10.3.1.1 y el Ejemplo 10.3) y Sección 5.5 (cruces por nivel y valores de pico: Ecs. (5.195)-(5.196), (5.206), (5.211), (5.217)-(5.223) y Ejemplos 5.12-5.15). ISBN 978-0-470-24877-5. El test de rachas complementario aparecía en los puntos porcentuales de la distribución de rachas de la tercera edición."
  - type: article
    authors: ["Wald, A.", "Wolfowitz, J."]
    year: 1940
    title: "On a test whether two samples are from the same population"
    journal: "The Annals of Mathematical Statistics"
    volume: 11
    issue: 2
    pages: "147-162"
    doi: "10.1214/aoms/1177731909"
    note: "La distribución condicional exacta del número de rachas, de la que se calculan las regiones de aceptación de rachas respecto de la mediana."
  - type: article
    authors: ["Rice, S. O."]
    year: 1945
    title: "Mathematical analysis of random noise"
    journal: "The Bell System Technical Journal"
    volume: 24
    issue: 1
    pages: "46-156"
    doi: "10.1002/j.1538-7305.1945.tb00453.x"
    note: "Las derivaciones originales de las tasas esperadas de cruces por nivel y de máximos y de la distribución de picos del ruido gaussiano que presenta la Sección 5.5 de Bendat y Piersol (su Ref. 6; las Partes I-II están en el volumen 23, 1944)."
---

import ThemeImage from '../../../../components/ThemeImage.astro';

Todo promedio de esta documentación - una [PSD](/phonometry/es/guides/spectral-analysis/), un
[Leq](/phonometry/es/guides/levels/), un
[balance GUM](/phonometry/es/guides/gum-uncertainty/) - supone que el registro es
**estacionario**: que el proceso que lo genera no derivó mientras se medía.
Bendat y Piersol dedican la Sección 10.3 de *Random Data* a calificar los
registros antes del análisis, y `phonometry.metrology` implementa su núcleo
cuantitativo: **tests de tendencia y de estacionariedad** libres de
distribución con las regiones de aceptación del propio libro, y las
**estadísticas de Rice** - cruces por nivel, frecuencia aparente, tasas y
alturas de pico - que resumen el aspecto de un registro gaussiano calificado
y delatan al que no lo es.

## 1. El test de inversiones de orden

Dada una secuencia de $N$ observaciones $x_1, \dots, x_N$ - estimaciones de
parámetros, niveles por segmento, lo que sea - cuéntense los pares $i < j$
con $x_i > x_j$. Cada par es una **inversión de orden** (*reverse
arrangement*), y para observaciones independientes de una misma variable
aleatoria su total $A$ tiene (B&P Ecs. (4.54)-(4.55))

$$
\mu_A = \frac{N(N-1)}{4}, \qquad
\sigma_A^2 = \frac{N(2N+5)(N-1)}{72},
$$

sin ninguna suposición sobre la distribución de las $x_i$. Una tendencia
monótona empuja $A$ a un extremo (0 si la secuencia sube, $N(N-1)/2$ si
baja), así que la hipótesis de *ausencia de tendencia* se acepta al nivel de
significación $\alpha$ cuando $A$ cae dentro de una región bilateral - la
Tabla A.6 de B&P, cuyas filas de $\alpha = 0{,}05$ reproduce exactamente
`trend_test` y fija la suite de conformidad. El Ejemplo 4.4 del libro
(veinte observaciones, $A = 86$, aceptado entre 64 y 125) corre tal cual:

```python
from phonometry import trend_test

values = [5.2, 6.2, 3.7, 6.4, 3.9, 4.0, 3.9, 5.3, 4.0, 4.6,
          5.9, 6.5, 4.3, 5.7, 3.1, 5.6, 5.2, 3.9, 6.2, 5.0]

res = trend_test(values)                  # B&P Ejemplo 4.4
print(res.statistic, res.bounds)          # 86, (64, 125)
print(res.trend_free, round(res.p_value, 3))   # True, 0.586
res.plot(language="es")
```

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/trend_test_es.svg" alt="Dos secuencias de veinte observaciones dibujadas frente al índice de muestra: el Ejemplo 4.4 de Bendat y Piersol, que fluctúa en torno a cinco con ochenta y seis inversiones de orden y se acepta como sin tendencia, y las mismas fluctuaciones con una deriva ascendente añadida que sube de unos cinco a nueve, cuyas treinta y ocho inversiones de orden caen por debajo del límite inferior de aceptación de sesenta y cuatro y se rechazan como con tendencia al nivel del cinco por ciento" width="82%" loading="eager" />

<details>
<summary>Mostrar el código de esta figura</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import trend_test

example = np.array([5.2, 6.2, 3.7, 6.4, 3.9, 4.0, 3.9, 5.3, 4.0, 4.6,
                    5.9, 6.5, 4.3, 5.7, 3.1, 5.6, 5.2, 3.9, 6.2, 5.0])
drifting = example + np.linspace(0.0, 4.0, example.size)   # deriva ascendente

res_flat = trend_test(example)
res_drift = trend_test(drifting)

fig, ax = plt.subplots(figsize=(10, 6))
index = np.arange(1, example.size + 1)
ax.plot(index, res_flat.values, "o-",
        label=f"Ejemplo 4.4: A = {res_flat.statistic}, aceptado")
ax.plot(index, res_drift.values, "s-",
        label=f"Deriva ascendente: A = {res_drift.statistic}, rechazado")
ax.set_xlabel("Índice de muestra")
ax.set_ylabel("Valor de la secuencia")
ax.legend()
plt.show()
```

</details>

El p-valor sale de la distribución nula exacta de $A$ (los conteos de
inversiones de una permutación aleatoria), calculada hasta $N = 100$ - el
alcance de la Tabla A.6 - y de la aproximación normal del libro más allá;
el veredicto sigue la región tabulada del libro. `.plot()` dibuja la
secuencia analizada frente a su índice de muestra con el conteo, la región
de aceptación y el veredicto en la leyenda (con `method="runs"` marca
además la mediana de la secuencia que clasifica cada valor).

## 2. Estacionariedad de un registro

El procedimiento de la Sec. 10.3.1.1 de B&P convierte el test de tendencia
en un test de estacionariedad para una única historia temporal: divídase el
registro en $N$ intervalos iguales lo bastante largos para ser
independientes, calcúlese una **media cuadrática por intervalo** y pruébese
esa secuencia. No hace falta saber nada del ancho de banda del registro, de
su distribución ni de sus unidades, y el test funciona igual de bien sobre
medias, valores eficaces o varianzas (`statistic=`). Una deriva de ganancia
del 20 % - el escenario del Ejemplo 10.3 del libro - se detecta de
inmediato, mientras que el mismo ruido sin deriva pasa:

```python
import numpy as np
from phonometry import stationarity_test

fs = 8192.0
n = 1 << 16
noise = np.random.default_rng(42).standard_normal(n)

res = stationarity_test(noise, fs)        # 20 segmentos, medias cuadráticas
print(res.stationary, res.count, res.bounds)   # True, 91, (64, 125)

drifting = noise * np.linspace(1.0, 1.2, n)    # rampa de ganancia del +20 %
res = stationarity_test(drifting, fs)
print(res.stationary, res.count)          # False, 7  (tendencia al alza -> A bajo)
res.plot(language="es")
```

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/stationarity_test_es.svg" alt="Veinte medias cuadráticas por segmento de dos registros de ruido: un registro estable cuyos valores fluctúan en torno a uno con un conteo de inversiones de orden de noventa y uno, aceptado como estacionario, y el mismo ruido con una rampa de ganancia del veinte por ciento cuyas medias cuadráticas por segmento suben de forma sostenida hasta uno coma cinco, dando solo siete inversiones de orden, rechazado como no estacionario al nivel del cinco por ciento" width="82%" loading="eager" />

<details>
<summary>Mostrar el código de esta figura</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import stationarity_test

fs = 8192.0
n = 1 << 16
steady = np.random.default_rng(42).standard_normal(n)
ramp = np.random.default_rng(42).standard_normal(n) * np.linspace(1.0, 1.2, n)

res_steady = stationarity_test(steady, fs)
res_ramp = stationarity_test(ramp, fs)

fig, ax = plt.subplots(figsize=(10, 6))
index = np.arange(1, res_steady.n_segments + 1)
ax.plot(index, res_steady.segment_values, "o-",
        label=f"Ruido estable: A = {res_steady.count}, aceptado")
ax.plot(index, res_ramp.segment_values, "s-",
        label=f"Rampa de ganancia del +20 %: A = {res_ramp.count}, rechazado")
ax.set_xlabel("Índice de segmento")
ax.set_ylabel("Media cuadrática por segmento")
ax.legend()
plt.show()
```

</details>

El resultado registra la secuencia por segmento (`segment_values`,
`segment_times`), el conteo, los `bounds` de la Tabla A.6, el `p_value`
exacto y el veredicto `stationary`; `.plot()` dibuja la secuencia con el
veredicto en la leyenda. El valor por defecto de 20 segmentos coincide con
los ejemplos resueltos del libro - más segmentos resuelven derivas más
rápidas, pero cada intervalo debe seguir siendo largo frente a las
frecuencias más bajas del registro para que los valores sean
independientes.

Merece la pena repetir dos salvedades del libro. Un registro puede ser no
estacionario con media cuadrática estacionaria (un barrido de frecuencia,
por ejemplo), así que pásese `statistic="mean"` o pruébense versiones
filtradas por bandas cuando importe; y el test necesita que la *tendencia*
sea lenta frente a la longitud del segmento, o se disuelve en la
fluctuación aleatoria de los valores de segmento.

## 3. El test de rachas

El complemento clásico (tabulado en la tercera edición de *Random Data*; la
distribución exacta es de Wald y Wolfowitz, 1940) clasifica cada valor como
por encima o por debajo de la mediana de la secuencia y cuenta **rachas**
de clasificación igual. Una tendencia o deriva lenta produce pocas rachas
largas; la alternancia rápida produce demasiadas. `method="runs"` lo aplica
con la distribución condicional exacta para cualquier $N$ - para veinte
valores repartidos 10/10 la región de aceptación con $\alpha = 0{,}05$ es la
clásica $(6, 15]$:

```python
import numpy as np
from phonometry import trend_test

rng = np.random.default_rng(3)
res = trend_test(rng.standard_normal(40), method="runs")
print(res.statistic, res.bounds, res.trend_free)

alternating = np.tile([1.0, -1.0], 10)   # 20 rachas: rechazado por el otro lado
print(trend_test(alternating, method="runs").trend_free)   # False
```

El test de inversiones de orden es el más potente de los dos frente a las
tendencias monótonas que dominan la práctica (B&P Sec. 4.5.2), y por eso es
el valor por defecto en todas partes; el de rachas añade sensibilidad al
agrupamiento no monótono.

## 4. Cruces por nivel y frecuencia aparente

Para un registro gaussiano de media cero con autoespectro unilateral
$G(f)$, los resultados clásicos de Rice dan la tasa esperada de cruces por
cero (ambas pendientes, B&P Ec. (5.195)) a partir de los momentos
frecuenciales $m_k = \int f^k\,G(f)\,\mathrm{d}f$:

$$
N_0 = \frac{1}{\pi}\frac{\sigma_v}{\sigma_x} = 2\sqrt{\frac{m_2}{m_0}},
\qquad
N_a = N_0\, e^{-a^2/2\sigma_x^2},
$$

donde $N_a$ es la tasa de cruces del nivel $a$ (Ec. (5.196)). $N_0/2$ es la
**frecuencia aparente** del registro: un seno de 60 Hz cruza el cero 120
veces por segundo, el ruido paso bajo de ancho de banda $B$ da
$N_0 = 2B/\sqrt{3}$ (una frecuencia aparente de $0{,}58B$, Ejemplo 5.12) y
una banda centrada en $f_c$ da $N_0 = 2\sqrt{f_c^2 + B^2/12}$
(Ejemplo 5.13). `level_crossing_rate` cuenta los cruces reales de cada
nivel y pone al lado la curva de Rice, tomando los momentos del propio
[autoespectro de Welch](/phonometry/es/guides/spectral-analysis/) del registro:

```python
import numpy as np
from phonometry import level_crossing_rate

fs = 8192.0
t = np.arange(1 << 16) / fs
x = np.sin(2 * np.pi * 60.0 * t)          # un seno de 60 Hz...

res = level_crossing_rate(x, fs)
print(round(res.zero_crossing_rate, 1))   # ...tiene 120 ceros por segundo
print(round(res.apparent_frequency, 1))   # 60.0
res.plot(language="es")
```

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/rice_level_crossings_es.svg" alt="Tasas de cruce por nivel medidas de un registro de ruido gaussiano limitado en banda entre ochocientos y mil doscientos hercios, dibujadas como puntos frente al nivel de cruce desde menos tres coma cinco hasta más tres coma cinco unidades de la señal en un eje de tasas logarítmico, cayendo desde unos dos mil cruces por segundo en el nivel cero hasta unos pocos por segundo a tres sigmas, con la curva exponencial de Rice pasando por todos los puntos medidos" width="82%" loading="eager" />

<details>
<summary>Mostrar el código de esta figura</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import level_crossing_rate

fs = 20480.0
n = 1 << 19
rng = np.random.default_rng(0)
freqs = np.fft.rfftfreq(n, 1 / fs)        # ruido gaussiano limitado en banda:
spec = rng.standard_normal(freqs.size) + 1j * rng.standard_normal(freqs.size)
spec[(freqs < 800.0) | (freqs > 1200.0)] = 0.0
x = np.fft.irfft(spec, n)

res = level_crossing_rate(x, fs, levels=np.linspace(-3.5, 3.5, 29) * np.std(x))

fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(res.levels, res.rice_rates, label="Rice (Ec. 5.196)")
ax.plot(res.levels, res.rates, "o", label="Medida")
ax.set_yscale("log")
ax.set_xlabel("Nivel a [unidades de la señal]")
ax.set_ylabel("Cruces por segundo [1/s]")
ax.legend()
plt.show()
```

</details>

La curva de Rice vale para registros gaussianos, y ese es precisamente su
segundo uso: unas tasas medidas que se apartan sistemáticamente de la curva
son un cribado rápido de no gaussianidad (B&P Sec. 5.5.1.1) - el recorte
aparece como cruces de nivel alto que faltan, la contaminación impulsiva
como un exceso. Las tasas basadas en conteo necesitan el registro
holgadamente sobremuestreado: un cruce entre dos muestras del mismo signo
queda sin contar.

## 5. Picos: tasas, factor de irregularidad y alturas

Los mismos momentos fijan la tasa esperada de máximos locales
$M = (1/2\pi)(\sigma_a/\sigma_v) = \sqrt{m_4/m_2}$ (Ec. (5.211)) y con ella
el **factor de irregularidad** adimensional

$$
r = \frac{N_0}{2M} = \frac{m_2}{\sqrt{m_0\, m_4}} \in (0, 1],
$$

el número único que fija la distribución de las alturas de pico
(B&P Sec. 5.5.4). Con $r = 1$ - datos de banda estrecha, un máximo por
ciclo de cruce por cero - los picos siguen una distribución de
**Rayleigh**: $\mathrm{Prob}[\text{pico} > a] = e^{-a^2/2\sigma_x^2}$
(Ec. (5.206)), la probabilidad de uno entre 3000 de un pico más allá de
$4\sigma$ del Ejemplo 5.14 de B&P. Cuando $r \to 0$ cada ciclo lleva cada
vez más rizos encima, aparecen máximos negativos y las alturas de pico se
acercan a la distribución de amplitud **gaussiana** simple. Entre medias,
la mezcla de Rice (Ecs. (5.217)/(5.223)) interpola ambas, disponible como
`peak_exceedance()` / `peak_density()` en el resultado:

```python
import numpy as np
from phonometry import peak_statistics

fs = 8192.0
t = np.arange(1 << 16) / fs
x = np.sin(2 * np.pi * 60.0 * t)          # banda estrecha: r es en esencia 1

res = peak_statistics(x, fs)
print(round(res.irregularity_factor, 3))       # 0.997
print(res.peak_exceedance(4.0))                # exp(-8): aprox. 1 entre 3000
```

<ThemeImage src="https://raw.githubusercontent.com/jmrplens/phonometry/main/.github/images/rice_peak_distribution_es.svg" alt="Probabilidad de excedencia de la altura de pico de ruido gaussiano paso bajo en eje logarítmico frente a la altura de pico estandarizada desde menos dos coma cinco hasta cuatro coma cinco: la escalera empírica de medio millón de muestras sigue la curva de mezcla de Rice para un factor de irregularidad de cero coma siete cuatro seis, claramente por debajo del límite de Rayleigh discontinuo y por encima del límite gaussiano punteado, con una fracción visible de máximos negativos" width="82%" loading="eager" />

<details>
<summary>Mostrar el código de esta figura</summary>

```python
import matplotlib.pyplot as plt
import numpy as np
from phonometry import peak_statistics
from phonometry.metrology.random_data import _rice_peak_exceedance

fs = 20480.0
n = 1 << 19
rng = np.random.default_rng(3)
freqs = np.fft.rfftfreq(n, 1 / fs)        # ruido paso bajo: r = sqrt(5)/3
spec = rng.standard_normal(freqs.size) + 1j * rng.standard_normal(freqs.size)
spec[freqs > 2000.0] = 0.0
x = np.fft.irfft(spec, n)

res = peak_statistics(x, fs)
peaks = res.peak_values
empirical = 1.0 - np.arange(1, peaks.size + 1) / peaks.size
z = np.linspace(-2.5, 4.5, 400)

fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(z, _rice_peak_exceedance(z, 1.0), "--", label="Rayleigh (r = 1)")
ax.plot(z, _rice_peak_exceedance(z, 0.0), ":", label="Gaussiana (r = 0)")
ax.plot(z, res.peak_exceedance(z),
        label=f"Rice (r = {res.irregularity_factor:.3f})")
ax.plot(peaks, empirical, drawstyle="steps-post", label="Empírica")
ax.set_yscale("log")
ax.set_ylim(1e-5, 1.5)
ax.set_xlabel(r"Altura de pico estandarizada $z = a/\sigma_x$")
ax.set_ylabel("Prob[pico > z]")
ax.legend()
plt.show()
```

</details>

Para una banda paso bajo ideal las formas cerradas dan
$r = \sqrt{5}/3 = 0{,}745$, y el valor medido aterriza encima. El factor de
irregularidad es el puente estándar hacia la estimación de fatiga y de daño
vibroacústico, donde selecciona la corrección de conteo de ciclos entre la
hipótesis de Rayleigh de banda estrecha y las correcciones rainflow de
banda ancha. Un aviso práctico: $m_4$ pondera el espectro por $f^4$, así
que el ruido de instrumentación de banda ancha muy por encima de la banda
física infla $M$ y deprime $r$ sin avisar - limítese antes el registro a la
banda físicamente significativa.

## Dónde encaja esto

La calificación va *antes* de las estadísticas que calculan las demás
páginas de esta sección: el intervalo de confianza chi-cuadrado de una
[PSD de Welch](/phonometry/es/guides/spectral-analysis/) y las fórmulas de error
aleatorio de los [estimadores de correlación](/phonometry/es/guides/correlation-delay/)
suponen que el registro es estacionario, igual que la idea misma de *el*
Leq de una medición en [Niveles](/phonometry/es/guides/levels/). Cuando un
registro no pasa el test, divídase en el cambio (la secuencia
`segment_values` muestra dónde), analícense los trozos, o pásese a las
vistas de tiempo corto - el
[espectrograma calibrado](/phonometry/es/guides/time-frequency/) - que no suponen
estacionariedad. Y cuando pasa, la
[maquinaria GUM](/phonometry/es/guides/gum-uncertainty/) puede propagar el error
aleatorio *restante* de cada estimación promediada con la conciencia
tranquila.

## Véase también

- Referencia de la API: [`metrology.random_data`](/phonometry/es/reference/api/metrology/random-data/).
