Open In Colab

17. Esercizi di probabilità discreta#

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import math
import seaborn as sns
from collections import defaultdict
from scipy.constants import golden
%matplotlib inline
sns.set_theme(
    context="paper",
    style="darkgrid",
    palette="colorblind",
    rc={"figure.figsize": (5.0, 5.0 / golden)},
)

SEED = 12345
rng = np.random.default_rng(SEED)

Considereremo qui alcuni esempi che illustrano come, generando delle liste che corrispondono a spazi campione ed eventi, sia possibile calcolare la probabilità associata agli eventi definiti sullo spazio campione di un esperimento casuale. In questo capitolo ci focalizzeremo su tre esperimenti casuali che producono uno spazio campione discreto: lancio di monete, lancio di dadi, estrazione di carte da un mazzo ben mescolato. Inizieremo ad esaminare il caso del lancio di uno o più dadi e i giochi di carte. In seguito esamineremo delle funzioni specializzate che possono essere usate per modellare l’esperimento casuale corrispondene ad una serie di lanci di una moneta. Prima di fare questo, però, esamineremo alcune funzioni Python utili per il calcolo delle probabilità.

Probabilità di ottenere un numero minore di 4 dal lancio di un dado

Abbiamo già visto in precedenza come generare lo spazio campione dell’esperimento aleatorio che corrisponde al lancio di un dado equilibrato a 6 facce.

sample = [dice1 for dice1 in range(1, 7)]
list(sample)
[1, 2, 3, 4, 5, 6]

Su tale spazio campione definiamo un evento.

event = [roll for roll in sample if roll < 4]
print(list(event))
[1, 2, 3]

Calcoliamo la probabilità di osservare l’evento che abbiamo definito.

print(f"La probabilità dell'evento è {len(event)}/{len(sample)}.")
La probabilità dell'evento è 3/6.

La probabilità di ottenere almeno un 6 dal lancio di due dadi

Consideriamo un caso un po’ più complesso, ma che abbiamo già incontrato in precedenza. Iniziamo nuovamente a definire lo spazio campione dell’esperimento casuale. Si noti la sintassi della list comprehension.

sample = [(dice1, dice2) for dice1 in range(1, 7) for dice2 in range(1, 7)]
sample
[(1, 1),
 (1, 2),
 (1, 3),
 (1, 4),
 (1, 5),
 (1, 6),
 (2, 1),
 (2, 2),
 (2, 3),
 (2, 4),
 (2, 5),
 (2, 6),
 (3, 1),
 (3, 2),
 (3, 3),
 (3, 4),
 (3, 5),
 (3, 6),
 (4, 1),
 (4, 2),
 (4, 3),
 (4, 4),
 (4, 5),
 (4, 6),
 (5, 1),
 (5, 2),
 (5, 3),
 (5, 4),
 (5, 5),
 (5, 6),
 (6, 1),
 (6, 2),
 (6, 3),
 (6, 4),
 (6, 5),
 (6, 6)]

L’evento definito dal problema si verifica se è vera la condizione roll[0] == 6 (si ottiene un 6 con il primo dado) oppure se si verifica la condizione roll[1] (si ottiene un 6 con il secondo dado), oppure se si verificano entrambe. Si noti il connettivo logico or.

event = [roll for roll in sample if roll[0] == 6 or roll[1] == 6]
event
[(1, 6),
 (2, 6),
 (3, 6),
 (4, 6),
 (5, 6),
 (6, 1),
 (6, 2),
 (6, 3),
 (6, 4),
 (6, 5),
 (6, 6)]
print(f"{len(event)} / {len(sample)}")
11 / 36

La probabilità di non ottenere neppure un 6 dal lancio di due dadi

La soluzione di questo problema richiede che si neghino le due condizioni definite in precedenza.

event = [roll for roll in sample if roll[0] != 6 and roll[1] != 6]
event
[(1, 1),
 (1, 2),
 (1, 3),
 (1, 4),
 (1, 5),
 (2, 1),
 (2, 2),
 (2, 3),
 (2, 4),
 (2, 5),
 (3, 1),
 (3, 2),
 (3, 3),
 (3, 4),
 (3, 5),
 (4, 1),
 (4, 2),
 (4, 3),
 (4, 4),
 (4, 5),
 (5, 1),
 (5, 2),
 (5, 3),
 (5, 4),
 (5, 5)]
print(f"{len(event)} / {len(sample)}")
25 / 36

In maniera equivalente, la soluzione è data dalla probabilità dell’evento complementare a quello definito dal problema precedente: 1 - 11/36.

La probabilità che lanciando contemporaneamente due dadi a 6 facce la somma faccia 4

In questo caso la condizione logica che viene definita nella list comprehension è if sum(roll) == 4.

event = [roll for roll in sample if sum(roll) == 4]
event
[(1, 3), (2, 2), (3, 1)]

Troviamo la probabilità.

print(f"{len(event)} / {len(sample)}")
3 / 36

La probabilità che lanciando contemporaneamente tre dadi a 6 facce la somma faccia 10

