Capítulo 10 de 23 11 secciones 21 min

Compartir

De la muestra a la población

Intervalos de confianza, lo que de verdad significan, y por qué dos intervalos que se solapan pueden ser una diferencia real.

Un intervalo de confianza del 95% es un rango construido con un método que acierta el 95% de las veces si lo repites muchas veces. No significa que haya un 95% de probabilidad de que el valor real esté ahí dentro, aunque es así como yo lo entendía al principio. Y dos intervalos que se solapan no implican que no haya diferencia: acá Horeca y Minimarket tienen intervalos solapados y su diferencia es real.

Tienes 3.000 ventas y calculas que el monto promedio es 803,76 soles.

Pero esas 3.000 no son todas las ventas del mundo: son las que te tocaron. Si el mes que viene sacas otras 3.000, el promedio va a salir distinto. Entonces, ¿de cuánto es el promedio de verdad? 🤷

La respuesta honesta no es un número, es un rango. Eso es un intervalo de confianza, y este capítulo va de calcularlo, entenderlo y no contarlo mal.

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)
m = v['monto']
n = len(m)

error_estandar = m.std() / np.sqrt(n)

print('promedio:        %.4f' % m.mean())
print('error estandar:  %.4f' % error_estandar)
promedio:        803.7613
error estandar:  13.7294

Ese error estándar es el del capítulo 8: cuánto se mueve un promedio de muestra en muestra. 13,73 soles.

EE=sn

cuánto se mueve un promedio de muestra en muestra, que baja con la raíz de cuántos datos tengas

Y una pregunta que vale para toda tu vida laboral: ¿cuántas veces has entregado un número sin decir cuánto se puede mover? El margen de error no es un adorno académico: es lo que separa un dato de una apuesta 📊

Histograma de las medias de dos mil remuestras con la banda del intervalo de confianza del 95% sombreada y la media original marcada con una línea vertical.
Cada barra es el promedio de una remuestra de tus propios datos. El ancho de ese montón es, literalmente, cuánto se mueve tu promedio, y recortando el 2,5% de cada lado sale el intervalo. Sin fórmulas y sin suponer que los datos son normales.

El intervalo, en una línea

bajo, alto = stats.t.interval(0.95, n - 1, m.mean(), error_estandar)

print('promedio: %.2f' % m.mean())
print('intervalo del 95%%: de %.2f a %.2f' % (bajo, alto))
print('margen de error: +/- %.2f soles' % ((alto - bajo) / 2))
promedio: 803.76
intervalo del 95%: de 776.84 a 830.68
margen de error: +/- 26.92 soles

Ahí lo tienes: el monto promedio está entre 776,84 y 830,68 soles 📊

Ese 1,96 que multiplica el error estándar (aquí lo pone la t por dentro) es el número mágico del 95%. Si quisieras el 99% sería 2,58, y el intervalo saldría más ancho: más seguridad cuesta más rango.

La regla rápida que puedes hacer de cabeza: margen de error igual a dos errores estándar 🧮

IC95%=x¯±1,96·sn

el promedio más menos dos errores estándar, que es de donde sale el margen de error de las encuestas

Línea de cobertura real del intervalo del 95% según el tamaño de la muestra, con una raya horizontal en el 95% prometido. Con muestras chicas la línea va por debajo y solo se acerca a la raya con mil datos.
El intervalo del 95% no cubre el 95% cuando la muestra es chica y los datos tienen cola: se queda alrededor del 93%. La promesa se cumple recién con muestras grandes.

Lo que significa, y lo que no

Esta es la parte que casi todo el mundo cuenta mal, incluida yo durante años 🙋

Mal: "hay un 95% de probabilidad de que el promedio real esté entre 776,84 y 830,68".

Bien: "si repitiera este cálculo muchas veces con muestras distintas, el 95% de los intervalos que saldrían contendrían el promedio real".

La diferencia parece de abogados y no lo es. El promedio real es un número fijo: o está dentro de tu intervalo o no está. Lo que tiene el 95% es el método, no el intervalo concreto que te salió.

Y como esto se entiende mejor viéndolo, vamos a hacerlo:

