Indice / Practicas / EJ-3.4
EJ-3.4 · Atipicos por IQR
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:
limites_iqr.json- el criterio: cuartiles, IQR, los dos límites y cuántos quedan afuera.atipicos_ingreso.csv- las 15 filas marcadas, con de qué lado cayeron y a cuántos IQR están.
Archivos
- Entrada ·
dw_hecho_ventas.csv - Objetivo ·
limites_iqr.json - Objetivo ·
atipicos_ingreso.csv - Herramienta ·
comparar.py
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
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
| Fila | Qué tiene | ¿Sale en el resultado? |
|---|---|---|
| venta 480 | ingreso = 9.999.999,99 | Sí: supera el límite superior por 2.279 IQR |
| una fila | ingreso = −320,00 | No: 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
| venta_id | fecha | producto_id | cliente_id | cantidad | ingreso | costo |
|---|---|---|---|---|---|---|
| 1 | 2026-01-13 | 25 | 22 | 18 | 10115.14 | 7375.11 |
| 2 | 2026-05-26 | 28 | 28 | 4 | 2456.66 | 1778.94 |
| 3 | 2026-06-05 | 27 | 29 | 17 | 2041.28 | 1072.75 |
| 4 | 2026-01-13 | 4 | 27 | 21 | 6084.27 | 4549.38 |
| 5 | 2026-07-31 | 19 | 24 | 19 | 9774.03 | 8178.44 |
El contrato de salida
limites_iqr.json · 9 filas
| Columna | Tipo | Que lleva |
|---|---|---|
columna | texto | Exactamente la cadena 'ingreso' |
k | decimal | El multiplicador: 1.5 |
n_validos | entero | Cuantos ingresos no son ausentes |
q1 | decimal | Percentil 25, 2 decimales |
q3 | decimal | Percentil 75, 2 decimales |
iqr | decimal | q3 - q1, 2 decimales |
limite_inferior | decimal | q1 - k * iqr, 2 decimales |
limite_superior | decimal | q3 + k * iqr, 2 decimales |
n_atipicos | entero | Cuantos valores caen fuera de los limites |
atipicos_ingreso.csv · 15 filas
| Columna | Tipo | Que lleva |
|---|---|---|
venta_id | entero | El identificador de la venta |
fecha | texto | La fecha tal cual vino |
producto_id | texto | Tal cual vino |
cliente_id | texto | Tal cual vino |
ingreso | decimal | El valor atipico, 2 decimales |
lado | texto | 'alto' si supera el limite superior, 'bajo' si esta por debajo del inferior |
distancia_iqr | decimal | A cuantos IQR del cuartil mas cercano esta el valor, 2 decimales. Permite ordenar por gravedad |
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| ingresos ausentes | 0 celdas: la columna ingreso esta completa | la la conversion va igual, y n_validos lo documenta. Si hubiera ausentes, no participarian de los cuartiles | limites_iqr.json, n_validos |
| un ingreso de 9.999.999,99 | venta_id 480 | NO NO se descarta: es el atipico que el ejercicio tiene que encontrar. Sale en atipicos_ingreso.csv con distancia_iqr de 2279.55 | atipicos_ingreso.csv |
| ingresos negativos | 1 fila con -320.0 | NO 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 ejercicio | no aparece en atipicos_ingreso.csv |
| un valor exactamente igual al limite | 0 filas en este archivo | los los limites son EXCLUSIVOS: se marca lo que esta estrictamente por debajo o por encima | no aparece en atipicos_ingreso.csv |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- convertir la columna a float, mandando los ausentes a NaN
- calcular q1 y q3 sobre los presentes, con np.percentile
- calcular los limites con k = 1.5, y escribirlos ANTES de filtrar
- 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
| filas de entrada | 500 |
|---|---|
| limites_iqr.json | 9 |
| atipicos_ingreso.csv | 15 |
| el mas extremo | venta 480, con 9.999.999,99 a 2279,55 IQR |
| por motivo | atipico: 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
{
"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
}| venta_id | fecha | producto_id | cliente_id | ingreso | lado | distancia_iqr |
|---|---|---|---|---|---|---|
| 18 | 2026-05-04 | 27 | 1 | 12690.45 | alto | 1.6 |
| 30 | 2026-04-03 | 13 | 20 | 12895.28 | alto | 1.65 |
| 116 | 2026-02-18 | 18 | 6 | 13839.96 | alto | 1.86 |
| 153 | 2026-05-04 | 25 | 25 | 13758.66 | alto | 1.85 |
| 183 | 2026-01-21 | 24 | 38 | 13474.75 | alto | 1.78 |
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.