Capítulo 13 de 23 11 secciones 20 min

Compartir

¿Qué prueba uso?

t, ji cuadrado, ANOVA y las no paramétricas, elegidas por el tipo de variable y comprobadas sobre las mismas ventas.

La prueba se elige por el tipo de las dos variables que comparas. Categórica contra categórica es ji cuadrado. Numérica entre dos grupos es la prueba t, o Mann-Whitney si hay cola. Numérica entre tres o más grupos es ANOVA, o Kruskal-Wallis si hay cola. Y en todos los casos yo reporto el resultado con el tamaño del efecto al lado, nunca con el valor p solo.

Ya sabes qué es un valor p. Ahora toca la pregunta práctica que atasca a todo el mundo: ¿cuál de todas las pruebas uso? 🧰

La buena noticia es que la respuesta es casi mecánica. Depende del tipo de las dos variables que estés comparando, que es justo lo del capítulo 2.

Lo que comparasPruebaSi hay cola o pocos datos
Categórica contra categóricaJi cuadradoPrueba exacta de Fisher
Numérica entre 2 gruposPrueba tMann-Whitney
Numérica entre 3 o más gruposANOVAKruskal-Wallis
Numérica contra numéricaCorrelaciónSpearman (capítulo 15)

Vamos con las tres primeras sobre las ventas de siempre.

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)
print('filas:', len(v))
filas: 3000

Y una pregunta que ordena el capítulo entero: ¿qué tipo de dato tienen las dos cosas que quieres comparar? Con eso contestado, la prueba se elige sola y no hace falta memorizar nada 🧰

Ji cuadrado: dos categóricas

Es la prueba de la independencia del capítulo 7, con un número encima. Empezamos por la tabla:

tabla = pd.crosstab(v['ciudad'], v['compro'])
print(tabla)
compro      0    1
ciudad
arequipa  232  314
chiclayo  218  292
cusco     211  285
lima      196  275
piura     218  288
trujillo  192  279
chi2, p, gl, esperados = stats.chi2_contingency(tabla)

print('ji cuadrado: %.4f' % chi2)
print('grados de libertad: %d' % gl)
print('valor p: %.4f' % p)
ji cuadrado: 0.7292
grados de libertad: 5
valor p: 0.9813

p = 0,9813. El 98% de los mundos sin efecto darían una tabla al menos tan desigual como esta 🤷

Este es el número que llevo prometiendo desde el capítulo 1, y ahí está: la ciudad no tiene absolutamente nada que decir sobre si una venta se cierra.

Lo bonito de ji cuadrado es lo que hay dentro: compara lo observado con lo que saldría si fueran independientes, que es justo el producto de probabilidades del capítulo 7.

χ2=(OE)2E

sumas, celda por celda, cuánto se aleja lo observado de lo que saldría si las dos columnas fueran independientes

print(pd.DataFrame(esperados.round(1), index=tabla.index,
                   columns=tabla.columns))
compro        0      1
ciudad
arequipa  230.6  315.4
chiclayo  215.4  294.6
cusco     209.5  286.5
lima      198.9  272.1
piura     213.7  292.3
trujillo  198.9  272.1

Compara con la tabla real: 232 contra 230,6, 218 contra 215,4... Todas pegadas. Por eso el ji cuadrado sale casi cero 🎯

Ahora las cuatro categóricas del archivo, de una pasada:

for col in ['ciudad', 'categoria', 'canal', 'segmento']:
    chi2, p, gl, esp = stats.chi2_contingency(pd.crosstab(v[col], v['compro']))
    print('%-11s chi2 = %8.4f | p = %.4g' % (col, chi2, p))
ciudad      chi2 =   0.7292 | p = 0.9813
categoria   chi2 =   1.6114 | p = 0.8067
canal       chi2 =  65.3061 | p = 4.314e-14
segmento    chi2 = 194.2990 | p = 7.193e-42

Ahí está el archivo entero resumido en cuatro líneas 🏆 Segmento y canal mandan; ciudad y categoría no dicen nada.

