*! version 1.0 21jul2026

Titulo

atip_reglas -- Deteccion de atipicos (outliers) por ensamble de 5 reglas clasicas

Sintaxis

atip_reglas varname,
group(varlist)
[opciones]

Requerida
group(varlist) variable(s) que definen el grupo de referencia (ej. departamento, cultivo)

Opcionales
rules(string) subconjunto de reglas a aplicar, de sd3 sd2 zscore iqr mahal; por defecto las 5

sdthresh3(#) umbral de la regla SD_MEAN_3; por defecto sdthresh3(3)

sdthresh2(#) umbral de la regla SD_MEAN_2; por defecto sdthresh2(2)

zthresh(#) umbral de la regla ZSCORE; por defecto zthresh(2)

zsymmetric usa ZSCORE de dos colas (|z|>zthresh); por defecto es de una cola (z>zthresh)

mahalthresh(#) umbral de la regla MHLBS (Mahalanobis univariado = z^2); por defecto mahalthresh(8)

gen(name) nombre de la variable de salida (union de las 5 reglas); por defecto OUTLIERS

classic usa media y desviacion estandar clasicas en vez de mediana/MAD. Por defecto (sin esta opcion) se usa la referencia robusta (mediana/MAD) -- ver seccion Robustez ante contaminacion abajo

Descripcion

atip_reglas aplica un ensamble de 5 reglas clasicas de deteccion
de atipicos (outliers) dentro de cada grupo definido por group():
desviacion respecto a la media (3 y 2 desviaciones estandar), z-score,
rango intercuartilico (Tukey) y distancia de Mahalanobis univariada.
Genera una variable binaria (por defecto OUTLIERS) que marca 1
si cualquiera de las reglas activas dispara para esa observacion
(union, no interseccion) -- una estrategia de alta sensibilidad
pensada para screening/pre-revision, no para confirmacion estadistica
formal.

Este comando corrige dos problemas frecuentes en implementaciones
tipicas de estas reglas: (1) la regla IQR suele usar OR en vez de
AND en la condicion de "valor normal", lo que anula silenciosamente
la deteccion de atipicos por el lado bajo; aqui esta corregido. (2) La
regla ZSCORE suele detectar solo un lado (cola alta); aqui es asi por
defecto tambien (para no romper el comportamiento esperado por defecto),
pero zsymmetric permite usar la version de dos colas.

La distancia de Mahalanobis, al ser univariada aqui, es
matematicamente identica al cuadrado del z-score -- se calcula
directamente como tal, sin necesidad de mahascore ni frames.

Vease tambien atip_score (generaliza esto a dos ejes continuos
en vez de un flag binario) y atip_volcano (grafico de
magnitud vs. evidencia).

Opciones

group(varlist) (requerida) variable(s) que
definen el grupo dentro del cual se calculan media, desviacion
estandar y cuartiles de referencia (ej. CCDD_ P204_NOM P204_COD).

rules(string) permite activar solo un subconjunto
de las 5 reglas, por ejemplo rules(iqr mahal) para usar solo
esas dos. Por defecto se aplican las 5: sd3 sd2 zscore iqr mahal.

zsymmetric cambia la regla ZSCORE de una cola (solo valores
altos) a dos colas (altos y bajos). Util si el interes no se limita a
valores anormalmente altos.

gen(name) nombre de la variable binaria de salida.
Ademas de esta, el comando genera (y no elimina) las variables
intermedias SD_MEAN_3, SD_MEAN_2, ZSCORE_I,
ZSCORE, IQR, DIST, MHLBS -- utiles para
inspeccionar que regla especifica disparo en cada caso.

Resultados almacenados

Escalares

r(n_outliers) numero de observaciones marcadas por la variable de salida

Metodos y formulas

SD_MEAN_3 / SD_MEAN_2: marca 1 si el valor esta fuera de
media ± k·DE (k=3 o k=2), calculado dentro de cada group().

ZSCORE: z = (x-media)/DE; marca 1 si z>zthresh (una cola, por
defecto) o |z|>zthresh (con zsymmetric).

IQR (Tukey): marca 1 si x esta fuera de
[Q1-1.5·RIC, Q3+1.5·RIC], con RIC=Q3-Q1, calculado dentro de cada
group().

MHLBS: distancia de Mahalanobis univariada, DIST=z^2; marca 1 si
DIST>mahalthresh. Bajo normalidad, DIST sigue una chi-cuadrado con 1
grado de libertad.

Union: la variable de salida es 1 si cualquiera de las reglas
activas dio 1 para esa observacion.

Robustez ante contaminacion (efecto de enmascaramiento)

Hallazgo (validado por simulacion, 1000 datasets, 74,000
observaciones, 21jul2026): las reglas basadas en media/desviacion
estandar (SD_MEAN_3, SD_MEAN_2, ZSCORE, MHLBS) sufren de
enmascaramiento (masking, Hampel et al. 1986): a mayor
proporcion de outliers verdaderos en los datos, la propia presencia
de esos outliers infla la media y la desviacion estandar de
referencia, haciendo que los outliers dejen de verse "extremos"
relativos a una vara de medir ya distorsionada. La media y la SD
tienen punto de ruptura 0% -- un solo valor extremo puede desplazar
la media arbitrariamente.

En la simulacion, la sensibilidad (recall) del criterio estricto
(N_REGLAS>=3, equivalente a exigir 3 de las 5 reglas) cayo asi con
el nivel de contaminacion real de la muestra:

3% de contaminacion: recall (mean/SD) = 95.5%

5% de contaminacion: recall (mean/SD) = 85.3%

10% de contaminacion: recall (mean/SD) = 66.6%

20% de contaminacion: recall (mean/SD) = 27.4%

La regla IQR (Tukey) no sufre este problema -- tiene punto de
ruptura 25% (aguanta hasta un cuarto de datos contaminados sin
distorsionarse), por eso atip_reglas (union de las 5 reglas)
se mantiene con recall >92% incluso al 20% de contaminacion: la
propia union rescata lo que las reglas basadas en SD pierden.

Por eso, desde esta version, la referencia robusta (mediana/MAD)
es el comportamiento POR DEFECTO de atip_reglas/atip_score,
reemplazando media/SD por mediana y MAD (median absolute deviation),
escalada por la constante de consistencia 1.4826 para que sea
comparable a una SD bajo normalidad (Iglewicz & Hoaglin 1993, "How to
Detect and Handle Outliers", ASQC Press). La mediana tiene punto de
ruptura 50%. Verificado por simulacion: el recall del criterio
estricto al 20% de contaminacion sube de 27.4% (media/SD) a
95.5% (mediana/MAD).

Resguardo ante empates: si en un grupo mas de la mitad de los
valores son identicos a la mediana, el MAD sale 0, lo cual haria que
el z-score robusto se disparara a infinito para cualquier desviacion
minima. En ese caso, atip_reglas usa automaticamente media/SD
como respaldo solo para ese grupo (el resto de grupos sigue
usando mediana/MAD), y avisa en pantalla cuantas observaciones se
vieron afectadas. Este escenario es mas comun de lo que parece con
datos de encuesta muy redondeados o con muchas respuestas identicas.

Use classic para volver al comportamiento anterior
(media/SD unicamente, sin mediana/MAD ni el resguardo de empates) --
por ejemplo para replicar exactamente resultados de una version
previa del pipeline, o si por alguna razon especifica se prefiere la
referencia clasica.

Recomendacion practica: deje el default (robusto) en la gran
mayoria de casos -- con contaminacion baja (pocos casos dispersos),
clasico y robusto dan resultados casi identicos, asi que no hay costo
real en dejarlo activado siempre; con contaminacion alta o
sistematica (ej. un error de captura que afecta a toda una sede
operativa), el default robusto es sustancialmente mejor. Use
classic solo si tiene una razon especifica para no querer la
proteccion adicional.

Ejemplos

Deteccion basica sobre auto.dta (price por foreign)

. sysuse auto, clear

. atip_reglas price, group(foreign)

. list make foreign price OUTLIERS if OUTLIERS==1, sep(0)

Usando solo IQR y Mahalanobis, con ZSCORE de dos colas si se activara

. sysuse auto, clear

. atip_reglas price, group(foreign) rules(iqr mahal)

Con nlsw88.dta (wage por union)

. sysuse nlsw88, clear

. drop if missing(wage, union)

. atip_reglas wage, group(union) zsymmetric

. tab union OUTLIERS

Comparando el default (robusto) contra classic -- con contaminacion baja (como auto.dta) deberian ser similares

. sysuse auto, clear

. atip_reglas price, group(foreign)

. rename OUTLIERS OUTLIERS_robusto

. atip_reglas price, group(foreign) classic

. tab OUTLIERS OUTLIERS_robusto

Mahalanobis MULTIVARIADO (2 variables) -- fuera del alcance de atip_reglas, ilustrativo

La regla MHLBS de atip_reglas es univariada (equivale a z²
de una sola variable). El siguiente bloque en Mata muestra el caso
genuinamente multivariado -- superficie y produccion juntas,
usando su correlacion -- que detecta un productor con ambas variables
individualmente normales pero cuya COMBINACION rompe el patron
esperado (ver seccion "Ejemplos" mas abajo para el detalle numerico
completo)

. mata:

: sup = (2.0\2.5\3.0\3.5\4.0)

: prod = (7.5\10.5\11.0\15.0\15.0)

: X = sup,prod

: media = mean(X)

: S = variance(X)

: Sinv = luinv(S)

: nuevo = (4.0,8.0)

: diff = nuevo - media

: D2 = diff*Sinv*diff'

: D2

: end

. * D2 deberia salir ~74 -- muy por encima de un caso tipico (D2<2),

. * a pesar de que ni superficie=4 ni produccion=8 son individualmente

. * atipicos en sus respectivas distribuciones marginales

Nota: este bloque calcula media/covarianza usando SOLO los 5
puntos de referencia (sin el sexto), evaluando el caso nuevo por
fuera -- una referencia "externa" o "limpia". El comando
atip_mahal calcula media/covarianza incluyendo todas las
observaciones del grupo (referencia "auto-inclusiva", igual criterio
que el resto de atip_reglas), asi que con muestras chicas puede
dar un D2 bastante menor para el mismo caso, por el efecto de
enmascaramiento -- ver atip_mahal,
seccion Ejemplos para el mismo caso corrido con el comando real.

Autor / notas de desarrollo

Andres Talavera Cuya

Direccion Nacional de Censos y Encuestas -- INEI Peru

Email: atalaveracuya@gmail.com

Comandos relacionados: atip_score y atip_volcano --
instalar los tres juntos (atip_score llama a atip_reglas
internamente).

Licencia

Este modulo se distribuye bajo los terminos de la GPL v3
(https://www.gnu.org/licenses/gpl-3.0.txt).