generador = np.random.default_rng(7)
verdadera = m.mean()
tamano = 50
contiene = 0

for _ in range(1000):
    muestra = generador.choice(m.values, tamano)
    ee = muestra.std(ddof=1) / np.sqrt(tamano)
    lo, hi = stats.t.interval(0.95, tamano - 1, muestra.mean(), ee)
    contiene += (lo <= verdadera <= hi)

print('de 1000 intervalos del 95%%, contienen el promedio real: %d' % contiene)
print('o sea el %.1f%%' % (contiene / 10))
de 1000 intervalos del 95%, contienen el promedio real: 929
o sea el 92.9%

92,9% en vez de 95%. Se queda corto 🤔

Y ese "casi" tiene explicación, que es de las cosas más útiles del capítulo. Vamos a probar con distintos tamaños:

for tamano in [20, 50, 200, 1000]:
    contiene = 0
    for _ in range(1000):
        muestra = generador.choice(m.values, tamano)
        ee = muestra.std(ddof=1) / np.sqrt(tamano)
        lo, hi = stats.t.interval(0.95, tamano - 1, muestra.mean(), ee)
        contiene += (lo <= verdadera <= hi)
    print('con muestras de %4d -> cobertura real %.1f%%' % (tamano, contiene / 10))
con muestras de   20 -> cobertura real 93.4%
con muestras de   50 -> cobertura real 93.1%
con muestras de  200 -> cobertura real 94.7%
con muestras de 1000 -> cobertura real 95.9%

Con muestras de 20 o 50, tu intervalo del 95% acierta el 93% 😬

O sea que te está mintiendo un poco: crees que fallas 1 de cada 20 y fallas 1 de cada 15. Y no hay ningún error en el cálculo. Solo cuando llegas a 1.000 la cobertura sube al 95,9% y la promesa se cumple.

La causa es la cola del capítulo 5. La fórmula supone que los promedios ya se han acampanado, y con 20 ventas de una distribución tan torcida todavía no lo están (capítulo 8: con 30 la asimetría seguía en 0,3223).

Compáralo con unidades, que sí era acampanada:

u = v['unidades'].values
verdadera_u = u.mean()

for tamano in [20, 50]:
    contiene = 0
    for _ in range(1000):
        muestra = generador.choice(u, tamano)
        ee = muestra.std(ddof=1) / np.sqrt(tamano)
        lo, hi = stats.t.interval(0.95, tamano - 1, muestra.mean(), ee)
        contiene += (lo <= verdadera_u <= hi)
    print('unidades, muestras de %3d -> cobertura %.1f%%' % (tamano, contiene / 10))
unidades, muestras de  20 -> cobertura 95.2%
unidades, muestras de  50 -> cobertura 95.0%

95,2% con solo 20 datos. Justo lo prometido, con la cuarta parte de la muestra que le hacía falta al monto 🔔

La forma de los datos importa más que la cantidad. Y date cuenta de lo que eso significa en la práctica: el mismo tamaño de muestra te da un intervalo honesto o uno optimista según qué columna estés midiendo.

El intervalo de una proporción

El caso más frecuente en la vida real: "el 57,77% de las ventas se cierra, ¿con cuánto margen?".

Aquí el error estándar tiene su propia fórmula, la que sacamos en el capítulo 4:

EEp=p(1p)n

el error estándar de un porcentaje, que es máximo cuando el reparto está en la mitad y se encoge cuando se acerca a cero o a uno

p = v['compro'].mean()
ee_p = np.sqrt(p * (1 - p) / n)

print('proporcion: %.4f' % p)
print('error estandar: %.6f' % ee_p)
print('intervalo del 95%%: %.4f a %.4f' % (p - 1.96 * ee_p, p + 1.96 * ee_p))
print('o sea: %.2f%% +/- %.2f puntos' % (100 * p, 100 * 1.96 * ee_p))
proporcion: 0.5777
error estandar: 0.009018
intervalo del 95%: 0.5600 a 0.5953
o sea: 57.77% +/- 1.77 puntos