Es exactamente el mismo orden que salió en el capítulo 7 con el cálculo casero. Lo que añade la prueba es el "cuánto hace falta para no ser casualidad".

Y ahora lo que casi nadie hace: mirar los residuos

Un ji cuadrado te dice "aquí pasa algo" pero no dónde. Para eso están los residuos, que miden cuánto se desvía cada celda:

tabla_seg = pd.crosstab(v['segmento'], v['compro'])
chi2, p, gl, esp = stats.chi2_contingency(tabla_seg)

residuos = (tabla_seg - esp) / np.sqrt(esp)
print(residuos.round(3))
compro          0      1
segmento
Bodega      8.299 -7.096
Horeca     -2.281  1.950
Mayorista  -6.167  5.273
Minimarket  0.365 -0.312

Ahora sí se ve la película 🎬

Un residuo se lee como una puntuación z: por encima de 2 en valor absoluto ya es notable, por encima de 3 es fuerte.

  • 🔴 Bodega: +8,299 en la columna de "no compró". Es la celda que más manda de toda la tabla: hay muchísimas más bodegas que no compran de las que cabría esperar.
  • 🟢 Mayorista: +5,273 en "sí compró". La otra punta.
  • Minimarket: 0,365 y -0,312. Se comporta exactamente como la media. No aporta nada al resultado.

O sea que ese ji cuadrado de 194,30 no viene de los cuatro segmentos: viene sobre todo de Bodega. Si el negocio quiere mover una aguja, ahí está la aguja 📍

Árbol para elegir la prueba: si las dos variables son categóricas, ji cuadrado. Si no, según se comparen dos grupos o tres o más, prueba t o ANOVA, y sus versiones Mann-Whitney y Kruskal-Wallis cuando los datos tienen cola.
Todo el capítulo cabe acá. La prueba no se elige por costumbre ni por la que te suene: la eligen los tipos de las dos variables y cuántos grupos hay.

ANOVA: una numérica entre varios grupos

La prueba t compara dos grupos. Con cuatro, la que toca es ANOVA:

grupos = [g['monto'].values for _, g in v.groupby('canal')]
print('monto por canal:', stats.f_oneway(*grupos))

grupos_seg = [g['monto'].values for _, g in v.groupby('segmento')]
print('monto por segmento:', stats.f_oneway(*grupos_seg))
monto por canal: F_onewayResult(statistic=np.float64(0.1865156164511293), pvalue=np.float64(0.905618171704761))
monto por segmento: F_onewayResult(statistic=np.float64(2636.7595738084665), pvalue=np.float64(0.0))

Los dos extremos posibles, en la misma pantalla 😄

Por canal, p = 0,9056: los cuatro canales facturan lo mismo por venta. Por segmento, p = 0,0 literal, o sea tan chico que ni se representa.

Y aquí hay una pregunta que quizá te estés haciendo: si tengo cuatro grupos, ¿por qué no hago las seis pruebas t de todos contra todos? Porque cada prueba tiene su 5% de falsa alarma, y seis pruebas suben eso al 26%. ANOVA hace una sola pregunta ("¿hay algún grupo distinto?") con un solo 5% 🛡️

Las no paramétricas, para cuando hay cola

ANOVA y la t comparan medias y suponen forma acampanada. Y ya sabemos que el monto tiene una cola de campeonato. La versión que no supone nada compara posiciones en vez de valores:

print('ANOVA por canal:          p = %.4f' % stats.f_oneway(*grupos).pvalue)
print('Kruskal-Wallis por canal: p = %.4f' % stats.kruskal(*grupos).pvalue)
ANOVA por canal:          p = 0.9056
Kruskal-Wallis por canal: p = 0.8179

0,9056 y 0,8179. Las dos dicen lo mismo, así que la cola no estaba molestando 👍

Mi criterio, y te lo doy como criterio y no como ley: corre las dos y mira si coinciden. Cuando coinciden, reporta la paramétrica que todo el mundo entiende. Cuando discrepan, la cola sí importa y hay que mirar por qué.