Questo problema è solo una variante del problema precedente. L’unica differenza di rilievo è che dobbiamo costruire uno spazio campione corrispondente al prodotto cartesiano dell’insieme dei punti di un dado elevato alla terza potenza.

r = range(1, 7)
sample = [(i, j, k) for i in r for j in r for k in r]
event = [roll for roll in sample if sum(roll) == 10]
print(event)
print(f"{len(event)} / {len(sample)}")
[(1, 3, 6), (1, 4, 5), (1, 5, 4), (1, 6, 3), (2, 2, 6), (2, 3, 5), (2, 4, 4), (2, 5, 3), (2, 6, 2), (3, 1, 6), (3, 2, 5), (3, 3, 4), (3, 4, 3), (3, 5, 2), (3, 6, 1), (4, 1, 5), (4, 2, 4), (4, 3, 3), (4, 4, 2), (4, 5, 1), (5, 1, 4), (5, 2, 3), (5, 3, 2), (5, 4, 1), (6, 1, 3), (6, 2, 2), (6, 3, 1)]
27 / 216

La probabilità che lanciando contemporaneamente quattro dadi a 6 facce la somma faccia 13

La struttura logica è identica alla precedente, aumenta solo la dimensione dello spazio campione.

r = range(1, 7)
sample = [(i, j, k, l) for i in r for j in r for k in r for l in r]
event = [roll for roll in sample if sum(roll) == 13]
print(f"{len(event)} / {len(sample)}")
140 / 1296

La probabilità di ottenere un 6 e un altro numero (diverso da 6) nel lancio di due dadi a 6 facce

Questo problema introduce un nuovo modo per valutare una condizione logica in riferimento allo spazio campione. Il problema dice che dobbiamo ottenere solo un 6, con il primo o con il secondo dado, ma non con entrambi. Dobbiamo dunque esaminare ciascun punto dello spazio campione (una tupla di due elementi) e verificare se contiene un solo 6. Per fare questo definiamo la funzione numsix() che prende come argomento una tupla e ritorna il numero di 6 che ha trovato. Avendo definito questa funzione, la applichiamo a tutti i punti dello spazio campione e estraiamo gli elementi nei quali la funzione ritorna 1 (ovvero, i punti campione nei quali c’è un solo 6).

sample = [(i, j) for i in r for j in r]


def numsix(roll):
    return len([dice for dice in roll if dice == 6])


event = [roll for roll in sample if numsix(roll) == 1]
print(event)
[(1, 6), (2, 6), (3, 6), (4, 6), (5, 6), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5)]

La probabilità cercata è dunque 10/36.

print(f"{len(event)} / {len(sample)}")
10 / 36

La probabilità di ottenere un 6 e un altro numero (diverso da 6) nel lancio di quattro dadi a 6 facce

Questo problema è simile al precedente, ma considera uno spazio campione più grande.

sample = [(i, j, k, l) for i in r for j in r for k in r for l in r]

event = [roll for roll in sample if numsix(roll) == 1]
print(f"{len(event)} / {len(sample)}")
500 / 1296

La probabilità di ottenere tre 6 e un altro numero (diverso da 6) nel lancio di quattro dadi a 6 facce

Qui cambia solo la quantificazione della condizione logica usata prima.

event = [roll for roll in sample if numsix(roll)==3]
print(f"{len(event)} / {len(sample)}")
20 / 1296

Esaminiamo ora alcuni esempio relativi ai giochi di carte. Per questi scopi abbiamo bisogno di generare degli spazi campione che corrispondono a selezioni di elementi nelle quali l’ordine non conta e gli elementi non possono essere ripetuti. Questa è la definizione di una combinazione semplice – si veda la sezione Combinazioni semplici. Importiamo la funzione combinations da itertools.

from itertools import combinations, product

Per chiarire la procedura, iniziamo a considerare un mazzo di carte molto piccolo, in cui abbiamo solo due semi e tre numeri. Generiamo il mazzo di carte.

cards = list(product(range(1,3), range(1,4)))
cards
[(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)]

Diciamo che il primo valore di ciascuna delle precedenti coppie di numeri corrisponde al seme e il secondo valore corrisponde al numero. Se vogliamo, possiamo immaginare cuori e picche con i numeri 1, 2, 3.

Iniziamo a chiederci quante mani di due carte siano possibili per questo mazzo di carte. Ci sono 15 possibilità. Usiamo la formula delle combinazioni semplici.

math.comb(6, 2)
15

Elenchiamo tutte le possibili combinazioni semplici.

sample = list(combinations(cards, 2))
sample
[((1, 1), (1, 2)),
 ((1, 1), (1, 3)),
 ((1, 1), (2, 1)),
 ((1, 1), (2, 2)),
 ((1, 1), (2, 3)),
 ((1, 2), (1, 3)),
 ((1, 2), (2, 1)),
 ((1, 2), (2, 2)),
 ((1, 2), (2, 3)),
 ((1, 3), (2, 1)),
 ((1, 3), (2, 2)),
 ((1, 3), (2, 3)),
 ((2, 1), (2, 2)),
 ((2, 1), (2, 3)),
 ((2, 2), (2, 3))]
len(sample)
15