57,77% con un margen de 1,77 puntos. Ese "±1,8 puntos" es exactamente lo que sale en las encuestas electorales 🗳️

El error del capítulo

stats.t.interval(95, n - 1, m.mean(), error_estandar)
ValueError: alpha must be between 0 and 1 inclusive

Le pedí el 95 en vez del 0,95. Menos mal que avisa 🙏

Porque el mismo despiste en la otra dirección no avisa:

print('pidiendo 0.95:', np.round(stats.t.interval(0.95, n - 1, m.mean(), error_estandar), 2))
print('pidiendo 0.05:', np.round(stats.t.interval(0.05, n - 1, m.mean(), error_estandar), 2))
pidiendo 0.95: [776.84 830.68]
pidiendo 0.05: [802.9  804.62]

Si por error pides 0,05 en vez de 0,95, te devuelve un intervalo estrechísimo y perfectamente válido: el intervalo del 5% 😱

Y ese sí que se cuela en un informe, porque un margen de error chiquito es justo lo que uno quiere ver. Cuidado con los resultados que te gustan demasiado 🍬

La trampa de los intervalos que se solapan

Y ahora lo más importante del capítulo, que es un error que he visto en informes serios.

Comparamos dos segmentos:

for seg in ['Horeca', 'Minimarket']:
    g = v.loc[v['segmento'] == seg, 'compro']
    pp = g.mean()
    e = np.sqrt(pp * (1 - pp) / len(g))
    print('%-11s n=%3d  p=%.4f  IC [%.4f, %.4f]'
          % (seg, len(g), pp, pp - 1.96 * e, pp + 1.96 * e))
Horeca      n=742  p=0.6321  IC [0.5974, 0.6668]
Minimarket  n=801  p=0.5693  IC [0.5350, 0.6036]

Los intervalos se solapan: Horeca llega hasta 0,5974 por abajo y Minimarket sube hasta 0,6036.

La conclusión que casi todo el mundo saca: "se solapan, así que no hay diferencia". Vamos a comprobarlo calculando el intervalo de la diferencia, que es lo que había que hacer:

a = v.loc[v['segmento'] == 'Horeca', 'compro']
b = v.loc[v['segmento'] == 'Minimarket', 'compro']

pa, pb = a.mean(), b.mean()
ea = np.sqrt(pa * (1 - pa) / len(a))
eb = np.sqrt(pb * (1 - pb) / len(b))
ee_dif = np.sqrt(ea ** 2 + eb ** 2)
dif = pa - pb

print('diferencia: %.4f' % dif)
print('intervalo del 95%%: [%.4f, %.4f]'
      % (dif - 1.96 * ee_dif, dif + 1.96 * ee_dif))
print('contiene el cero:', (dif - 1.96 * ee_dif) <= 0 <= (dif + 1.96 * ee_dif))
diferencia: 0.0628
intervalo del 95%: [0.0140, 0.1116]
contiene el cero: False

El intervalo de la diferencia no contiene el cero. O sea que la diferencia es real, aunque los intervalos individuales se solapen 🤯

Por qué pasa: el error estándar de la diferencia no es la suma de los dos, es la raíz de la suma de los cuadrados, que sale más chica. Sumar dos incertidumbres "a lo bruto" exagera.

EEd=EE12+EE22

la incertidumbre de una diferencia no es la suma de las dos, es la raíz de la suma de sus cuadrados, y por eso sale más chica

Regla: para comparar dos grupos, calcula el intervalo de la diferencia. Nunca mires si los individuales se tocan 📏

Y dos más, y el primero es EL error de este capítulo

stats.t.interval(95, len(m) - 1, m.mean(), error_estandar)
ValueError: alpha must be between 0 and 1 inclusive

El 95% se escribe 0,95 y no 95. Este lo he visto tantas veces que casi merece un capítulo propio, y menos mal que revienta: si scipy aceptara el 95 sin quejarse, te devolvería un intervalo absurdo y no habría forma de notarlo.

stats.bootstrap(m, np.mean)
AxisError: `axis` is out of bounds for array of dimension 0