El error del capítulo

Quiero ji cuadrado sobre un cruce que no existe:

stats.chi2_contingency([[10, 5], [0, 0]])
ValueError: The internally computed table of expected frequencies has a zero element at (np.int64(1), np.int64(0)).

Ese error es de los que enseñan 🎓

Ji cuadrado divide entre los valores esperados. Si una fila entera está vacía, su esperado es cero y la división se va al infinito. No es un capricho del software: la prueba no está definida ahí.

Y hay un pariente que no da error y que hay que vigilar. La regla es que ningún esperado debería bajar de 5:

chi2, p, gl, esp = stats.chi2_contingency(pd.crosstab(v['ciudad'], v['compro']))
print('el esperado mas chico de nuestra tabla: %.1f' % esp.min())

pequena = pd.crosstab(v['ciudad'], v['compro']).head(2) // 100
print()
print(pequena)
chi2b, pb, glb, espb = stats.chi2_contingency(pequena)
print('esperado mas chico: %.2f | p = %.4f' % (espb.min(), pb))
el esperado mas chico de nuestra tabla: 198.9

compro    0  1
ciudad        
arequipa  2  3
chiclayo  2  2
esperado mas chico: 1.78 | p = 1.0000

Con esperados de 1,78 la prueba corre igual y te devuelve un p tan formal como cualquier otro 😑 Solo que no vale nada, porque la aproximación en la que se basa necesita celdas con al menos cinco casos esperados.

Para tablas chicas está la prueba exacta de Fisher, que calcula la probabilidad contando todas las tablas posibles en vez de aproximar.

Y dos más, y las dos se quejan de lo mismo

stats.f_oneway(v['monto'])
TypeError: At least two samples are required; got 1.

ANOVA compara varios grupos, así que con uno no hay nada que comparar. Sale al pasarle la columna entera en vez de la lista de grupos, que es justo la confusión que este capítulo intenta quitar.

stats.kruskal(v['monto'])
IndexError: tuple index out of range

Y su prima no paramétrica se queja de lo mismo, aunque con un mensaje muchísimo peor: un IndexError que no dice nada. Guárdate esto, que vale para toda la librería: un mensaje malo no significa un error raro. Cuando no entiendas la queja, lo primero que hay que mirar es si le pasaste los datos con la forma que pedía 🧩

Practica 💪

1. ¿Qué canal manda dentro del ji cuadrado?

Ya sabemos que canal sale con p = 4,3e-14. Saca los residuos y di qué canal es el responsable.

tabla_canal = pd.crosstab(v['canal'], v['compro'])
chi2, p, gl, esp = stats.chi2_contingency(tabla_canal)

print(((tabla_canal - esp) / np.sqrt(esp)).round(3))
compro           0      1
canal                    
Marketplace  4.945 -4.228
Tienda      -1.748  1.495
Web         -0.136  0.116
WhatsApp    -3.194  2.731

Marketplace, con +4,945 en "no compró" 🔴

Y mira Web: -0,136 y +0,116. Casi cero. Web se comporta prácticamente como el promedio, o sea que no aporta nada al resultado de la prueba.

Eso cambia la conclusión práctica. No es que "el canal importe": es que Marketplace convierte mal y WhatsApp convierte bien, y los otros dos están en la media. Con esa frase se puede hacer algo; con "el canal es significativo", no 💼

2. La prueba de Fisher para tablas chicas

Coge las ventas de un solo día y compara segmento contra compra con las dos pruebas.

un_dia = v[v['fecha'] == '2025-03-14']
tabla_dia = pd.crosstab(un_dia['segmento'] == 'Mayorista', un_dia['compro'])

print(tabla_dia)
print()
chi2, p, gl, esp = stats.chi2_contingency(tabla_dia)
print('esperado mas chico: %.2f' % esp.min())
print('ji cuadrado: p = %.4f' % p)
print('Fisher:      p = %.4f' % stats.fisher_exact(tabla_dia).pvalue)
compro    0  1
segmento      
False     3  1
True      0  1

