%matplotlib inline
import random
import matplotlib.pyplot as plt
En este cuaderno compararemos dos enfoques para calcular probabilidades:
Cualquier lenguaje de programación tiene una función random
, que cuando la llamamos nos devuelve un número pseudo-aleatorio entre 0 y 1. Todos los números entre 0 y 1 son "igualmente probables"
#Cada vez que ejecutamos este código obtenemos un número distinto:
random.random()
#Generamos 10 números pseudo-aleatorios y los imprimimos
for i in range(10):
print(random.random())
Además, python nos ofrece otras funciones prácticas:
random.randint(a,b)
devuelve un número entero entre a y b (inclusive). Todos los números entre a y b son "igualmente probables".random.choice(lista)
devuelve un elemento de lista
(que debe ser una lista, cadena de caracteres, conjunto...). Todos los elementos son "igualmente probables".for i in range(10):
print (random.randint(10,20))
for i in range(10):
print(random.choice('AEIOU'))
Estos números parecen aleatorios, pero el ordenador los obtiene aplicando reglas deterministas. Sólo parecen aleatorios, pero la cpu no lanza dados cuando llamamos a random.
Esto no es un inconveniente en la práctica, porque son casi imposibles de distinguir de números auténticamente aleatorios. De hecho, es una virtud, porque si fijamos la semilla aleatoria, podemos obtener exactmente los mismos números, aunque hagamos el cálculo en máquinas distintas varios años después.
#Si cambias la semilla, las elecciones cambian, pero con la misma semilla
#obtenemos los mismos resultados
random.seed(15)
for i in range(10):
print (random.random())
for i in range(10):
print (random.randint(10,20))
for i in range(10):
print(random.choice('AEIOU'))
Comprueba que si usáis la misma semilla, obtenéis los mismos resultados que vuestr_s compañer_s de al lado. Puedes incluso probar en una terminal de python 3 en tu ordenador de casa. No funciona con una terminal de python 2, creo que cambiaron el algoritmo...
Vamos a simular un ejercicio típico de probabilidad. Usaremos siempre el mismo enfoque:
random
(más adelante estudiaremos otras opciones).N
de veces.x
del espacio muestral, y devuelve True
si x
pertenece al suceso A, y False
en caso contrario.El esqueleto del código es
def experimento():
resultado = ... random ...
return resultado
#Cuanto mayor N, más trabajo para el ordenador, pero mejor aproximación
N = 1000
# muestra son N llamadas a la misma función, que cada vez devuelve un
#resultado distinto
muestra = [experimento() for _ in range(N)]
def es_suceso_A(resultado):
if ... condicion ... :
return True
else:
return False
#Para cada elemento "e" de la muestra, es_suceso_A(e) es True
#si "e" pertenece a A. La suma de una secuencia de booleanos es la
#cantidad de booleanos que son "True"
proporcion_A = sum(es_suceso_A(e) for e in muestra)
Ejercicio 10 de la hoja de la asignatura obligatoria del plan antiguo. Se tienen dos urnas, y cada una de ellas contiene un número diferente de bolas blancas y rojas:
Se realiza el siguiente experimento aleatorio: Se tira una moneda al aire y si sale cara se elige una bola de la primera urna, y si sale cruz de la segunda. ¿Cuál es la probabilidad de que salga una bola blanca?. (sol = 19/30)
def experimento_urnas():
'''
Función aleatoria que devuelve el resultado de simular el experimento *una vez*.
El resultado es distinto cada vez que llamamos a la función.
El resultado puede ser 'B' (bola blanca) o 'R' (bola roja)
'''
urna = random.randint(1,2)
if urna == 1:
#Escogemos una bola.
return random.choice('BBBRR')
else:
#Escogemos una bola. Las bolas son [B,B,B,B,R,R]
return random.choice('BBBBRR')
for _ in range(10):
bola = experimento_urnas()
print(bola)
N = 10
muestra = [experimento_urnas() for _ in range(N)]
print(muestra)
def es_bola_blanca(bola):
'''
Función que recibe como argumento un string, que puede ser
"B" (representa una bola blanca)
"R" (representa una bola roja)
Devuelve True <=> el argumento es "B" (una bola blanca)
'''
return bola=='B'
def es_bola_blanca(bola):
'''
Función que recibe como argumento un string, que puede ser
"B" (representa una bola blanca)
"R" (representa una bola roja)
Devuelve True <=> el argumento es "B" (una bola blanca)
'''
if bola=='B':
return True
else:
return False
random.seed(1) #Prueba a cambiar la semilla: la aproximación cambia
N = 1000
muestra = [experimento_urnas() for _ in range(N)]
prob = sum(es_bola_blanca(e) for e in muestra)/N
print('Probabilidad aproximada de bola blanca:', prob)
Una urna contiene 5 bolas numeradas 1,2,3,4 y 5. Calcular la probabilidad de que al sacar 2 bolas con reposición la suma de los puntos sea impar.
numeros1al5 = range(1,6)
def experimento_dos_bolas():
'''
Función aleatoria que devuelve el resultado de simular el experimento *una vez*.
El resultado es distinto cada vez que llamamos a la función.
El resultado es un par de números (las dos bolas)
'''
bola1 = random.choice(numeros1al5)
bola2 = random.choice(numeros1al5)
return [bola1, bola2]
experimento_dos_bolas()
def suma_es_impar(bolas):
return sum(bolas)%2 == 1
random.seed(1) #Prueba a cambiar la semilla: la aproximación cambia
N = 1000
muestra = [experimento_dos_bolas() for _ in range(N)]
prob = sum(suma_es_impar(e) for e in muestra)/N
print('Probabilidad aproximada de que la suma de los puntos sea impar:', prob)
Una urna contiene 5 bolas numeradas 1,2,3,4 y 5. Calcular la probabilidad de que al sacar 2 bolas sin reposición la suma de los puntos sea impar. (sol = 0,6)
¿Cómo podemos conseguir que extraiga dos bolas distintas?...
numeros1al5 = range(1,6)
def experimento_dos_bolas_sin_reposicion():
'''
Función aleatoria que devuelve el resultado de simular el experimento *una vez*.
El resultado es distinto cada vez que llamamos a la función.
El resultado es un par de números (las dos bolas)
'''
b1 = random.choice(numeros1al5)
b2 = random.choice(numeros1al5)
while b1 == b2:
b2 = random.choice(numeros1al5)
return (b1,b2)
numeros1al5 = range(1,6)
def experimento_dos_bolas_sin_reposicion():
'''
Función aleatoria que devuelve el resultado de simular el experimento *una vez*.
El resultado es distinto cada vez que llamamos a la función.
El resultado es un par de números (las dos bolas)
'''
b1 = random.choice(numeros1al5)
b2 = random.choice(numeros1al5)
while b1 == b2:
b1 = random.choice(numeros1al5)
b2 = random.choice(numeros1al5)
return (b1,b2)
lista =[100,200,300,400]
lista.remove(200)
lista
lista =[100,200,300,400]
del lista[2]
lista
numeros1al5 = list(range(1,6))
from copy import copy
def experimento_dos_bolas_sin_reposicion():
'''
Función aleatoria que devuelve el resultado de simular el experimento *una vez*.
El resultado es distinto cada vez que llamamos a la función.
El resultado es un par de números (las dos bolas)
'''
i1 = random.randint(0, 4)
lista = copy(numeros1al5)
b1 = lista[i1]
del lista[i1]
b2 = random.choice(lista)
return (b1,b2)
numeros1al5 = range(1,6)
def experimento_dos_bolas_sin_reposicion():
'''
Función aleatoria que devuelve el resultado de simular el experimento *una vez*.
El resultado es distinto cada vez que llamamos a la función.
El resultado es un par de números (las dos bolas)
'''
lista = copy(numeros1al5)
b1 = random.choice(lista)
lista.remove(b1)
b2 = random.choice(lista)
return (b1,b2)
random.sample??
random.sample(numeros1al5,4)
random.seed(1) #Prueba a cambiar la semilla: la aproximación cambia
N = 1000
muestra = [experimento_dos_bolas_sin_reposicion() for _ in range(N)]
prob = sum(suma_es_impar(e) for e in muestra)/N
print('Probabilidad aproximada de que la suma de los puntos sea impar:', prob)
Vamos a comparar el resultado con la probabilidad exacta obtenida usando las reglas de cálculo de probabilidades.
El resultado final puede ser una bola blanca de dos formas:
Ambos sucesos son incompatibles, y por tanto la probabilidad de bola blanca es la suma de las probabilidades de los dos eventos. Cada evento se compone de dos sucesos: un lanzamiento de moneda equilibarda y una extracción de bola en la que suponemos que todas las bolas son equiprobables. La probabilidad total de que salga bola blanca es la suma de las dos probabilidades
random.seed(1) #Prueba a cambiar la semilla: la aproximación cambia
N = 1000
muestra = [experimento_urnas() for _ in range(N)]
prob = sum(es_bola_blanca(e) for e in muestra)/N
print('Probabilidad aproximada de bola blanca:', prob)
print('Probabilidad exacta de bola blanca:', 19/30)
random.seed(1) #Prueba a cambiar la semilla: la aproximación cambia
N = 1000
muestra = [experimento_dos_bolas() for _ in range(N)]
prob = sum(suma_es_impar(e) for e in muestra)/N
print('Probabilidad aproximada de que la suma de los puntos sea impar:', prob)
print('Probabilidad exacta de que la suma de los puntos sea impar:', 12/25)
random.seed(1) #Prueba a cambiar la semilla: la aproximación cambia
N = 1000
muestra = [experimento_dos_bolas_sin_reposicion() for _ in range(N)]
prob = sum(suma_es_impar(e) for e in muestra)/N
print('Probabilidad aproximada de que la suma de los puntos sea impar:', prob)
print('Probabilidad exacta de que la suma de los puntos sea impar:', 3/5)
Un problema clásico:
Primero simulamos el experimento...
def birthdays(k):
'''
birthdays(k) devuelve k fechas de cumpleaños (k números entre 1 y 365)
'''
return [random.randint(1,365) for _ in range(k)]
birthdays(10)
Ahora tenemos que repetir el experimento N veces, para obtener N conjuntos de k cumpleaños cada uno, y contar cuántas veces ese conjunto tiene repeticiones.
Para comprobar si hay coincidencias, usamos una estructura de datos de python: el conjunto. Es un contenedor de objetos sin repeticiones. Si construimos un conjunto con los elementos de una lista, desaparecen los elementos repetidos:
#Ejemplos de conjuntos
print(set([1,2,3,1,7,8,9]))
print(set([1,1,1,1,2,2]))
def hay_coincidencia(bdays):
'''Hay una coincidencia en la lista bdays si y solo si
la longitud de la lista bdays después de eliminar repeticiones
es menor que la longitud de la lista antes de eliminar repeticiones
'''
return len(set(bdays)) < len(bdays)
#Hay k personas en la habitación
k = 25
random.seed(2) #Prueba a cambiar la semilla: la aproximación cambia
N = 1000
muestra = [birthdays(k) for _ in range(N)]
prob = sum(hay_coincidencia(e) for e in muestra)/N
print('Probabilidad aproximada de que dos personas cumplan años el mismo día', prob)
Dibujamos la proporción obtenida en las simulaciones para grupos de k personas, donde k varía desde 0 hasta 40 personas. Para ello es interesante crear una función proporcion_coincidencia
que devuelve directamente una aproximación a la probabilidad de coincidencia en función del número de personas:
def proporcion_coincidencia(k, N=1000):
muestra = [birthdays(k) for _ in range(N)]
proporcion = sum(hay_coincidencia(e) for e in muestra)/N
return proporcion
random.seed(1) #Prueba a cambiar la semilla: la aproximación cambia
kmax = 40
plt.figure(figsize=(16, 12))
plt.bar(range(1,kmax+1),[proporcion_coincidencia(k) for k in range(1,kmax+1)], color='orange')
plt.show()
Vamos a pensarlo en la pizarra...
def probabilidad_coincidencia(k):
'''
probabilidad_coincidencia(k) devuelve la probabilidad exacta de que en
un grupo de k personas al menos dos cumplan años el mismo día
'''
...
probabilidad_coincidencia(50)
K = 40
plt.figure(figsize=(16, 12))
plt.bar(range(1,K+1),[probabilidad_coincidencia(k) for k in range(1,K+1)], fill=False, zorder=10)
plt.bar(range(1,K+1),[proporcion_coincidencia(k) for k in range(1,K+1)], color='orange')
plt.show()