Y este te presenta al capítulo siguiente. scipy trae su propio bootstrap, y se queja porque quiere una secuencia de muestras y le diste la muestra: hay que escribir stats.bootstrap((m,), np.mean), con esa coma que convierte a m en una tupla de un elemento. Es una coma y es la diferencia entre que funcione y que no 🥾

Practica 💪

1. El margen de error de cada segmento

Calcula el intervalo de la tasa de compra de los cuatro segmentos. ¿Cuál tiene el margen más ancho y por qué?

for seg, g in v.groupby('segmento'):
    pp = g['compro'].mean()
    e = np.sqrt(pp * (1 - pp) / len(g))
    print('%-11s n=%3d  %.2f%% +/- %.2f puntos'
          % (seg, len(g), 100 * pp, 100 * 1.96 * e))
Bodega      n=707  37.48% +/- 3.57 puntos
Horeca      n=742  63.21% +/- 3.47 puntos
Mayorista   n=750  72.40% +/- 3.20 puntos
Minimarket  n=801  56.93% +/- 3.43 puntos

Los cuatro rondan los 3,4 puntos, y el más estrecho es Mayorista 🎯

Lo interesante es por qué. Mayorista no tiene más filas que Minimarket (750 contra 801) y aun así su margen es más chico.

La razón está en la fórmula: p(1-p) es máximo cuando p vale 0,5 y baja cuando te alejas. Mayorista está en 0,724, o sea lejos de la mitad, así que hay menos incertidumbre que estimar.

Es lo que decíamos en el capítulo 4: una votación peleada necesita más gente que una que se ve clara 🗳️

2. ¿Cuántos datos necesito para un margen de 1 punto?

Despeja n de la fórmula del margen de error de una proporción y calcula cuántas ventas hacen falta para varios márgenes.

p = v['compro'].mean()

for margen in [0.05, 0.03, 0.02, 0.01]:
    necesarias = (1.96 ** 2) * p * (1 - p) / (margen ** 2)
    print('para +/- %.0f puntos hacen falta %7.0f ventas'
          % (100 * margen, necesarias))
para +/- 5 puntos hacen falta     375 ventas
para +/- 3 puntos hacen falta    1041 ventas
para +/- 2 puntos hacen falta    2343 ventas
para +/- 1 puntos hacen falta    9372 ventas

Ahí está el famoso "1.000 encuestados" de los medios: es justo lo que hace falta para un margen de 3 puntos 📰

Y fíjate en el salto final: bajar de 2 a 1 punto pasa de 2.344 a 9.375. Cuatro veces más gente para la mitad de margen, la raíz cuadrada de siempre.

Truco de campo: si no sabes qué p esperar, usa 0,5. Es el peor caso, o sea que el número que te salga te va a alcanzar seguro 🛡️

3. Un intervalo sin fórmulas: bootstrap

La mediana no tiene fórmula sencilla de error estándar. Calcula su intervalo remuestreando.

medianas = [np.median(generador.choice(m.values, n)) for _ in range(2000)]

print('mediana observada: %.2f' % m.median())
print('intervalo del 95%%: [%.2f, %.2f]'
      % (np.percentile(medianas, 2.5), np.percentile(medianas, 97.5)))
print('error estandar de la mediana: %.2f' % np.std(medianas))
mediana observada: 533.63
intervalo del 95%: [509.82, 552.86]
error estandar de la mediana: 11.36

El bootstrap es de las ideas más bonitas que hay, y en tres líneas 🥾

Lo que hace es tratar tu muestra como si fuera la población: saca muestras de ella con reposición, calcula el estadístico en cada una, y mira cómo varía. Sin fórmulas, sin suponer normalidad, y funciona con cualquier cosa que sepas calcular.

Fíjate en un detalle: el error estándar de la mediana sale 11,36 y el de la media era 13,73. La mediana es más precisa que la media aquí, y tiene sentido: es la medida robusta, la cola no la zarandea.

4. El intervalo de una diferencia de medias

¿Cuánto más vende un mayorista que una bodega? Da la diferencia con su intervalo.

may = v.loc[v['segmento'] == 'Mayorista', 'monto']
bod = v.loc[v['segmento'] == 'Bodega', 'monto']