Chiediamoci ora quale sia la probabilità di una coppia di assi. Ovviamente nello spazio campione precedente c’è solo un modo di ottenere una coppia di assi: ((1, 1), (2, 1)). Ma poniamoci il problema di trovare questa soluzione in maniera algoritmica.

def numval(hand, val):
    return len([card for card in hand if card[1] == val])


def numace(hand):
    return numval(hand, 1)


event = [hand for hand in sample if numace(hand) == 2]
print(f"{len(event)} / {len(sample)}")
1 / 15

La probabilità di un pocker d’assi

Ora che abbiamo capito come fare, possiamo usare la procedura descritta sopra per calcolare un evento più interessante, ovvero la probabilità di ottenere un pocker d’assi in un regolare mazzo da pocker di 52 carte. Iniziamo trovando il numero di possibili di mani di 5 carte:

cards = list(product(range(1,5), range(1,14)))
sample = list(combinations(cards, 5))
len(sample)
2598960

Troviamo ora la probabilità di un pocker d’assi.

event = [hand for hand in sample if numace(hand) == 4]
print(f"{len(event)} / {len(sample)}")
48 / 2598960

La probabilità di una coppia d’assi e una coppia di Jack

In una variante di questo problema, troviamo la probabilità di una coppia d’assi e una coppia di Jack:

def numjack(hand):
    return numval(hand, 11)


event = [hand for hand in sample if numace(hand) == 2 and numjack(hand) == 2]
print(f"{len(event)} / {len(sample)}")
1584 / 2598960

Concludiamo con alcuni esempi discussi nel secondo capitolo del testo di Unpingco [Unp22].

Consideriamo nuovamente l’esperimento casuale corrispondente al lancio di due dati equilibrati. Sia \(X\) la v.c. corrispondente alla somma dei punti prodotti dai due lanci. Poniamoci il problema di trovare la distribuzione di massa di probabilità di \(X\) e la probabilità associata a diversi eventi. Vedremo qui una procedura alternativa per risolvere questo problema rispetto quella discussa in precedenza.

Per affrontare questo problema, Unpingco [Unp22] inizia a costruire un dizionario Python i cui elementi sono i punti del prodotto cartesiano i cui elementi sono della forma (a,b), dove \(a\) appartiene ad \(A\) = {1, 2, 3, 4, 5, 6} (punti del primo dado) e \(b\) appartiene a \(B\) = {1, 2, 3, 4, 5, 6} (punti del secondo dado).

d = {(i, j): i + j for i in range(1, 7) for j in range(1, 7)}
d
{(1, 1): 2,
 (1, 2): 3,
 (1, 3): 4,
 (1, 4): 5,
 (1, 5): 6,
 (1, 6): 7,
 (2, 1): 3,
 (2, 2): 4,
 (2, 3): 5,
 (2, 4): 6,
 (2, 5): 7,
 (2, 6): 8,
 (3, 1): 4,
 (3, 2): 5,
 (3, 3): 6,
 (3, 4): 7,
 (3, 5): 8,
 (3, 6): 9,
 (4, 1): 5,
 (4, 2): 6,
 (4, 3): 7,
 (4, 4): 8,
 (4, 5): 9,
 (4, 6): 10,
 (5, 1): 6,
 (5, 2): 7,
 (5, 3): 8,
 (5, 4): 9,
 (5, 5): 10,
 (5, 6): 11,
 (6, 1): 7,
 (6, 2): 8,
 (6, 3): 9,
 (6, 4): 10,
 (6, 5): 11,
 (6, 6): 12}

Il passo successivo è quello di raccogliere tutte le coppie (a,b) la cui somma corrisponde a ciascuno dei possibili valori da 2 a 12. Per fare questo, Unpingco [Unp22] utilizza la funzione defaultdict():

dinv = defaultdict(list)
for i, j in d.items():
    dinv[j].append(i)

dinv
defaultdict(list,
            {2: [(1, 1)],
             3: [(1, 2), (2, 1)],
             4: [(1, 3), (2, 2), (3, 1)],
             5: [(1, 4), (2, 3), (3, 2), (4, 1)],
             6: [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)],
             7: [(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)],
             8: [(2, 6), (3, 5), (4, 4), (5, 3), (6, 2)],
             9: [(3, 6), (4, 5), (5, 4), (6, 3)],
             10: [(4, 6), (5, 5), (6, 4)],
             11: [(5, 6), (6, 5)],
             12: [(6, 6)]})

A questo punto è possibile ottenere una lista di tutte le coppie la cui somma è 7, per esempio:

dinv[7]
[(1, 6), (2, 5), (3, 4), (4, 3), (5, 2), (6, 1)]

Il passo successivo consiste nel calcolare la probabilità misurata per ciascun elemento di dinv. Utilizzando l’ipotesi di indipendenza, ciò significa che dobbiamo calcolare la somma dei prodotti delle probabilità dei singoli elementi in dinv. Poiché sappiamo che ogni risultato è ugualmente probabile, la probabilità di ogni termine nella somma è uguale a 1/36. Pertanto, tutto ciò che dobbiamo fare è contare il numero di elementi nell’elenco corrispondente per ogni key in dinv e dividere per 36. Ad esempio, dinv[11] contiene [(5, 6), (6, 5)]. La probabilità di 5+6=6+5=11 è la probabilità di questo insieme, che è composto dalla somma delle probabilità dei singoli elementi {(5,6),(6,5)}. In questo caso, abbiamo P(11) = P({(5, 6)}) + P({(6, 5)}) = 1/36 + 1/36 = 2/36. Ripetendo questa procedura per tutti gli elementi, deriviamo la funzione di massa di probabilità come mostrato di seguito:

