La campana de Gauss es la figura más famosa de la estadística, y también la más aplicada donde no toca 🔔
la altura de la campana en cada punto, que solo depende de a cuántas desviaciones del centro estás
Importa por una razón muy concreta: media docena de herramientas del libro la dan por supuesta. La regla del 68%, los intervalos de confianza clásicos, la prueba t, la detección de atípicos por desviaciones. Si tus datos no son campana, esas herramientas no se rompen con un error rojo: te devuelven un número equivocado con toda la calma 😶
Así que vamos a aprender a comprobarlo. Y de paso vamos a ver que la forma "oficial" de comprobarlo, la prueba de normalidad, es bastante inútil.
Y contéstate esto antes de seguir: ¿cuántas veces has usado la regla del 68% sin mirar si tus datos eran una campana? Yo, muchas, y por eso este capítulo existe 🔔
Primero, mirarlo
import pandas as pd import numpy as np from scipy import stats URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv' def carga_limpia(url): """La misma del capítulo 2.""" v = pd.read_csv(url).drop_duplicates() v['ciudad'] = (v['ciudad'].str.strip().str.lower() .str.normalize('NFKD') .str.encode('ascii', 'ignore').str.decode('utf-8')) v['monto'] = pd.to_numeric(v['monto'].str.replace(',', '.')) for col in ['fecha', 'fecha_ultima_compra']: f = pd.to_datetime(v[col], format='%Y-%m-%d', errors='coerce') falta = f.isna() & v[col].notna() f[falta] = pd.to_datetime(v.loc[falta, col], format='%d/%m/%Y', errors='coerce') v[col] = f return v v = carga_limpia(URL) def histograma(serie, tramos=10): """Un histograma de texto, que se ve igual de bien y no necesita gráficos.""" cuenta, bordes = np.histogram(serie.dropna(), bins=tramos) for i in range(tramos): print('%8.0f a %8.0f | %4d %s' % (bordes[i], bordes[i + 1], cuenta[i], '#' * int(cuenta[i] / 20))) histograma(v['monto'])
-2498 a -1824 | 2
-1824 a -1151 | 0
-1151 a -477 | 3
-477 a 196 | 474 #######################
196 a 869 | 1571 ##############################################################################
869 a 1543 | 440 ######################
1543 a 2216 | 288 ##############
2216 a 2890 | 173 ########
2890 a 3563 | 43 ##
3563 a 4237 | 6
Con eso ya sabes que monto no es una campana, y no hizo falta
ninguna prueba 👀
Una campana sube y baja simétrica. Esto sube de golpe, tiene su pico entre 196 y 869, y luego baja con una cola larga por la derecha que llega hasta 4.237. Y por la izquierda hay cinco filas sueltas en territorio negativo.
Los dos números que resumen la forma
for col in ['monto', 'unidades', 'satisfaccion', 'descuento']: s = v[col].dropna() print('%-13s asimetria %7.4f | curtosis %7.4f' % (col, s.skew(), s.kurtosis()))
monto asimetria 1.3405 | curtosis 1.5458 unidades asimetria 0.1843 | curtosis -0.3351 satisfaccion asimetria -0.0149 | curtosis -1.3089 descuento asimetria -0.0157 | curtosis -1.1604
La asimetría dice hacia dónde cae la cola. Cero es simétrico, positivo es cola a la derecha, negativo a la izquierda. Como regla de campo, entre -0,5 y 0,5 es bastante simétrico y por encima de 1 la cola ya es evidente.
La curtosis (la que devuelve pandas es la de exceso) dice si la campana es puntiaguda o achatada. Cero es como la normal, positivo es más picuda con colas más pesadas, negativo es más plana.
Ahora léelo como si fueran cuatro personas 🙂
- 💰
monto: asimetría 1,3405. Cola a la derecha, confirmado. - 📦
unidades: 0,1843 y -0,3351. Esto sí parece una campana. - ⭐
satisfaccion: simétrica pero con curtosis -1,3089, o sea muy plana. Eso no es campana, es una meseta: los cinco valores salen casi igual de veces. - 🏷️
descuento: lo mismo, plana.
Ahora la prueba oficial, y la decepción
La prueba de Shapiro-Wilk contesta "¿podrían estos datos venir de una normal?". Si el valor p es chico, la respuesta es no.
Se la aplicamos a unidades, la que mejor pinta tenía:
print(stats.shapiro(v['unidades']))
ShapiroResult(statistic=np.float64(0.9869264852139725), pvalue=np.float64(5.180807008091816e-16))
p = 0,00000000000000052 😬
O sea que la prueba dice, con toda la contundencia del mundo, que
unidades no es normal. La misma columna que tenía
asimetría 0,18 y que en el capítulo 4 cumplía la regla del 68% clavada
(68,70%).
¿Quién miente? Ninguno. Mira lo que pasa cuando le doy los mismos datos en trozos de distinto tamaño:
for n in [20, 50, 100, 300, 1000, 3000]: muestra = v['unidades'].sample(n, random_state=7) print('con %5d filas -> p = %.6f' % (n, stats.shapiro(muestra).pvalue))
con 20 filas -> p = 0.442212 con 50 filas -> p = 0.235741 con 100 filas -> p = 0.045462 con 300 filas -> p = 0.002306 con 1000 filas -> p = 0.000000 con 3000 filas -> p = 0.000000
Ahí está el truco entero 🎩
Los mismos datos. Con 50 filas la prueba dice "normal, adelante". Con 1.000 dice "de ninguna manera". La columna no cambió: cambió cuánta evidencia tienes.
Y no es que Shapiro sea mala. Mira qué pasa con datos que sí vienen de una normal de verdad:
generador = np.random.default_rng(7) for n in [100, 1000, 3000]: print('normal de verdad con %5d datos -> p = %.4f' % (n, stats.shapiro(generador.normal(size=n)).pvalue))
normal de verdad con 100 datos -> p = 0.8599 normal de verdad con 1000 datos -> p = 0.4335 normal de verdad con 3000 datos -> p = 0.3883
Con datos normales de verdad, el p se queda alto por muchos datos que le des ✅
O sea que la prueba funciona perfectamente. Lo que pasa es que responde a una pregunta que no es la tuya:
| Lo que crees que preguntas | Lo que la prueba contesta |
|---|---|
| ¿Mis datos son normales? | ¿Tengo evidencia suficiente para afirmar que no lo son exactamente? |
Ningún dato real es exactamente normal. Con suficientes filas siempre se nota. Por eso, en la práctica: mira el histograma y la asimetría, y usa la prueba como acompañamiento, nunca como sentencia 🧭
Guárdate esta idea, porque es la misma que va a salir en el capítulo 14 con todas las pruebas: significativo no quiere decir importante. Con muchos datos, todo sale significativo.
Lo que sí decide: comparar cuantiles
Mi comprobación favorita, porque contesta la pregunta práctica de verdad: "¿me equivoco mucho si trato esto como normal?"
u = v['unidades'] m, s = u.mean(), u.std() for q in [0.05, 0.25, 0.50, 0.75, 0.95]: print('cuantil %.2f -> observado %6.2f | si fuera normal %6.2f' % (q, u.quantile(q), stats.norm.ppf(q, m, s)))
cuantil 0.05 -> observado 2.00 | si fuera normal 2.10 cuantil 0.25 -> observado 7.00 | si fuera normal 7.71 cuantil 0.50 -> observado 12.00 | si fuera normal 11.60 cuantil 0.75 -> observado 15.00 | si fuera normal 15.50 cuantil 0.95 -> observado 21.00 | si fuera normal 21.10
Mira qué cerca 😍 En los extremos, que es donde más duele equivocarse, la diferencia es de una décima de unidad.
Entonces, ¿es unidades normal? Formalmente no, y Shapiro tiene
razón. ¿Puedo tratarla como normal para decidir cosas de negocio?
Perfectamente.
Esa es la respuesta madura, y es la que quiero que te lleves: la normalidad no es un sí o un no, es cuánto me cuesta suponerla 💡
Cuando no hay campana: transformar
Volvamos a monto, que sí estaba torcido de verdad. Hay un truco
clásico para colas por la derecha: aplicar el logaritmo.
positivos = v.loc[v['monto'] > 0, 'monto'] print('filas positivas: %d de %d' % (len(positivos), len(v))) print('asimetria antes: %.4f' % positivos.skew()) print('asimetria despues: %.4f' % np.log(positivos).skew())
filas positivas: 2979 de 3000 asimetria antes: 1.4278 asimetria despues: -0.1585
De 1,4278 a -0,1585. El logaritmo aplasta la cola y deja algo casi simétrico 🪄
Funciona porque el logaritmo comprime los números grandes mucho más que los chicos: la distancia entre 100 y 1.000 pasa a ser la misma que entre 1.000 y 10.000.
Fíjate en el precio de la operación: hay que tirar 21 filas, las que tienen monto negativo o cero, porque el logaritmo de un número negativo no existe. Nunca transformes sin contar cuántas filas te dejas 🧾
Y hay un regalo escondido en esto:
print('mediana de las positivas: %.2f' % positivos.median()) print('exp(media del logaritmo): %.2f' % np.exp(np.log(positivos).mean()))
mediana de las positivas: 536.65 exp(media del logaritmo): 538.39
Casi el mismo número. Lo que hiciste al promediar en escala logarítmica y volver fue calcular la media geométrica, la del capítulo 3. Y en una distribución con cola por la derecha, la media geométrica cae encima de la mediana 🎯
Lo que cuesta transformar es la interpretación. Después de la transformación tus resultados están en "log de soles", que no significa nada para nadie. Hay que deshacerla para contar el resultado, y ahí es donde se cometen errores. Yo transformo solo cuando la herramienta lo necesita de verdad.
El error del capítulo
Vamos a intentar partir la satisfacción en diez tramos iguales, que es lo que harías para ver su forma con más detalle:
pd.qcut(v['satisfaccion'], 10)
ValueError: Bin edges must be unique: Index([1.0, 1.0, 1.0, 2.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0], dtype='float64', name='satisfaccion'). You can drop duplicate edges by setting the 'duplicates' kwarg
Este error es de los buenos, porque te está enseñando algo del dato y no del código 🎓
qcut parte en tramos con la misma cantidad de filas cada
uno. Para hacer diez tramos necesita diez cortes distintos, y
satisfaccion solo tiene cinco valores posibles. Al buscar el borde
del decil 10 y del decil 20 encuentra el mismo 1.0 en los dos, y se planta.
La tentación es hacerle caso al mensaje y poner
duplicates='drop'. No lo hagas sin pensar: eso taparía el problema y
te dejaría tramos de tamaños muy distintos disfrazados de deciles.
Lo correcto es aceptar lo que el error te está diciendo: esa columna
no tiene diez tramos, tiene cinco categorías. Se cuenta con
value_counts y se acabó.
Y dos más, de intentar meter en la campana lo que no cabe
stats.norm.fit(v['ciudad'])
TypeError: ufunc 'isfinite' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
Ajustar una normal es buscar la media y la desviación que mejor encajan, y las dos son cuentas. Sobre nombres de ciudad no hay nada que ajustar, y scipy lo dice con un mensaje bastante peor de lo que merece la pregunta 😅
stats.zscore(v['ciudad'])
TypeError: unsupported operand type(s) for /: 'str' and 'int'
La puntuación z es restar la media y dividir entre la desviación, y ahí se ve la división imposible en el mensaje. Todo este capítulo vive sobre variables numéricas, y las categóricas tienen su propio capítulo en el 13.
Practica 💪
1. ¿De qué forma es la satisfacción?
Píntala con el histograma de texto y di qué forma tiene.
print(v['satisfaccion'].value_counts().sort_index())
satisfaccion 1.0 560 2.0 537 3.0 551 4.0 558 5.0 563 Name: count, dtype: int64
560, 537, 551, 558, 563. Es plana: los cinco valores salen prácticamente las mismas veces 📏
A eso se le llama distribución uniforme, y es lo contrario de una campana. En una campana el centro tiene muchos más casos que los extremos; aquí un 1 es tan frecuente como un 3.
Esto tiene una lectura de negocio incómoda: si tus clientes puntúan absolutamente al azar entre 1 y 5, la encuesta no está midiendo satisfacción, está midiendo ruido. Una encuesta real casi siempre sale torcida hacia arriba, porque la gente contenta responde más.
Y explica el -1,3089 de curtosis del principio. Vamos a comprobar que ese número es justo el de una uniforme:
generador = np.random.default_rng(7) uniforme = pd.Series(generador.integers(1, 6, size=3000)) print('satisfaccion -> curtosis %.4f | asimetria %.4f' % (v['satisfaccion'].kurtosis(), v['satisfaccion'].skew())) print('uniforme 1-5 -> curtosis %.4f | asimetria %.4f' % (uniforme.kurtosis(), uniforme.skew()))
satisfaccion -> curtosis -1.3089 | asimetria -0.0149 uniforme 1-5 -> curtosis -1.2944 | asimetria -0.0219
-1,3089 contra -1,2944. Clavado 🎯
O sea que la curtosis no solo te dice "no es normal": te dice qué es. Un -1,3 en una variable de cinco valores es la firma de una uniforme.
2. La normalidad dentro de un segmento
En el capítulo 3 vimos que dentro de cada segmento la media y la mediana coincidían. ¿Quiere eso decir que cada segmento es normal?
for seg in ['Bodega', 'Mayorista']: s = v.loc[v['segmento'] == seg, 'monto'] print('%-10s asimetria %7.4f | curtosis %7.4f | minimo %8.2f' % (seg, s.skew(), s.kurtosis(), s.min()))
Bodega asimetria -0.7752 | curtosis 3.4590 | minimo -224.61 Mayorista asimetria -0.4560 | curtosis 3.0510 | minimo -2497.72
Pues no, y la respuesta es más interesante de lo que esperaba 🤔
Los dos segmentos tienen asimetría negativa, o sea cola a la izquierda, justo al revés que el total. Y curtosis alrededor de 3, que es mucha: colas pesadas.
La explicación está en la tercera columna: los mínimos son negativos. Esas ventas negativas, que son poquísimas, tiran de la cola izquierda dentro de cada segmento.
Y en el total esa cola izquierda desaparecía porque la aplastaba la cola derecha enorme que creaba la mezcla de segmentos.
Conclusión práctica: simétrico no es lo mismo que normal. Que la media y la mediana coincidan es necesario pero no suficiente. Hay que mirar también las colas 👀
3. Cuánto te cuesta suponer normalidad en el monto
Haz con monto la comparación de cuantiles que
hicimos con unidades. ¿Dónde se rompe más?
m, s = v['monto'].mean(), v['monto'].std() for q in [0.05, 0.25, 0.50, 0.75, 0.95, 0.99]: obs = v['monto'].quantile(q) teo = stats.norm.ppf(q, m, s) print('cuantil %.2f -> observado %8.2f | normal %8.2f | error %+8.2f' % (q, obs, teo, obs - teo))
cuantil 0.05 -> observado 119.03 | normal -433.15 | error +552.18 cuantil 0.25 -> observado 252.69 | normal 296.55 | error -43.86 cuantil 0.50 -> observado 533.63 | normal 803.76 | error -270.13 cuantil 0.75 -> observado 1068.68 | normal 1310.97 | error -242.29 cuantil 0.95 -> observado 2431.76 | normal 2040.67 | error +391.08 cuantil 0.99 -> observado 3058.59 | normal 2553.15 | error +505.44
Aquí sí duele 💥
Suponer normalidad dice que el 5% de las ventas debería estar por debajo de -433,15 soles. En la realidad el percentil 5 es +119,03, o sea que la normal se equivoca en 552 soles justo ahí. Y por arriba hace lo contrario: el percentil 99 real es 3.058,59 y la normal lo pone en 2.553,15, o sea que se queda 505 soles corta.
Fíjate en el patrón, que es la firma de una cola por la derecha: la normal se inventa valores muy negativos que no existen y se queda corta en los muy grandes que sí existen.
El motivo es de fondo: una normal va de menos infinito a más infinito, y una venta no puede ser muy negativa. Siempre que tu variable tenga un tope natural (no puede bajar de cero, no puede pasar de 100), la normal va a mentir en esa punta.
Compáralo con unidades, donde el error era de una décima. Misma
suposición, dos costes completamente distintos ⚖️
4. Normaliza tú un dato: la puntuación z
Convierte el monto a "cuántas desviaciones estoy del promedio" y comprueba qué le pasa a la media y a la desviación.
z = (v['monto'] - v['monto'].mean()) / v['monto'].std() print('media de z: %.6f' % z.mean()) print('desviacion de z: %.6f' % z.std()) print('asimetria de z: %.4f' % z.skew()) print() print('la venta mas grande esta a %.2f desviaciones' % z.max()) print('la mas chica, a %.2f' % z.min())
media de z: -0.000000 desviacion de z: 1.000000 asimetria de z: 1.3405 la venta mas grande esta a 4.57 desviaciones la mas chica, a -4.39
La media queda en 0 y la desviación en 1, que es lo que hace la puntuación z: poner cualquier variable en la misma escala 📐
Pero mira la tercera línea, que es la importante: la asimetría sigue siendo 1,3405, exactamente la de antes.
Estandarizar no normaliza. Mueve y estira, pero no cambia la
forma. Es un malentendido de los grandes, y lo alimenta el nombre de
StandardScaler en scikit-learn, que muchísima gente cree que
convierte los datos en normales.
La z sirve para comparar variables de escalas distintas y para los modelos que lo necesitan. No sirve para arreglar una cola 🚫
5. El test que se rinde con muchos datos
Reproduce el fenómeno del capítulo con datos fabricados: genera una normal con un defecto minúsculo y mira desde qué tamaño lo detecta Shapiro.
generador = np.random.default_rng(11) for n in [100, 500, 2000, 5000]: casi = np.concatenate([ generador.normal(size=int(n * 0.99)), generador.normal(loc=6, size=int(n * 0.01)), ]) print('n=%6d con 1%% de intrusos -> p = %.6f' % (n, stats.shapiro(casi).pvalue))
n= 100 con 1% de intrusos -> p = 0.000005 n= 500 con 1% de intrusos -> p = 0.000000 n= 2000 con 1% de intrusos -> p = 0.000000 n= 5000 con 1% de intrusos -> p = 0.000000
Con solo un 1% de datos "intrusos" el test ya se cae con 100 filas 😮
Y aquí está lo que hay que sacar de todo esto. La prueba de normalidad es extremadamente sensible: detecta desviaciones que a ti te dan exactamente igual para tomar una decisión.
Un p chiquito no significa "esto está mal, no lo uses". Significa "hay algo que no es exactamente normal". Que eso importe o no lo decides tú, mirando la comparación de cuantiles del ejercicio 3 🧑⚖️
6. Tu chequeo de forma, en una función
Junta todo en algo que puedas correr sobre cualquier columna y te diga qué tienes delante.
def que_forma_tiene(serie, nombre): s = serie.dropna() a, k = s.skew(), s.kurtosis() if abs(a) > 1: forma = 'cola larga hacia la ' + ('derecha' if a > 0 else 'izquierda') elif k < -1: forma = 'plana, parece uniforme' elif abs(a) < 0.5 and abs(k) < 1: forma = 'razonablemente acampanada' else: forma = 'algo torcida, mirala' return ('%-13s asimetria %+7.4f | curtosis %+7.4f -> %s' % (nombre, a, k, forma)) for col in ['monto', 'unidades', 'satisfaccion', 'descuento']: print(que_forma_tiene(v[col], col))
monto asimetria +1.3405 | curtosis +1.5458 -> cola larga hacia la derecha unidades asimetria +0.1843 | curtosis -0.3351 -> razonablemente acampanada satisfaccion asimetria -0.0149 | curtosis -1.3089 -> plana, parece uniforme descuento asimetria -0.0157 | curtosis -1.1604 -> plana, parece uniforme
Cuatro columnas etiquetadas en una pasada 🙌
Los umbrales (1 para la asimetría, 1 para la curtosis, 0,5) son convenciones de campo, no leyes. Lo importante es que estén escritos y que los uses igual siempre, en vez de decidir a ojo según el día.
Y fíjate en lo que no hace esta función: no llama a ninguna prueba de normalidad. Después de lo de arriba, ya sabes por qué 😌
7. El 68% sobre tres columnas
Comprueba en tres columnas qué porcentaje cae dentro de una desviación, y compáralo con el 68,3% que promete la campana.
for col in ['monto', 'unidades', 'satisfaccion']: s = v[col].dropna() dentro = ((s > s.mean() - s.std()) & (s < s.mean() + s.std())).mean() print('%-13s dentro de una desviacion: %.1f%% (la campana dice 68,3%%)' % (col, dentro * 100))
monto dentro de una desviacion: 81.8% (la campana dice 68,3%) unidades dentro de una desviacion: 68.7% (la campana dice 68,3%) satisfaccion dentro de una desviacion: 59.4% (la campana dice 68,3%)
Una acierta y las otras dos fallan por lados contrarios 😬
unidades da 68,7% y la campana le queda como un guante. El monto da 81,8%, o sea que hay más concentración en el centro de la que la campana espera, porque la cola estira la desviación. Y satisfaccion da 59,4%, que falla por lo contrario: solo tiene cinco valores posibles y no hay forma de que una escala del 1 al 5 se parezca a una curva continua.
Esta comprobación cabe en tres líneas y contesta mejor que cualquier prueba de normalidad, que es de lo que va este capítulo 🔔
8. Cuando el logaritmo sí endereza
Aplica logaritmo al monto y mira si la asimetría se va.
import numpy as np log = np.log(v['monto'][v['monto'] > 0]) print('asimetria del monto: %.3f' % v['monto'].skew()) print('asimetria del logaritmo: %.3f' % log.skew()) print('la prueba de normalidad sobre el logaritmo: p = %.6f' % stats.shapiro(log.sample(500, random_state=0)).pvalue)
asimetria del monto: 1.340 asimetria del logaritmo: -0.158 la prueba de normalidad sobre el logaritmo: p = 0.000011
Mira las dos primeras líneas y después la tercera, que se contradicen 🤨
La asimetría se fue casi entera: de 1,340 a -0,158, o sea que el logaritmo enderezó la cola, que es justo para lo que se usa. Y aun así la prueba de normalidad dice que no, con un p de 0,000011.
Las dos cosas son verdad a la vez y esa es la lección: la prueba contesta "¿es exactamente normal?" y la respuesta con 500 datos casi siempre es no. Lo que tú necesitas saber es "¿se parece lo bastante para lo que voy a hacer?", y eso lo contesta la asimetría, no el valor p 📏
El 68% que salió 81,6%
Antes de cerrar, la trampa de este capítulo. La regla es correcta, la cuenta es correcta, y el resultado no cuadra 🔔
La trampa
Aplicas la regla que todo el mundo sabe: entre la media menos una desviación y la media más una está el 68% de los datos. La usas para poner el rango normal de una venta.
mu, sd = v['monto'].mean(), v['monto'].std() dentro = ((v['monto'] > mu - sd) & (v['monto'] < mu + sd)).mean() print(round(dentro * 100, 1)) # 81.8
Qué está mal
81,8% donde la campana prometía 68,3% 🔔 Trece puntos y medio de diferencia, y no está mal calculado.
La regla del 68-95-99,7 no es una ley de la naturaleza: es una propiedad de la campana. Y el monto no es una campana, es una distribución con cola a la derecha (asimetría 1,3405), así que hay muchos más datos apelotonados cerca del centro de lo que la campana esperaría.
Hay una comprobación de diez segundos que lo canta: mu - 2 * sd da -700,22 soles. Si tu rango normal incluye una venta negativa, la campana no es la forma de tus datos.
Antes de usar cualquier regla que venga de la campana, mira el histograma. Y si sale con cola, usa percentiles: el rango del 5 al 95 no supone ninguna forma y siempre dice la verdad.
Comprueba que lo tienes
Shapiro suspende unidades con p = 5,2e-16 y la aprueba con p = 0,4422 si le das solo 20 filas. ¿Qué concluyes?
- Que la prueba mide cuánta evidencia tengo, no si los datos son normales
- Que con 20 filas la columna sí es normal y con 3.000 deja de serlo
- Que la prueba de Shapiro está mal implementada
- Que hay que usar siempre muestras de 20 para estas pruebas
Lo que te llevas
- 👀 El histograma decide más que cualquier prueba. Diez líneas de texto y ya
sabías que
montono era una campana. - 📐 Asimetría dice hacia dónde cae la cola; curtosis, si es picuda o plana. Una curtosis de -1,3 en cinco valores es la firma de una uniforme.
- 🎭 Las pruebas de normalidad no contestan lo que crees. Los mismos datos dan p = 0,44 con 50 filas y p = 0,000000 con 1.000.
- 🧭 La pregunta útil no es "¿es normal?" sino "¿cuánto me cuesta suponer que lo es?". En unidades el error de cuantiles era de una décima; en monto, la normal se inventaba ventas de -433 soles.
- 🪄 El logaritmo endereza colas por la derecha: la asimetría del monto pasó de 1,4278 a -0,1585. Pero cuesta 21 filas y complica la interpretación.
- 📏 Estandarizar no normaliza. La z deja media 0 y desviación 1, y la asimetría intacta.
Y si de todo el capítulo te llevas una sola frase, que sea esta:
La campana no es cómo son los datos. Es una suposición, y se comprueba.
Y para mirar la forma de tus datos sin escribir código, un histograma en un tablero hace exactamente el mismo trabajo: guía de Power BI 📈
Qué viene ahora
Han salido tres veces ya: las ventas negativas y las de cuatro mil soles. En el capítulo 6 vamos a por ellas, con las tres formas de detectarlas y la pregunta que casi nadie hace, que es si hay que quitarlas o no 🎯
Preguntas frecuentes
¿Qué es un histograma?
Un gráfico de barras pegadas donde cada barra cuenta cuántos datos caen dentro de un rango. Sirve para ver de un vistazo la forma que tienen tus datos: si están centrados, si tienen cola, si hay dos grupos escondidos.
¿Cómo se hace un histograma?
Se parte el rango de los datos en intervalos del mismo ancho, se cuenta cuántos datos caen en cada uno y se dibuja una barra por intervalo. Lo único que hay que elegir es cuántos intervalos, y esa decisión cambia lo que ves.
¿Qué diferencia hay entre un histograma y un gráfico de barras?
Las barras del histograma van pegadas porque el eje es continuo: representan tramos de un número. Las de un gráfico de barras van separadas porque son categorías sueltas, como canal o ciudad.
¿Qué es un histograma de frecuencias?
Es el mismo histograma llamado por su nombre completo: la altura de cada barra es la frecuencia, o sea cuántas veces aparece un valor dentro de ese tramo.
¿Qué es la campana de Gauss?
El nombre coloquial de la distribución normal, por la forma que tiene su histograma: simétrica, con casi todo en el medio y colas que se van apagando a los lados.
¿Cómo sé si mis datos son normales?
Mirando el histograma primero y con la prueba de Shapiro Wilk después. Y ojo con lo que contesta la prueba: con muchos datos casi siempre dice que no, y eso no significa que no te sirvan.
¿Cómo se hace un histograma en Excel?
Seleccionas los datos, Insertar, gráficos estadísticos, histograma. Excel elige los intervalos solo, y eso es justo lo que conviene revisar a mano: cambiar el ancho cambia la forma que ves.
Practica este capítulo 📓
Todo el código de arriba en un cuaderno que corre de principio a fin, y los ejercicios con una celda vacía para que los hagas tú. Se abre en Google Colab de un clic y no hay que instalar nada. Donde veas %%revisa, escribe tu respuesta y el cuaderno te dice si te salió.
¿Prefieres trabajar en tu máquina? Bájate el cuaderno de práctica o el de soluciones. Todos están también en github.com/soymissyera/MissYeraEjercicios.