dif = may.mean() - bod.mean()
ee = np.sqrt(may.var() / len(may) + bod.var() / len(bod))

print('mayorista: %.2f | bodega: %.2f' % (may.mean(), bod.mean()))
print('diferencia: %.2f soles' % dif)
print('intervalo del 95%%: [%.2f, %.2f]' % (dif - 1.96 * ee, dif + 1.96 * ee))
mayorista: 1856.34 | bodega: 178.70
diferencia: 1677.64 soles
intervalo del 95%: [1625.89, 1729.39]

Un mayorista compra entre 1.625 y 1.729 soles más que una bodega, por venta 💰

Y esa forma de decirlo es la buena. Compara:

  • 😐 "Hay diferencia significativa entre segmentos".
  • 😍 "Un mayorista compra entre 1.625 y 1.729 soles más por venta que una bodega".

La segunda tiene el tamaño, la unidad y la incertidumbre. Con eso alguien puede decidir algo. Con la primera, no 🤝

Esa es la diferencia entre reportar significancia y reportar un intervalo, y es el motivo por el que muchos estadísticos prefieren los intervalos a las pruebas de hipótesis del capítulo siguiente.

5. Fabrica el gráfico de cobertura a mano

Genera 20 intervalos de muestras de 100 ventas y dibuja cuáles contienen el promedio real.

verdadera = m.mean()
fallos = 0

for i in range(20):
    muestra = generador.choice(m.values, 100)
    ee = muestra.std(ddof=1) / np.sqrt(100)
    lo, hi = stats.t.interval(0.95, 99, muestra.mean(), ee)
    dentro = lo <= verdadera <= hi
    fallos += not dentro
    print('%2d: [%7.2f, %7.2f]  %s' % (i + 1, lo, hi, 'ok' if dentro else 'FALLA'))

print()
print('fallaron %d de 20' % fallos)
 1: [ 616.29,  926.35]  ok
 2: [ 741.65, 1053.20]  ok
 3: [ 698.04, 1016.73]  ok
 4: [ 825.21, 1140.85]  FALLA
 5: [ 588.81,  855.16]  ok
 6: [ 677.77,  983.14]  ok
 7: [ 657.62,  932.26]  ok
 8: [ 655.14,  932.56]  ok
 9: [ 729.09, 1025.84]  ok
10: [ 708.92,  981.22]  ok
11: [ 554.88,  829.98]  ok
12: [ 695.73,  997.46]  ok
13: [ 720.68, 1049.35]  ok
14: [ 698.92,  964.96]  ok
15: [ 607.22,  902.92]  ok
16: [ 639.63,  905.61]  ok
17: [ 622.90,  921.65]  ok
18: [ 710.77, 1015.38]  ok
19: [ 672.77,  981.01]  ok
20: [ 654.24,  940.69]  ok

fallaron 1 de 20

Uno de veinte. Justo el 5% que promete el método 🎯

Mira el número 4: va de 825,21 a 1.140,85, y el promedio real es 803,76. Se quedó por debajo del intervalo entero.

Y aquí está la gracia: ese intervalo se ve exactamente igual de bien que los otros diecinueve. Es igual de ancho, está igual de centrado en su propia muestra, y no tiene ninguna marca que lo delate.

Por eso la interpretación correcta habla del método y no de tu intervalo concreto. Tú tienes uno solo y no sabes si es de los 19 o es el 1 😅

6. El resumen que sí se puede llevar a una reunión

Escribe una función que dé una proporción con su margen en lenguaje de persona.

def con_margen(serie, nombre):
    s = serie.dropna()
    pp = s.mean()
    e = np.sqrt(pp * (1 - pp) / len(s))
    return ('%s: %.1f%% (entre %.1f%% y %.1f%%, con %d casos)'
            % (nombre, 100 * pp, 100 * (pp - 1.96 * e),
               100 * (pp + 1.96 * e), len(s)))