X = {i: len(j) / 36.0 for i, j in dinv.items()}
X
{2: 0.027777777777777776,
 3: 0.05555555555555555,
 4: 0.08333333333333333,
 5: 0.1111111111111111,
 6: 0.1388888888888889,
 7: 0.16666666666666666,
 8: 0.1388888888888889,
 9: 0.1111111111111111,
 10: 0.08333333333333333,
 11: 0.05555555555555555,
 12: 0.027777777777777776}

Unpingco [Unp22] afferma che questo esempio mostra quali sono gli elementi della teoria della probabilità che sono in gioco in questo semplice problema, sopprimendo deliberatamente alcuni dei dettagli tecnici più fastidiosi.

Esaminiamo con più attenzione la funzione defaultdict(). Si noti che, nell’esempio sopra, essa prende come argomento list. Consideriamo con il seguente problema. Vogliamo contare quante volte ciascuna parola compare in un testo. Un primo modo per affrontare il problema è il seguente: creiamo un dizionario a cui aggiungiamo come key ciascuna parola (se non è già presente nel dizionario) e, come valore, il numero di occorrenze:

text = "Suppose I need to count the number of word occurrences in a text. How could I do that? Python provides us with multiple ways to do this same thing."
word_count_dict = {}
for w in text.split(" "):
    if w in word_count_dict:
        word_count_dict[w]+=1
    else:
        word_count_dict[w]=1

word_count_dict
{'Suppose': 1,
 'I': 2,
 'need': 1,
 'to': 2,
 'count': 1,
 'the': 1,
 'number': 1,
 'of': 1,
 'word': 1,
 'occurrences': 1,
 'in': 1,
 'a': 1,
 'text.': 1,
 'How': 1,
 'could': 1,
 'do': 2,
 'that?': 1,
 'Python': 1,
 'provides': 1,
 'us': 1,
 'with': 1,
 'multiple': 1,
 'ways': 1,
 'this': 1,
 'same': 1,
 'thing.': 1}

Lo stesso risultato si ottiene con defaultdict(). In questo caso

word_count_dict = defaultdict(int)
for w in text.split(" "):
    word_count_dict[w] += 1

word_count_dict
defaultdict(int,
            {'Suppose': 1,
             'I': 2,
             'need': 1,
             'to': 2,
             'count': 1,
             'the': 1,
             'number': 1,
             'of': 1,
             'word': 1,
             'occurrences': 1,
             'in': 1,
             'a': 1,
             'text.': 1,
             'How': 1,
             'could': 1,
             'do': 2,
             'that?': 1,
             'Python': 1,
             'provides': 1,
             'us': 1,
             'with': 1,
             'multiple': 1,
             'ways': 1,
             'this': 1,
             'same': 1,
             'thing.': 1})

Ciò rende chiaro che, in questo caso, defaultdict() viene usato per creare un dizionario dove associamo a ciascuna key la frequenza con la quale essa è presente nell’oggetto text. In questo secondo esempio, defaultdict() prende come argomento int perché ritornerà un integer.

Nel caso del lancio dei due dadi, invece, l’argomento di defaultdict() è list, perché a ciascuna key verrà associata una lista. d.items() sono i dict_items.

d.items()
dict_items([((1, 1), 2), ((1, 2), 3), ((1, 3), 4), ((1, 4), 5), ((1, 5), 6), ((1, 6), 7), ((2, 1), 3), ((2, 2), 4), ((2, 3), 5), ((2, 4), 6), ((2, 5), 7), ((2, 6), 8), ((3, 1), 4), ((3, 2), 5), ((3, 3), 6), ((3, 4), 7), ((3, 5), 8), ((3, 6), 9), ((4, 1), 5), ((4, 2), 6), ((4, 3), 7), ((4, 4), 8), ((4, 5), 9), ((4, 6), 10), ((5, 1), 6), ((5, 2), 7), ((5, 3), 8), ((5, 4), 9), ((5, 5), 10), ((5, 6), 11), ((6, 1), 7), ((6, 2), 8), ((6, 3), 9), ((6, 4), 10), ((6, 5), 11), ((6, 6), 12)])

Il ciclo for i, j in d.items(): fa riferimento a ciascuno degli elementi del dizionario d.

for i, j in d.items():
    print(i,j)