esperado mas chico: 0.40
ji cuadrado: p = 0.8195
Fisher:      p = 0.4000

Cinco ventas ese día, y el esperado más chico es 0,40 😬

Y mira los dos valores p: ji cuadrado dice 0,8195 y Fisher dice 0,4000. Sobre la misma tabla de cinco filas, uno da el doble que el otro.

Ninguno encuentra nada, que con cinco filas es la única respuesta honesta posible, pero fíjate en que ji cuadrado no protestó: corrió con esperados de 0,40 y devolvió un número con cuatro decimales. Fisher es el que hay que usar aquí, porque cuenta todas las tablas posibles en vez de aproximar.

La lección no es sobre pruebas, es sobre datos: con cinco filas no se hace inferencia de nada. Y sin embargo se hace todo el tiempo, cada vez que alguien filtra por región, mes y categoría a la vez 🧊

3. Todas contra todas, con y sin corrección

Haz las seis comparaciones de segmentos dos a dos y cuenta cuántas salen significativas. Luego aplica la corrección de Bonferroni.

from itertools import combinations

segmentos = sorted(v['segmento'].unique())
pares = list(combinations(segmentos, 2))
alfa_corregido = 0.05 / len(pares)

print('%d comparaciones, alfa corregido = %.4f' % (len(pares), alfa_corregido))
print()
for a, b in pares:
    ga = v.loc[v['segmento'] == a, 'compro']
    gb = v.loc[v['segmento'] == b, 'compro']
    pv = stats.ttest_ind(ga, gb, equal_var=False).pvalue
    print('%-11s vs %-11s p = %.6f  %s %s'
          % (a, b, pv,
             'sig' if pv < 0.05 else '   ',
             'sig corregido' if pv < alfa_corregido else ''))
6 comparaciones, alfa corregido = 0.0083

Bodega      vs Horeca      p = 0.000000  sig sig corregido
Bodega      vs Mayorista   p = 0.000000  sig sig corregido
Bodega      vs Minimarket  p = 0.000000  sig sig corregido
Horeca      vs Mayorista   p = 0.000142  sig sig corregido
Horeca      vs Minimarket  p = 0.011806  sig 
Mayorista   vs Minimarket  p = 0.000000  sig sig corregido

Cinco de las seis aguantan la corrección. La que se cae es justo la que usamos de ejemplo en el capítulo 12: Horeca contra Minimarket, con p = 0,011806, que queda por encima del 0,0083 corregido 🫠

Con alfa de 0,05 era significativa. Con el alfa corregido de 0,0083, no.

Y no hay nada nuevo en los datos: lo único que cambió es que ahora estoy haciendo seis preguntas en vez de una, así que exijo más evidencia para cada una. Bonferroni divide alfa entre el número de comparaciones, y es la corrección más simple que existe.

Es también la más severa, y ahí está su crítica: protege tanto de falsas alarmas que te hace perder efectos reales. Volvemos a esto en el capítulo 17 ⚖️

4. Comparar la misma cosa antes y después

Las pruebas de arriba comparan grupos distintos. Prueba la versión emparejada: parte el año y compara los mismos clientes en los dos tramos.

mitad = v['fecha'].median()
antes = v[v['fecha'] <= mitad].groupby('cliente_id')['monto'].mean()
despues = v[v['fecha'] > mitad].groupby('cliente_id')['monto'].mean()

comunes = antes.index.intersection(despues.index)
a = antes[comunes]
d = despues[comunes]

print('clientes en los dos tramos: %d' % len(comunes))
print('promedio antes:   %.2f' % a.mean())
print('promedio despues: %.2f' % d.mean())
print()
print('emparejada:      p = %.4f' % stats.ttest_rel(a, d).pvalue)
print('sin emparejar:   p = %.4f' % stats.ttest_ind(a, d).pvalue)
clientes en los dos tramos: 507
promedio antes:   822.34
promedio despues: 798.75