print(con_margen(v['compro'], 'tasa de cierre'))
print(con_margen(v.loc[v['canal'] == 'WhatsApp', 'compro'], 'cierre por WhatsApp'))
print(con_margen(v.loc[v['ciudad'] == 'lima', 'compro'], 'cierre en Lima'))
print(con_margen(v.loc[(v['ciudad'] == 'lima') & (v['canal'] == 'WhatsApp'), 'compro'],
                 'cierre en Lima por WhatsApp'))
tasa de cierre: 57.8% (entre 56.0% y 59.5%, con 3000 casos)
cierre por WhatsApp: 65.5% (entre 62.0% y 69.0%, con 719 casos)
cierre en Lima: 58.4% (entre 53.9% y 62.8%, con 471 casos)
cierre en Lima por WhatsApp: 62.9% (entre 54.4% y 71.4%, con 124 casos)

Mira cómo se abre el intervalo según vas filtrando 📈

Con 3.000 casos el margen es de menos de 2 puntos. Al llegar a "Lima por WhatsApp" quedan 124 filas y el intervalo va de 54,4% a 71,4%, o sea 17 puntos de ancho.

Eso quiere decir que con ese trozo de datos no puedes distinguir "cierra igual que la media" de "cierra muchísimo mejor". No es que no haya efecto: es que no tienes datos para verlo.

Y aquí está la costumbre que quiero dejarte del capítulo entero: cada vez que cortes los datos en trocitos, pon el número de casos al lado. Un porcentaje sin su n no significa nada 🙏

7. Cuánto encoge el margen al crecer la muestra

Calcula el intervalo del promedio con 30, 100, 500 y todos los pedidos, y mira cómo cambia el margen.

for n in [30, 100, 500, len(m)]:
    sub = m.sample(n, random_state=3)
    lo2, hi2 = stats.t.interval(0.95, n - 1, sub.mean(), sub.std() / np.sqrt(n))
    print('con %5d pedidos: de %7.2f a %7.2f, margen +/- %6.2f'
          % (n, lo2, hi2, (hi2 - lo2) / 2))
con    30 pedidos: de  680.58 a 1289.62, margen +/- 304.52
con   100 pedidos: de  673.54 a  994.88, margen +/- 160.67
con   500 pedidos: de  785.72 a  927.84, margen +/-  71.06
con  3000 pedidos: de  776.84 a  830.68, margen +/-  26.92

De 304 soles de margen a 27 😮

Y fíjate en el ritmo, que es lo que hay que interiorizar: pasar de 30 a 500 pedidos, o sea multiplicar por 16, divide el margen entre cuatro. La raíz cuadrada otra vez. Para la mitad de margen hace falta cuatro veces más gente, y para la décima parte, cien veces más.

Esa es la tabla que hay que tener a mano cuando alguien pregunte "¿y si encuestamos a más?" 💰

8. El precio de estar más seguro

Saca el intervalo al 80%, 90%, 95% y 99% y compara los anchos.

for conf in [0.80, 0.90, 0.95, 0.99]:
    lo2, hi2 = stats.t.interval(conf, len(m) - 1, m.mean(), error_estandar)
    print('al %2.0f%%: de %7.2f a %7.2f, ancho %6.2f'
          % (conf * 100, lo2, hi2, hi2 - lo2))
al 80%: de  786.16 a  821.36, ancho  35.20
al 90%: de  781.17 a  826.35, ancho  45.18
al 95%: de  776.84 a  830.68, ancho  53.84
al 99%: de  768.37 a  839.15, ancho  70.77

Estar más seguro cuesta ancho, y no sale gratis 📏

Del 95% al 99% el intervalo crece un 31%. Y ahí está el intercambio que nadie te explica: un intervalo más ancho es más probable que contenga la verdad y dice menos. Si tu intervalo va de 0 a infinito, aciertas siempre y no has dicho nada.

El 95% no tiene nada de sagrado, igual que el 0,05 del capítulo 12. Es una convención, y las convenciones se pueden discutir cuando hay una razón 🤝

Los intervalos se tocan y la diferencia es real

Antes de cerrar el capítulo, el atajo más repetido sobre intervalos, y por qué es falso 😬

La trampa

Tienes los intervalos de los dos canales y quieres el de la diferencia. Restas los extremos, que es lo que parece razonable.

