Saltar al contenido

Indice / Practicas / EJ-3.4

EJ-3.4 · Atipicos por IQR

Practica de: 3.7 Atipicos: z-score e IQRDificultad: M

Quinientas ventas, un criterio de tres números y quince filas que caen afuera. Una de ellas está a 2.279 IQR del tercer cuartil.

Qué hay que hacer

A partir de dw_hecho_ventas.csv (500 filas), aplicar el criterio de Tukey con k = 1.5 a la columna ingreso y producir dos archivos:

Archivos

ELI5Explicado facil

Son dos preguntas distintas y por eso son dos archivos: «¿dónde ponemos la raya?» y «¿quiénes quedaron del otro lado?».

La primera se contesta antes de mirar a nadie. Si se contesta después, la raya se puede acomodar para que dé el resultado que uno esperaba.

Por qué el criterio va en un archivo aparte

Los límites se calculan una vez y no se recalculan

Sobre las 500 filas, con k = 1.5. Si se recalcularan sobre lo que sobrevive al primer filtro, el IQR se achica, los límites se cierran y aparecen atípicos nuevos - y el resultado pasa a depender de cuántas veces se corrió el script.

Iterar hasta que no queden es un criterio distinto y perfectamente válido. Lo que no se puede es hacerlo sin decirlo.

Lo que este ejercicio NO hace

FilaQué tiene¿Sale en el resultado?
venta 480ingreso = 9.999.999,99Sí: supera el límite superior por 2.279 IQR
una filaingreso = −320,00No: es un valor incoherente, pero no es un atípico por IQR

Un ingreso negativo es imposible y hay que rechazarlo - pero eso es negativo_incoherente, y es la validación del ejercicio EJ-5.8. Acá el criterio es uno solo, el IQR, y hay que aplicarlo tal cual. Mezclar criterios sin declararlos es exactamente lo que el contrato existe para evitar.

Cómo saber si te salió

python comparar.py mis_limites.json limites_iqr.json
python comparar.py mis_atipicos.csv atipicos_ingreso.csv

Antes de arrancar, mirá los datos

dw_hecho_ventas.csv (entrada) · 500 filas, se muestran 5
venta_idfechaproducto_idcliente_idcantidadingresocosto
12026-01-1325221810115.147375.11
22026-05-26282842456.661778.94
32026-06-052729172041.281072.75
42026-01-13427216084.274549.38
52026-07-311924199774.038178.44

El contrato de salida

limites_iqr.json · 9 filas

ColumnaTipoQue lleva
columnatextoExactamente la cadena 'ingreso'
kdecimalEl multiplicador: 1.5
n_validosenteroCuantos ingresos no son ausentes
q1decimalPercentil 25, 2 decimales
q3decimalPercentil 75, 2 decimales
iqrdecimalq3 - q1, 2 decimales
limite_inferiordecimalq1 - k * iqr, 2 decimales
limite_superiordecimalq3 + k * iqr, 2 decimales
n_atipicosenteroCuantos valores caen fuera de los limites

Orden de las filas: No hay filas: es un objeto. sort_keys=True fija el orden de las claves. Indice: no aplica: es un JSON, no una tabla. Con pandas la equivalencia seria to_csv(index=False)

atipicos_ingreso.csv · 15 filas

ColumnaTipoQue lleva
venta_identeroEl identificador de la venta
fechatextoLa fecha tal cual vino
producto_idtextoTal cual vino
cliente_idtextoTal cual vino
ingresodecimalEl valor atipico, 2 decimales
ladotexto'alto' si supera el limite superior, 'bajo' si esta por debajo del inferior
distancia_iqrdecimalA cuantos IQR del cuartil mas cercano esta el valor, 2 decimales. Permite ordenar por gravedad

Orden de las filas: venta_id ascendente. Los 500 venta_id del archivo son distintos, asi que el orden es total. Indice: no se escribe: to_csv(index=False)

Que hacer con cada defecto