emparejada:      p = 0.4772
sin emparejar:   p = 0.4722

507 clientes aparecen en los dos tramos. Promediaban 822,34 antes y 798,75 después, y las dos pruebas dicen que no pasó nada (p = 0,4772 y p = 0,4722) 🙂

Lo que quiero que veas es la diferencia entre las dos pruebas. La emparejada mira el cambio de cada cliente consigo mismo; la otra trata los dos tramos como si fueran gente distinta.

Cuando de verdad hay un efecto, la emparejada lo detecta muchísimo mejor, porque se quita de encima toda la variación entre clientes (que aquí es enorme: un mayorista contra una bodega). Aquí dan casi lo mismo porque no hay nada que detectar.

Regla: si puedes emparejar, empareja. Antes y después del mismo cliente, de la misma tienda, del mismo mes del año anterior 🔗

5. Cuando la paramétrica y la no paramétrica discrepan

Fabrica el caso: coge los montos de Bodega, mételes un atípico enorme y mira qué prueba se deja engañar.

bod = v.loc[v['segmento'] == 'Bodega', 'monto'].values
mini = v.loc[v['segmento'] == 'Minimarket', 'monto'].values

print('sin tocar nada:')
print('  t:            p = %.3e' % stats.ttest_ind(bod, mini, equal_var=False).pvalue)
print('  Mann-Whitney: p = %.3e' % stats.mannwhitneyu(bod, mini).pvalue)

trucado = np.concatenate([bod, [500000.0]])
print()
print('metiendo una venta de 500000 en Bodega:')
print('  media de Bodega pasa de %.2f a %.2f' % (bod.mean(), trucado.mean()))
print('  t:            p = %.4f' % stats.ttest_ind(trucado, mini, equal_var=False).pvalue)
print('  Mann-Whitney: p = %.3e' % stats.mannwhitneyu(trucado, mini).pvalue)
sin tocar nada:
  t:            p = 1.042e-222
  Mann-Whitney: p = 6.808e-189

metiendo una venta de 500000 en Bodega:
  media de Bodega pasa de 178.70 a 884.66
  t:            p = 0.5060
  Mann-Whitney: p = 6.662e-188

Una sola fila 🤯

La prueba t pasa de p = 10 elevado a -222 (o sea "segurísimo que son distintos") a p = 0,5060, que significa "no distingo nada". Una venta entre 708.

Y mira lo que le hizo a la media: Bodega pasa de 178,70 a 884,66, o sea que por culpa de una fila el segmento que menos vende parece el que más.

Mann-Whitney ni se inmuta: pasa de 10 elevado a -189 a 10 elevado a -188. Como solo mira posiciones, esa venta enorme es simplemente "la última de la fila", y da igual que valga 500.000 o 1.000.

Este es el argumento entero a favor de las no paramétricas, en un ejemplo. Cuando sospeches de tus datos, o cuando los extremos sean de verdad, usa la que no se deja arrastrar 🛡️

6. Tu elector de pruebas

Escribe una función que, dadas dos columnas, elija la prueba y la corra.

def compara(df, grupo, medida):
    niveles = df[grupo].nunique()
    datos = [g[medida].dropna().values for _, g in df.groupby(grupo)]
    categorica = df[medida].nunique() <= 2

    if categorica:
        chi2, pv, gl, esp = stats.chi2_contingency(pd.crosstab(df[grupo], df[medida]))
        return 'ji cuadrado', pv, esp.min() < 5
    if niveles == 2:
        return 'prueba t', stats.ttest_ind(*datos, equal_var=False).pvalue, False
    return 'ANOVA', stats.f_oneway(*datos).pvalue, False


for grupo, medida in [('segmento', 'compro'), ('ciudad', 'compro'),
                      ('canal', 'monto'), ('segmento', 'monto')]:
    prueba, pv, aviso = compara(v, grupo, medida)
    print('%-10s vs %-7s -> %-12s p = %-10.4g %s'
          % (grupo, medida, prueba, pv, 'AVISO: celdas chicas' if aviso else ''))