# WhatsApp 0.655  IC [0.620, 0.690]
# Tienda   0.620  IC [0.585, 0.655]

# restando extremos: [0.620 - 0.655, 0.690 - 0.585]
#                  = [-0.035, 0.105]

# el de la diferencia, bien calculado:
#                  = [-0.014, 0.085]
Qué está mal

El intervalo bien hecho es un 41% más estrecho 📐

Restar los extremos supone lo peor de los dos mundos a la vez: que un canal se equivoca todo hacia arriba y el otro todo hacia abajo. Eso pasa poquísimas veces, así que el intervalo que sale es mucho más ancho que la incertidumbre de verdad.

La cuenta correcta suma las varianzas y saca la raíz, que es lo mismo que hace la prueba: 1.96 * sqrt(pa*(1-pa)/na + pb*(1-pb)/nb).

Y el precio de la versión ancha es real: hace que diferencias que sí se podían afirmar se queden en "no sabemos". Aquí el resultado no cambia, porque el cero está dentro de los dos. Pero con WhatsApp contra Marketplace la diferencia es de 19,4 puntos y el intervalo bien hecho va de 14,4 a 24,3, que es lo que se puede llevar a una reunión.

Si la pregunta es sobre la diferencia, calcula el intervalo de la diferencia. Un solo intervalo y una sola lectura: si no incluye el cero, hay diferencia.

Comprueba que lo tienes

Dos segmentos tienen intervalos de confianza que se solapan. ¿Hay diferencia entre ellos?

  • No se sabe: hay que calcular el intervalo de la diferencia
  • No, porque si se solapan la diferencia no es real
  • Sí, porque los centros son distintos
  • Depende de cuánto se solapen

Lo que te llevas

  • 📏 Margen de error = dos errores estándar. Aquí el monto promedio es 803,76 con un margen de ±26,92.
  • 🎯 El 95% es del método, no de tu intervalo. Tú tienes uno y no sabes si es de los buenos.
  • ⚠️ Con muestras chicas de una columna con cola, el intervalo del 95% acierta el 93%. Con unidades, que es acampanada, acierta el 95,2% ya con 20 datos.
  • 🔗 Intervalos que se solapan NO significan que no haya diferencia. Horeca y Minimarket se solapan y su diferencia va de 0,0140 a 0,1116, sin tocar el cero.
  • 🥾 El bootstrap da intervalos de cualquier cosa sin fórmulas. La mediana salió más precisa que la media: 11,36 contra 13,73.
  • 🧮 Para un margen de 3 puntos hacen falta 1.042 casos; para 1 punto, 9.375.
  • 🔢 Un porcentaje sin su número de casos no significa nada.

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

Un número sin intervalo es una opinión con decimales.

Y esta idea de dar un rango en vez de un número es la misma que hay detrás de la validación cruzada: un modelo tampoco tiene una nota, tiene un rango. Está en el libro de machine learning 🎯

Qué viene ahora

Con el intervalo de la diferencia ya casi hemos hecho una prueba de hipótesis sin darnos cuenta: mirábamos si el cero estaba dentro. En el capítulo 12 le ponemos nombre, y sobre todo desmontamos el valor p, que es el número peor entendido de toda la estadística 🎓

Preguntas frecuentes

¿Qué es un intervalo de confianza?

Un rango donde es razonable que esté el valor de verdad, dado lo que viste en la muestra. Se reporta en vez de un número solo porque el número solo esconde que hubo muestra.

¿Cómo se interpreta un intervalo de confianza del 95%?

No es que haya 95% de probabilidad de que el valor esté ahí. Es que si repitieras el muestreo muchas veces, 95 de cada 100 intervalos calculados así contendrían el valor real.

¿Qué es el margen de error?

La mitad del ancho del intervalo. Cuando una encuesta dice "más o menos 3 puntos", eso es el margen de error.

¿Cómo hago el intervalo más angosto?

Con más datos, y crece con la raíz: para que el intervalo mida la mitad hace falta cuatro veces la muestra. Por eso pasar de mil a dos mil encuestados sirve menos de lo que la gente espera.

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?