Contrastar una hipótesis
En resumen: Un contraste de hipótesis pregunta si una diferencia podría ser casualidad: la hipótesis nula dice «no hay diferencia», y el valor p es cuántas veces el azar da una diferencia al menos así de grande.
Problema
«¿Es el viernes de verdad más fuerte?»
El jefe quiere poner una persona más en el empaquetado los viernes y quitar una los domingos. Antes pregunta: ¿el viernes es de verdad más fuerte, o este año simplemente salió así? Esto es cuántos pedidos entran de media por día, según el día de la semana (0 es lunes, 6 es domingo):
Contamos todos los pedidos, también los cancelados: la pregunta es cuántos llegan, y un pedido cancelado también se recibe y se tramita. Un viernes entran de media 36,35 pedidos, y un domingo 24,62. El año tiene 52 viernes y también 52 domingos. Cada día es un poco distinto: un viernes trae 22 pedidos, otro 68. ¿Es real una diferencia de 11,73 pedidos, o es ruido como en la lección sobre muestras?
La idea
Si el día no importara
La pregunta «¿es el viernes más fuerte que el domingo?» es una hipótesis. Para comprobarla, partes de la explicación aburrida: el día no importa y la diferencia es casualidad. Esa es la hipótesis nula.
Si la hipótesis nula es cierta, las etiquetas «viernes» y «domingo» no significan nada. Puedes mezclarlas: pon los 104 días en un montón, mézclalos y di que los primeros 52 son «viernes» y los otros 52 «domingos». La diferencia entre las dos mitades es la diferencia que da el azar por sí solo:
La diferencia real es de 11,73 pedidos. Tres mezclas dan 0,50, 1,23 y 1,46. np.concatenate pega los dos arreglos en uno, y rng.permutation devuelve una copia mezclada.
La idea
10 000 mezclas y el valor p
Tres mezclas son pocas. Haces 10 000 de una vez: np.tile repite los 104 días en 10 000 filas, y rng.permuted(..., axis=1) mezcla cada fila por separado. Luego un solo cálculo da la diferencia de cada fila:
La mayor diferencia de 10 000 mezclas es de 7,50 pedidos. La real es de 11,73. La proporción de mezclas con una diferencia al menos así de grande es el valor p: aquí es 0, es decir, menos de 1 entre 10 000. Si el día no importara, una diferencia así casi nunca saldría. La conclusión: el viernes es de verdad más fuerte.
Este enfoque se llama prueba de permutaciones. np.abs está ahí porque la pregunta es «¿hay una diferencia?», sin decir de antemano en qué dirección. El umbral habitual es 0,05: por debajo, dices que la diferencia es estadísticamente significativa.
Pruébalo
Lunes y sábado
La diferencia entre lunes y sábado es menor. ¿Es casualidad? Guarda en gap la diferencia del número medio de pedidos por día (lunes menos sábado), en gaps las diferencias de 10 000 mezclas y en p el valor p.
Solución
import numpy as np
import pandas as pd
orders = pd.read_csv('year_orders.csv')
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
daily = orders.set_index('ordered_at').resample('D').size()
weekday = daily.index.dayofweek.rename('weekday')
mon = daily[weekday == 0].to_numpy()
sat = daily[weekday == 5].to_numpy()
gap = mon.mean() - sat.mean()
both = np.concatenate([mon, sat])
rng = np.random.default_rng(42)
shuffled = rng.permuted(np.tile(both, (10_000, 1)), axis=1)
gaps = shuffled[:, :len(mon)].mean(axis=1) - shuffled[:, len(mon):].mean(axis=1)
p = (np.abs(gaps) >= abs(gap)).mean()
print(round(gap, 2), p)El lunes tiene de media 3,46 pedidos más que el sábado. De 10 000 mezclas, cerca del 1 % (p = 0,0104) da una diferencia al menos así de grande. Eso está por debajo de 0,05: hay evidencia de que el sábado es más flojo. Hay 52 lunes y 53 sábados, así que cortas en len(mon), no en 52. Sin rng.permuted todas las filas son iguales y p es 1, y sin np.abs solo cuentas una dirección.
La idea
Cuando no hay diferencia
El domingo hay menos pedidos. ¿Son también más pequeños los propios pedidos? Aquí se comparan 10 459 pedidos sin los cancelados, no días, así que una tabla de números de 2000 × 10 459 ocuparía unos 170 MB. Un bucle con 2000 mezclas de la máscara tarda menos de un segundo:
Hay 1204 pedidos de domingo, con una media de 10,83 €, y los demás tienen de media 10,60 €. La diferencia es de 0,23 € y p = 0,425: en el 42,5 % de las mezclas la diferencia es al menos así de grande. Es ruido corriente.
La conclusión honesta es «no hay evidencia de que el pedido del domingo sea distinto». No es «hemos demostrado que son iguales»: puede haber una diferencia pequeña, solo que estos datos no la muestran. El domingo la gente pide con menos frecuencia, y no hay evidencia de que pida menos.
La idea
Lo que p no dice
El valor p responde una pregunta estrecha: cuán sorprendentes son los datos si la hipótesis nula es cierta. No es dos cosas con las que a menudo se confunde:
- no es la probabilidad de que la hipótesis sea cierta: p = 0,425 no significa «un 42,5 % de probabilidad de que no haya diferencia»;
- no es el tamaño de la diferencia: una p muy pequeña significa «no es casualidad», no «una diferencia grande».
Cuán grande es la diferencia lo dice la propia diferencia con un intervalo. Con el bootstrap de la lección anterior, por separado para los viernes y los domingos:
El viernes tiene de media 11,73 pedidos más, con un intervalo del 95 % de 9,00 a 14,56. Es 1,48 veces el domingo. Con este número el jefe decide a cuánta gente pone, y p solo dice que no está persiguiendo ruido.
Más precisamente, para curiosos
El umbral de 0,05 es una convención, no una ley de la naturaleza. Con p = 0,06 los datos no son «casi evidencia», y con p = 0,04 no son «seguramente ciertos». Con muchísimos datos, incluso una diferencia diminuta da una p pequeña: con un millón de pedidos en cada grupo y una diferencia de 10 céntimos la prueba será «significativa», pero de ahí no se sigue ninguna decisión. Así que anota siempre las dos cosas, la diferencia y el intervalo.
La idea
Lo mismo con scipy
scipy.stats tiene las pruebas listas. ttest_ind (la prueba t) compara las medias de dos grupos con una fórmula. permutation_test hace la mezcla por ti, por tandas (batch), para que no se acabe la memoria:
Para el pedido del domingo la prueba t da 0,426, permutation_test 0,419 y nuestro bucle 0,425. La respuesta es la misma: no hay evidencia. Para viernes contra domingo la prueba t da 1,04 × 10⁻¹²: cero a efectos prácticos, como nuestro 0 de 10 000.
Más precisamente, para curiosos
La prueba t supone que la media de cada grupo está distribuida casi de forma normal. Con cientos de pedidos en un grupo eso es cierto (teorema central del límite). Con pocos datos y una cola larga la prueba de permutaciones es más segura, porque no supone nada sobre la distribución. permutation_test da una p algo distinta de la nuestra: las mezclas son otras, y además cuenta los propios datos como una de ellas, (cantidad + 1) / (mezclas + 1).
Ejercicio
Diciembre y viernes
En diciembre se venden tarjetas regalo. ¿Es el pedido de diciembre mayor que los pedidos de los demás meses, o es casualidad? Guarda en gap la diferencia del pedido medio (diciembre menos los demás meses) y en p el valor p. Puedes usar una prueba de permutaciones o stats.ttest_ind.
Solución
from scipy import stats
import numpy as np
import pandas as pd
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
orders = orders[orders['status'] != 'cancelled']
items['revenue'] = items['quantity'] * items['unit_price']
totals = items.groupby('order_id')['revenue'].sum()
orders['value'] = orders['id'].map(totals)
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
value = orders['value'].to_numpy()
is_dec = (orders['ordered_at'].dt.month == 12).to_numpy()
gap = value[is_dec].mean() - value[~is_dec].mean()
p = stats.ttest_ind(value[is_dec], value[~is_dec]).pvalue
print(is_dec.sum(), round(gap, 2), p)Hay 1211 pedidos de diciembre, y el pedido es de media 2,84 € mayor que el de los demás meses. La prueba t da p = 5,1 × 10⁻²³: el azar no explica esta diferencia. La comparación es diciembre contra los otros 11 meses. Contra todo el año compararías diciembre también consigo mismo, y la diferencia se encoge.
El pedido del viernes es algo menor que el de los demás días. ¿Hay evidencia de ello? Haz una prueba de permutaciones con 2000 mezclas (semilla 42): gap es la diferencia del pedido medio (viernes menos los demás días), p es el valor p y evidence indica si p está por debajo del umbral de 0,05.
Solución
import numpy as np
import pandas as pd
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
orders = orders[orders['status'] != 'cancelled']
items['revenue'] = items['quantity'] * items['unit_price']
totals = items.groupby('order_id')['revenue'].sum()
orders['value'] = orders['id'].map(totals)
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
value = orders['value'].to_numpy()
is_fri = (orders['ordered_at'].dt.dayofweek == 4).to_numpy()
gap = value[is_fri].mean() - value[~is_fri].mean()
rng = np.random.default_rng(42)
gaps = np.empty(2_000)
for i in range(2_000):
s = rng.permutation(is_fri)
gaps[i] = value[s].mean() - value[~s].mean()
p = (np.abs(gaps) >= abs(gap)).mean()
evidence = p < 0.05
print(round(gap, 2), p, evidence)El pedido del viernes es 0,45 € menor y p = 0,0645. Eso está por encima de 0,05: con el umbral habitual no hay evidencia, y evidence es False. Es tentador decir «casi significativo» o mover el umbral a 0,1 después de ver p. No lo hagas: el umbral se elige antes de la prueba. La respuesta honesta es «puede haber una diferencia pequeña, pero estos datos no la demuestran».
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Fechas y series temporales · Describir los datos · Muestra y población · Intervalos de confianza
- Continúa en: Prueba A/B · Correlación y causa · ¿Por qué bajaron los ingresos?
- Ejercicios sobre el tema: Estadística para decidir