(1, 1) 2
(1, 2) 3
(1, 3) 4
(1, 4) 5
(1, 5) 6
(1, 6) 7
(2, 1) 3
(2, 2) 4
(2, 3) 5
(2, 4) 6
(2, 5) 7
(2, 6) 8
(3, 1) 4
(3, 2) 5
(3, 3) 6
(3, 4) 7
(3, 5) 8
(3, 6) 9
(4, 1) 5
(4, 2) 6
(4, 3) 7
(4, 4) 8
(4, 5) 9
(4, 6) 10
(5, 1) 6
(5, 2) 7
(5, 3) 8
(5, 4) 9
(5, 5) 10
(5, 6) 11
(6, 1) 7
(6, 2) 8
(6, 3) 9
(6, 4) 10
(6, 5) 11
(6, 6) 12

L’indice i indica le coppie

for i, j in d.items():
    print(i)
(1, 1)
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(1, 6)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(2, 5)
(2, 6)
(3, 1)
(3, 2)
(3, 3)
(3, 4)
(3, 5)
(3, 6)
(4, 1)
(4, 2)
(4, 3)
(4, 4)
(4, 5)
(4, 6)
(5, 1)
(5, 2)
(5, 3)
(5, 4)
(5, 5)
(5, 6)
(6, 1)
(6, 2)
(6, 3)
(6, 4)
(6, 5)
(6, 6)

Mentre j fa riferimento alla somma

for i, j in d.items():
    print(j)
2
3
4
5
6
7
3
4
5
6
7
8
4
5
6
7
8
9
5
6
7
8
9
10
6
7
8
9
10
11
7
8
9
10
11
12

Ora possiamo usare defaultdict(list). Per ciascun valore della somma (j), appendiamo alla lista ad esso associata gli elementi (i) che producono tale somma:

dinv = defaultdict(list)
for i, j in d.items():
    dinv[j].append(i)

Avendo ottenuto il risultato riportato sopra, Unpingco [Unp22] si pone un altra domanda: qual è la probabilità che la metà del prodotto dei punti prodotti da tre dadi superi la loro somma? Possiamo risolverlo usando lo stesso metodo del seguente. Innanzitutto, si crea lo spazio campione dell’esperimento casuale, ovvero un dizionario a cui, per ciascun punto dello spazio campione si aggiunge una variabile booleana che è True se la condizione specificata è soddisfatta ed è False altrimenti.