DefectoDondeAccionAdonde va
ingresos ausentes0 celdas: la columna ingreso esta completala la conversion va igual, y n_validos lo documenta. Si hubiera ausentes, no participarian de los cuartileslimites_iqr.json, n_validos
un ingreso de 9.999.999,99venta_id 480NO NO se descarta: es el atipico que el ejercicio tiene que encontrar. Sale en atipicos_ingreso.csv con distancia_iqr de 2279.55atipicos_ingreso.csv
ingresos negativos1 fila con -320.0NO NO se descarta ni se marca aparte: el criterio de esta practica es el IQR y ese valor no lo supera. Marcarlo como negativo_incoherente es la validacion de EJ-5.8, que es otro ejerciciono aparece en atipicos_ingreso.csv
un valor exactamente igual al limite0 filas en este archivolos los limites son EXCLUSIVOS: se marca lo que esta estrictamente por debajo o por encimano aparece en atipicos_ingreso.csv
Orden de comprobacion

Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:

  1. convertir la columna a float, mandando los ausentes a NaN
  2. calcular q1 y q3 sobre los presentes, con np.percentile
  3. calcular los limites con k = 1.5, y escribirlos ANTES de filtrar
  4. recorrer las filas y quedarse con las que caen fuera

Los limites se calculan UNA vez, sobre todos los datos de entrada, y no se recalculan sobre lo que sobrevive. Si se iterara, el criterio siempre encontraria un 'mas lejos que el resto' y el resultado dependeria de cuantas veces se corrio.

Conteos clave

Si estos numeros no te dan, algo se desvio antes de que llegues a comparar los archivos.

filas de entrada500
limites_iqr.json9
atipicos_ingreso.csv15
el mas extremoventa 480, con 9.999.999,99 a 2279,55 IQR
por motivoatipico: 15

Invariantes

Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:

limites['n_validos'] == 500
limites['iqr'] == round(limites['q3'] - limites['q1'], 2)
abs(limites['limite_superior'] - (limites['q3'] + 1.5 * limites['iqr'])) < 0.01
abs(limites['limite_inferior'] - (limites['q1'] - 1.5 * limites['iqr'])) < 0.01
len(atipicos) == limites['n_atipicos'] == 15
atipicos['venta_id'].is_unique
atipicos['venta_id'].is_monotonic_increasing
not atipicos.isna().any().any()
set(atipicos['lado']) <= {'alto', 'bajo'}
(atipicos.loc[atipicos['lado'] == 'alto', 'ingreso'] > limites['limite_superior']).all()
(atipicos['distancia_iqr'] > 1.5).all()
atipicos['ingreso'].max() > 9000000
PISTASSi te trabas, abri esto
  • Los limites se calculan una sola vez, sobre las 500 filas. Recalcularlos sobre lo que queda es otro criterio, y hay que declararlo si se usa.
  • El limite inferior da -5294.20, que es negativo: es normal y no hay que corregirlo. Simplemente ningun ingreso cae ahi.
  • distancia_iqr se mide contra el cuartil mas cercano, no contra el limite ni contra la mediana: (valor - q3) / iqr para los altos.
  • La venta 480 tiene distancia_iqr 2279.55. Si tu numero es otro, revisa contra que cuartil estas midiendo.

Así tienen que quedar

limites_iqr.json (objetivo) · 2 de 1 registros
{
  "columna": "ingreso",
  "iqr": 4384.35,
  "k": 1.5,
  "limite_inferior": -5294.2,
  "limite_superior": 12243.21,
  "n_atipicos": 15,
  "n_validos": 500,
  "q1": 1282.33,
  "q3": 5666.68
}
atipicos_ingreso.csv (objetivo) · 15 filas, se muestran 5
venta_idfechaproducto_idcliente_idingresoladodistancia_iqr
182026-05-0427112690.45alto1.6
302026-04-03132012895.28alto1.65
1162026-02-1818613839.96alto1.86
1532026-05-04252513758.66alto1.85
1832026-01-21243813474.75alto1.78
El límite inferior es negativo, y está bien

Q1 − 1,5·IQR da −5.294,20. Es normal y no hay que corregirlo: simplemente ningún ingreso cae ahí, y por eso las 15 filas marcadas son todas del lado alto.

Si querés el límite real del dominio - «un ingreso no puede ser negativo» - eso es otra validación: fuera_de_rango, no atipico.

distancia_iqr

Se mide contra el cuartil más cercano, no contra el límite ni contra la mediana: (valor − q3) / iqr para los altos.

Sirve para ordenar por gravedad, y para que el 2.279,55 de la venta 480 hable por sí solo: las otras catorce están todas entre 1,54 y 1,92.