segmento   vs compro  -> ji cuadrado  p = 7.193e-42  
ciudad     vs compro  -> ji cuadrado  p = 0.9813     
canal      vs monto   -> ANOVA        p = 0.9056     
segmento   vs monto   -> ANOVA        p = 0          

Cuatro cruces, cuatro pruebas bien elegidas, una línea cada uno 🙌

Esta función la puedes pegar en cualquier cuaderno. Y fíjate en lo que hace por dentro: la elección se decide mirando nunique(), que es exactamente el detector de tipos del capítulo 2.

Un aviso para que no la uses a ciegas: esto elige la prueba, no sustituye a pensar. Te devuelve un p y nada más. Le falta lo que viene en el capítulo 14, que es lo que de verdad hay que reportar 😌

7. La paramétrica y la no paramétrica, lado a lado

Corre ANOVA y Kruskal sobre segmento y sobre ciudad, y compara.

grupos = [g['monto'].values for _, g in v.groupby('segmento')]
print('ANOVA        : p = %.6e' % stats.f_oneway(*grupos).pvalue)
print('Kruskal      : p = %.6e' % stats.kruskal(*grupos).pvalue)
print()
grupos_c = [g['monto'].values for _, g in v.groupby('ciudad')]
print('por ciudad, ANOVA  : p = %.4f' % stats.f_oneway(*grupos_c).pvalue)
print('por ciudad, Kruskal: p = %.4f' % stats.kruskal(*grupos_c).pvalue)
ANOVA        : p = 0.000000e+00
Kruskal      : p = 0.000000e+00

por ciudad, ANOVA  : p = 0.8429
por ciudad, Kruskal: p = 0.9901

Las dos dicen lo mismo las dos veces, y eso es tranquilizador 💚

Cuando la paramétrica y la no paramétrica coinciden, la elección daba igual y puedes reportar cualquiera. Cuando no coinciden es cuando hay que pararse a pensar, y suele significar que los supuestos de la paramétrica no se cumplen.

Y fíjate en ese 0.000000e+00 de arriba: no es que el valor p sea cero, es que es tan pequeño que no cabe en un número de computadora. Se reporta como p < 0,001 y nunca como cero 🔬

8. Ji cuadrado sobre las cuatro categóricas

Cruza cada columna categórica contra si el pedido cerró, y ordénalas por lo que separan.

for col in ['ciudad', 'canal', 'categoria', 'segmento']:
    t = pd.crosstab(v[col], v['compro'])
    chi, p2, _, _ = stats.chi2_contingency(t)
    print('%-11s chi2 = %8.2f   p = %.6f' % (col, chi, p2))
ciudad      chi2 =     0.73   p = 0.981325
canal       chi2 =    65.31   p = 0.000000
categoria   chi2 =     1.61   p = 0.806734
segmento    chi2 =   194.30   p = 0.000000

Dos separan y dos no, y no son las que uno diría 🤔

El segmento arrasa, como en todo el libro. Pero mira el canal, con un chi cuadrado de 65,31: para el monto no separaba nada (era 1,03 veces en el capítulo 1) y para cerrar o no cerrar separa muchísimo.

Esa es la lección escondida: una columna puede no explicar una cosa y explicar otra. "¿Esta variable sirve?" es una pregunta mal hecha si no dices para qué 🎯

La prueba equivocada sobre los datos correctos

Antes de cerrar, la trampa. Y esta vez el resultado sale bien igual, que es lo que hace que nadie la corrija 😵

La trampa

Comparas el monto antes y después del descuento para ver cuánto se está regalando. Usas la prueba t de siempre, sale significativo, y lo das por bueno.

from scipy import stats

print(stats.ttest_ind(antes, despues).pvalue)
# 0.0001

print(stats.ttest_rel(antes, despues).pvalue)
# 4.4e-176
Qué está mal