d = {
    (i, j, k): ((i * j * k) / 2 > i + j + k)
    for i in range(1, 7)
    for j in range(1, 7)
    for k in range(1, 7)
}
d
{(1, 1, 1): False,
 (1, 1, 2): False,
 (1, 1, 3): False,
 (1, 1, 4): False,
 (1, 1, 5): False,
 (1, 1, 6): False,
 (1, 2, 1): False,
 (1, 2, 2): False,
 (1, 2, 3): False,
 (1, 2, 4): False,
 (1, 2, 5): False,
 (1, 2, 6): False,
 (1, 3, 1): False,
 (1, 3, 2): False,
 (1, 3, 3): False,
 (1, 3, 4): False,
 (1, 3, 5): False,
 (1, 3, 6): False,
 (1, 4, 1): False,
 (1, 4, 2): False,
 (1, 4, 3): False,
 (1, 4, 4): False,
 (1, 4, 5): False,
 (1, 4, 6): True,
 (1, 5, 1): False,
 (1, 5, 2): False,
 (1, 5, 3): False,
 (1, 5, 4): False,
 (1, 5, 5): True,
 (1, 5, 6): True,
 (1, 6, 1): False,
 (1, 6, 2): False,
 (1, 6, 3): False,
 (1, 6, 4): True,
 (1, 6, 5): True,
 (1, 6, 6): True,
 (2, 1, 1): False,
 (2, 1, 2): False,
 (2, 1, 3): False,
 (2, 1, 4): False,
 (2, 1, 5): False,
 (2, 1, 6): False,
 (2, 2, 1): False,
 (2, 2, 2): False,
 (2, 2, 3): False,
 (2, 2, 4): False,
 (2, 2, 5): True,
 (2, 2, 6): True,
 (2, 3, 1): False,
 (2, 3, 2): False,
 (2, 3, 3): True,
 (2, 3, 4): True,
 (2, 3, 5): True,
 (2, 3, 6): True,
 (2, 4, 1): False,
 (2, 4, 2): False,
 (2, 4, 3): True,
 (2, 4, 4): True,
 (2, 4, 5): True,
 (2, 4, 6): True,
 (2, 5, 1): False,
 (2, 5, 2): True,
 (2, 5, 3): True,
 (2, 5, 4): True,
 (2, 5, 5): True,
 (2, 5, 6): True,
 (2, 6, 1): False,
 (2, 6, 2): True,
 (2, 6, 3): True,
 (2, 6, 4): True,
 (2, 6, 5): True,
 (2, 6, 6): True,
 (3, 1, 1): False,
 (3, 1, 2): False,
 (3, 1, 3): False,
 (3, 1, 4): False,
 (3, 1, 5): False,
 (3, 1, 6): False,
 (3, 2, 1): False,
 (3, 2, 2): False,
 (3, 2, 3): True,
 (3, 2, 4): True,
 (3, 2, 5): True,
 (3, 2, 6): True,
 (3, 3, 1): False,
 (3, 3, 2): True,
 (3, 3, 3): True,
 (3, 3, 4): True,
 (3, 3, 5): True,
 (3, 3, 6): True,
 (3, 4, 1): False,
 (3, 4, 2): True,
 (3, 4, 3): True,
 (3, 4, 4): True,
 (3, 4, 5): True,
 (3, 4, 6): True,
 (3, 5, 1): False,
 (3, 5, 2): True,
 (3, 5, 3): True,
 (3, 5, 4): True,
 (3, 5, 5): True,
 (3, 5, 6): True,
 (3, 6, 1): False,
 (3, 6, 2): True,
 (3, 6, 3): True,
 (3, 6, 4): True,
 (3, 6, 5): True,
 (3, 6, 6): True,
 (4, 1, 1): False,
 (4, 1, 2): False,
 (4, 1, 3): False,
 (4, 1, 4): False,
 (4, 1, 5): False,
 (4, 1, 6): True,
 (4, 2, 1): False,
 (4, 2, 2): False,
 (4, 2, 3): True,
 (4, 2, 4): True,
 (4, 2, 5): True,
 (4, 2, 6): True,
 (4, 3, 1): False,
 (4, 3, 2): True,
 (4, 3, 3): True,
 (4, 3, 4): True,
 (4, 3, 5): True,
 (4, 3, 6): True,
 (4, 4, 1): False,
 (4, 4, 2): True,
 (4, 4, 3): True,
 (4, 4, 4): True,
 (4, 4, 5): True,
 (4, 4, 6): True,
 (4, 5, 1): False,
 (4, 5, 2): True,
 (4, 5, 3): True,
 (4, 5, 4): True,
 (4, 5, 5): True,
 (4, 5, 6): True,
 (4, 6, 1): True,
 (4, 6, 2): True,
 (4, 6, 3): True,
 (4, 6, 4): True,
 (4, 6, 5): True,
 (4, 6, 6): True,
 (5, 1, 1): False,
 (5, 1, 2): False,
 (5, 1, 3): False,
 (5, 1, 4): False,
 (5, 1, 5): True,
 (5, 1, 6): True,
 (5, 2, 1): False,
 (5, 2, 2): True,
 (5, 2, 3): True,
 (5, 2, 4): True,
 (5, 2, 5): True,
 (5, 2, 6): True,
 (5, 3, 1): False,
 (5, 3, 2): True,
 (5, 3, 3): True,
 (5, 3, 4): True,
 (5, 3, 5): True,
 (5, 3, 6): True,
 (5, 4, 1): False,
 (5, 4, 2): True,
 (5, 4, 3): True,
 (5, 4, 4): True,
 (5, 4, 5): True,
 (5, 4, 6): True,
 (5, 5, 1): True,
 (5, 5, 2): True,
 (5, 5, 3): True,
 (5, 5, 4): True,
 (5, 5, 5): True,
 (5, 5, 6): True,
 (5, 6, 1): True,
 (5, 6, 2): True,
 (5, 6, 3): True,
 (5, 6, 4): True,
 (5, 6, 5): True,
 (5, 6, 6): True,
 (6, 1, 1): False,
 (6, 1, 2): False,
 (6, 1, 3): False,
 (6, 1, 4): True,
 (6, 1, 5): True,
 (6, 1, 6): True,
 (6, 2, 1): False,
 (6, 2, 2): True,
 (6, 2, 3): True,
 (6, 2, 4): True,
 (6, 2, 5): True,
 (6, 2, 6): True,
 (6, 3, 1): False,
 (6, 3, 2): True,
 (6, 3, 3): True,
 (6, 3, 4): True,
 (6, 3, 5): True,
 (6, 3, 6): True,
 (6, 4, 1): True,
 (6, 4, 2): True,
 (6, 4, 3): True,
 (6, 4, 4): True,
 (6, 4, 5): True,
 (6, 4, 6): True,
 (6, 5, 1): True,
 (6, 5, 2): True,
 (6, 5, 3): True,
 (6, 5, 4): True,
 (6, 5, 5): True,
 (6, 5, 6): True,
 (6, 6, 1): True,
 (6, 6, 2): True,
 (6, 6, 3): True,
 (6, 6, 4): True,
 (6, 6, 5): True,
 (6, 6, 6): True}
dinv = defaultdict(list)
for i, j in d.items():
    dinv[j].append(i)

dinv
defaultdict(list,
            {False: [(1, 1, 1),
              (1, 1, 2),
              (1, 1, 3),
              (1, 1, 4),
              (1, 1, 5),
              (1, 1, 6),
              (1, 2, 1),
              (1, 2, 2),
              (1, 2, 3),
              (1, 2, 4),
              (1, 2, 5),
              (1, 2, 6),
              (1, 3, 1),
              (1, 3, 2),
              (1, 3, 3),
              (1, 3, 4),
              (1, 3, 5),
              (1, 3, 6),
              (1, 4, 1),
              (1, 4, 2),
              (1, 4, 3),
              (1, 4, 4),
              (1, 4, 5),
              (1, 5, 1),
              (1, 5, 2),
              (1, 5, 3),
              (1, 5, 4),
              (1, 6, 1),
              (1, 6, 2),
              (1, 6, 3),
              (2, 1, 1),
              (2, 1, 2),
              (2, 1, 3),
              (2, 1, 4),
              (2, 1, 5),
              (2, 1, 6),
              (2, 2, 1),
              (2, 2, 2),
              (2, 2, 3),
              (2, 2, 4),
              (2, 3, 1),
              (2, 3, 2),
              (2, 4, 1),
              (2, 4, 2),
              (2, 5, 1),
              (2, 6, 1),
              (3, 1, 1),
              (3, 1, 2),
              (3, 1, 3),
              (3, 1, 4),
              (3, 1, 5),
              (3, 1, 6),
              (3, 2, 1),
              (3, 2, 2),
              (3, 3, 1),
              (3, 4, 1),
              (3, 5, 1),
              (3, 6, 1),
              (4, 1, 1),
              (4, 1, 2),
              (4, 1, 3),
              (4, 1, 4),
              (4, 1, 5),
              (4, 2, 1),
              (4, 2, 2),
              (4, 3, 1),
              (4, 4, 1),
              (4, 5, 1),
              (5, 1, 1),
              (5, 1, 2),
              (5, 1, 3),
              (5, 1, 4),
              (5, 2, 1),
              (5, 3, 1),
              (5, 4, 1),
              (6, 1, 1),
              (6, 1, 2),
              (6, 1, 3),
              (6, 2, 1),
              (6, 3, 1)],
             True: [(1, 4, 6),
              (1, 5, 5),
              (1, 5, 6),
              (1, 6, 4),
              (1, 6, 5),
              (1, 6, 6),
              (2, 2, 5),
              (2, 2, 6),
              (2, 3, 3),
              (2, 3, 4),
              (2, 3, 5),
              (2, 3, 6),
              (2, 4, 3),
              (2, 4, 4),
              (2, 4, 5),
              (2, 4, 6),
              (2, 5, 2),
              (2, 5, 3),
              (2, 5, 4),
              (2, 5, 5),
              (2, 5, 6),
              (2, 6, 2),
              (2, 6, 3),
              (2, 6, 4),
              (2, 6, 5),
              (2, 6, 6),
              (3, 2, 3),
              (3, 2, 4),
              (3, 2, 5),
              (3, 2, 6),
              (3, 3, 2),
              (3, 3, 3),
              (3, 3, 4),
              (3, 3, 5),
              (3, 3, 6),
              (3, 4, 2),
              (3, 4, 3),
              (3, 4, 4),
              (3, 4, 5),
              (3, 4, 6),
              (3, 5, 2),
              (3, 5, 3),
              (3, 5, 4),
              (3, 5, 5),
              (3, 5, 6),
              (3, 6, 2),
              (3, 6, 3),
              (3, 6, 4),
              (3, 6, 5),
              (3, 6, 6),
              (4, 1, 6),
              (4, 2, 3),
              (4, 2, 4),
              (4, 2, 5),
              (4, 2, 6),
              (4, 3, 2),
              (4, 3, 3),
              (4, 3, 4),
              (4, 3, 5),
              (4, 3, 6),
              (4, 4, 2),
              (4, 4, 3),
              (4, 4, 4),
              (4, 4, 5),
              (4, 4, 6),
              (4, 5, 2),
              (4, 5, 3),
              (4, 5, 4),
              (4, 5, 5),
              (4, 5, 6),
              (4, 6, 1),
              (4, 6, 2),
              (4, 6, 3),
              (4, 6, 4),
              (4, 6, 5),
              (4, 6, 6),
              (5, 1, 5),
              (5, 1, 6),
              (5, 2, 2),
              (5, 2, 3),
              (5, 2, 4),
              (5, 2, 5),
              (5, 2, 6),
              (5, 3, 2),
              (5, 3, 3),
              (5, 3, 4),
              (5, 3, 5),
              (5, 3, 6),
              (5, 4, 2),
              (5, 4, 3),
              (5, 4, 4),
              (5, 4, 5),
              (5, 4, 6),
              (5, 5, 1),
              (5, 5, 2),
              (5, 5, 3),
              (5, 5, 4),
              (5, 5, 5),
              (5, 5, 6),
              (5, 6, 1),
              (5, 6, 2),
              (5, 6, 3),
              (5, 6, 4),
              (5, 6, 5),
              (5, 6, 6),
              (6, 1, 4),
              (6, 1, 5),
              (6, 1, 6),
              (6, 2, 2),
              (6, 2, 3),
              (6, 2, 4),
              (6, 2, 5),
              (6, 2, 6),
              (6, 3, 2),
              (6, 3, 3),
              (6, 3, 4),
              (6, 3, 5),
              (6, 3, 6),
              (6, 4, 1),
              (6, 4, 2),
              (6, 4, 3),
              (6, 4, 4),
              (6, 4, 5),
              (6, 4, 6),
              (6, 5, 1),
              (6, 5, 2),
              (6, 5, 3),
              (6, 5, 4),
              (6, 5, 5),
              (6, 5, 6),
              (6, 6, 1),
              (6, 6, 2),
              (6, 6, 3),
              (6, 6, 4),
              (6, 6, 5),
              (6, 6, 6)]})