Las dos dicen que hay diferencia, y hay 172 órdenes de magnitud entre ellas 😵

Son las mismas 1.733 ventas medidas dos veces: antes del descuento y después. Están emparejadas, fila a fila. La prueba independiente no lo sabe, así que trata las dos columnas como si fueran dos grupos de clientes distintos y tiene que abrirse paso entre toda la variación que hay entre unas ventas y otras, que es enorme.

La pareada mira solo la resta de cada fila consigo misma, que son 97,13 soles de media y muy consistente. Toda esa variación que estorbaba desaparece.

Aquí las dos daban significativo y no pasó nada. Pero el caso normal es el contrario: una diferencia real que existe y que la prueba equivocada no ve. La pregunta que decide la prueba es siempre la misma: ¿cada fila de un lado tiene su pareja del otro?

Comprueba que lo tienes

El ji cuadrado del segmento sale 194,30 con p = 7,2e-42. ¿Qué haces después?

  • Miro los residuos para saber qué celda manda
  • Reporto que el segmento es significativo y sigo
  • Hago las seis pruebas de a dos para ver cuáles difieren
  • Reviso si la muestra es suficiente

Lo que te llevas

  • 🧰 La prueba la elige el tipo de las variables. Dos categóricas, ji cuadrado. Numérica entre dos grupos, t. Entre tres o más, ANOVA.
  • 📊 Ciudad da p = 0,9813 y categoría 0,8067: nada. Canal 4,3e-14 y segmento 7,2e-42: todo.
  • 🔍 Los residuos dicen dónde está el efecto. Bodega tiene +8,299 en "no compró" y Minimarket 0,365, o sea que Minimarket no aporta nada al resultado.
  • 🛡️ ANOVA en vez de seis pruebas t porque seis pruebas al 5% suben la falsa alarma al 26%.
  • 👻 Ji cuadrado con esperados de 1,78 corre igual y devuelve un p que no vale nada. Con cinco filas, ji cuadrado dio 0,8195 y Fisher 0,4000 sobre la misma tabla.
  • 🪨 Una sola venta inventada llevó la prueba t de p = 10⁻²²² a p = 0,5060, y la media de Bodega de 178,70 a 884,66. Mann-Whitney ni se movió.
  • 🔗 Si puedes emparejar, empareja: la prueba emparejada se quita de encima toda la variación entre individuos.

Y si de todo el capítulo te llevas una sola frase, que sea esta:

La prueba no se elige por costumbre. La eligen los tipos de las dos variables que comparas.

Las cuatro pruebas de este capítulo salen de scipy.stats, que es una línea de import. Si Python todavía te da respeto, empieza por el libro de Python 🧰

Qué viene ahora

Todo este capítulo devuelve valores p, y ya avisé en el 10 de que un p no mide importancia. En el capítulo 14 vemos lo que hay que reportar al lado: el tamaño del efecto y la potencia 📐

Preguntas frecuentes

¿Qué es la prueba de chi cuadrado?

La prueba para saber si dos variables de categorías tienen que ver una con la otra. Canal y si compró o no, por ejemplo.

¿Cuándo uso chi cuadrado y cuándo una prueba t?

Chi cuadrado cuando las dos variables son categorías. La prueba t cuando comparas el promedio de un número entre dos grupos.

¿Qué es ANOVA y en qué se diferencia de la prueba t?

ANOVA compara promedios de tres grupos o más. La prueba t solo compara dos, y hacer muchas pruebas t seguidas es como comprar muchos boletos: alguna sale significativa por azar.

¿Qué pruebas uso si mis datos no son normales?

Las no paramétricas: Mann Whitney en vez de la t, Kruskal Wallis en vez de ANOVA. Trabajan sobre el orden de los datos y no piden campana.

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.

¿Le sirve a alguien que conoces?

Pásale el libro. Es gratis, está entero y no pide registro 🐣

Instagram y TikTok no dejan compartir enlaces desde la web: esos dos copian la URL para que la pegues en tu historia.

¿Tienes alguna duda o consulta?