X = {i: len(j) / 6.0**3 for i, j in dinv.items()}
print(X)
{False: 0.37037037037037035, True: 0.6296296296296297}
d = pd.DataFrame(
    index=[(i, j) for i in range(1, 7) for j in range(1, 7)],
    columns=["sm", "d1", "d2", "pd1", "pd2", "p"],
)
d
sm d1 d2 pd1 pd2 p
(1, 1) NaN NaN NaN NaN NaN NaN
(1, 2) NaN NaN NaN NaN NaN NaN
(1, 3) NaN NaN NaN NaN NaN NaN
(1, 4) NaN NaN NaN NaN NaN NaN
(1, 5) NaN NaN NaN NaN NaN NaN
(1, 6) NaN NaN NaN NaN NaN NaN
(2, 1) NaN NaN NaN NaN NaN NaN
(2, 2) NaN NaN NaN NaN NaN NaN
(2, 3) NaN NaN NaN NaN NaN NaN
(2, 4) NaN NaN NaN NaN NaN NaN
(2, 5) NaN NaN NaN NaN NaN NaN
(2, 6) NaN NaN NaN NaN NaN NaN
(3, 1) NaN NaN NaN NaN NaN NaN
(3, 2) NaN NaN NaN NaN NaN NaN
(3, 3) NaN NaN NaN NaN NaN NaN
(3, 4) NaN NaN NaN NaN NaN NaN
(3, 5) NaN NaN NaN NaN NaN NaN
(3, 6) NaN NaN NaN NaN NaN NaN
(4, 1) NaN NaN NaN NaN NaN NaN
(4, 2) NaN NaN NaN NaN NaN NaN
(4, 3) NaN NaN NaN NaN NaN NaN
(4, 4) NaN NaN NaN NaN NaN NaN
(4, 5) NaN NaN NaN NaN NaN NaN
(4, 6) NaN NaN NaN NaN NaN NaN
(5, 1) NaN NaN NaN NaN NaN NaN
(5, 2) NaN NaN NaN NaN NaN NaN
(5, 3) NaN NaN NaN NaN NaN NaN
(5, 4) NaN NaN NaN NaN NaN NaN
(5, 5) NaN NaN NaN NaN NaN NaN
(5, 6) NaN NaN NaN NaN NaN NaN
(6, 1) NaN NaN NaN NaN NaN NaN
(6, 2) NaN NaN NaN NaN NaN NaN
(6, 3) NaN NaN NaN NaN NaN NaN
(6, 4) NaN NaN NaN NaN NaN NaN
(6, 5) NaN NaN NaN NaN NaN NaN
(6, 6) NaN NaN NaN NaN NaN NaN
d.d1 = [i[0] for i in d.index]
d.d2 = [i[1] for i in d.index]

d.head(), d.tail()
(         sm  d1  d2  pd1  pd2    p
 (1, 1)  NaN   1   1  NaN  NaN  NaN
 (1, 2)  NaN   1   2  NaN  NaN  NaN
 (1, 3)  NaN   1   3  NaN  NaN  NaN
 (1, 4)  NaN   1   4  NaN  NaN  NaN
 (1, 5)  NaN   1   5  NaN  NaN  NaN,
          sm  d1  d2  pd1  pd2    p
 (6, 2)  NaN   6   2  NaN  NaN  NaN
 (6, 3)  NaN   6   3  NaN  NaN  NaN
 (6, 4)  NaN   6   4  NaN  NaN  NaN
 (6, 5)  NaN   6   5  NaN  NaN  NaN
 (6, 6)  NaN   6   6  NaN  NaN  NaN)
d["sm"] = d["d1"] + d["d2"]
d.head()
sm d1 d2 pd1 pd2 p
(1, 1) 2 1 1 NaN NaN NaN
(1, 2) 3 1 2 NaN NaN NaN
(1, 3) 4 1 3 NaN NaN NaN
(1, 4) 5 1 4 NaN NaN NaN
(1, 5) 6 1 5 NaN NaN NaN
d["pd1"] = 1/6
d["pd2"] = 1/6
d["p"] = d["pd1"] * d["pd2"]
d.head()
sm d1 d2 pd1 pd2 p
(1, 1) 2 1 1 0.166667 0.166667 0.027778
(1, 2) 3 1 2 0.166667 0.166667 0.027778
(1, 3) 4 1 3 0.166667 0.166667 0.027778
(1, 4) 5 1 4 0.166667 0.166667 0.027778
(1, 5) 6 1 5 0.166667 0.166667 0.027778
d["p"].sum()
1.0
d.groupby('sm')['p'].sum()
sm
2     0.027778
3     0.055556
4     0.083333
5     0.111111
6     0.138889
7     0.166667
8     0.138889
9     0.111111
10    0.083333
11    0.055556
12    0.027778
Name: p, dtype: float64

17.1. Watermark#

%load_ext watermark
%watermark -n -u -v -iv -w
Last updated: Sat Jun 17 2023

Python implementation: CPython
Python version       : 3.11.3
IPython version      : 8.12.0

pandas    : 1.5.3
seaborn   : 0.12.2
numpy     : 1.24.3
matplotlib: 3.7.1

Watermark: 2.3.1