<jupyter_start><jupyter_text>1. Factors<jupyter_code>%run '_preamble.ipynb'
import itertools

import thomas.core
from thomas.core.factors import Factor
from thomas.core import examples

print(f"Using thomas.core version '{thomas.core.__version__}'")

from IPython.display import display, HTML
# Create the Factors for the Sprinkler network. 
# Note that the order in which the states are defined is important. 
# As such, this will only work in Python ≥ 3.6!          ^^^^^^^^^

# P(A)
fA = Factor(
    [0.6, 0.4], 
    {'A': ['a1', 'a0']}
)

# P(B|A)
fB_A = Factor(
    [0.2, 0.8, 0.75, 0.25], 
    {'A': ['a1', 'a0'],'B': ['b1', 'b0']}
)

# P(C|A)
fC_A = Factor(
    [0.8, 0.2, 0.1, 0.9], 
    {'A': ['a1', 'a0'],'C': ['c1', 'c0']}
)

# Define a factor that holds the *conditional* distribution P(D|BC)
fD_BC = Factor(
    [0.95, 0.05, 0.9, 0.1,0.8, 0.2, 0.0, 1.0], 
    {'B': ['b1', 'b0'],'C': ['c1', 'c0'],'D': ['d1', 'd0']}
)

# P(E|C)
fE_C = Factor(
    [0.7, 0.3, 0.0, 1.0], 
    {'C': ['c1', 'c0'],'E': ['e1', 'e0']}
)
# fA is essentially a prior distribution.
fA
# fB_A holds a conditional distribution: P(B|A)
fB_A
# Multiplying the factor with a *prior* with a *conditional* distribution, yields
# a *joint* distribution.
fAB = fA * fB_A
display(fAB)
# By summing out A, we'll get the prior over B
fAB.sum_out('A')
# Reordering the variables is done through reorder_scope()
(fB_A * fD_BC).reorder_scope(['A', 'B', 'D', 'C'])
# Factors can be easily serialized
fA.as_dict()
# Access individual entries as you would a dictionary
fA['a0']
# Note that the order of the states should correspond to the order of the variables.
fAB['a0', 'b1']
# Summing out removes a variable from the Factor.
fAB.sum_out('A')
# Projecting onto a variable is equivalent to summing out the complement
fA.project('A')
# Factors can also be added, altough this may be of limited use
fA + fB_A
# If you want, you can also convert to a pandas.Series
sfAB = fAB.as_series()
sfAB
# Or create a Factor from a Series. Note that this could change the order of the states.
# This is a result of the fact that (as far as I can find) pandas.MultiIndex doesn't 
# provide a way to keep the order.
Factor.from_series(sfAB)
# Factors can be set/updated using the `set` method. This is useful for factors
# that indicate evidence. This sets all entries with 'a0' to 0.
fAB.set(0, A='a0')
# This sets everything *but* 'b0' to 1.
fAB.set_complement(1, B='b0')
# Result can be chained
fAB.set(0, A='a0').set_complement(1, A='a0')
# If you'd rather to this in place, either pass 'inplace=True' to `set()` or
# use alternative assignment. 
fAB['a0'] = 1
# Note that in this case you're responsible for selecting the right variable:
fAB[:, 'b1'] = 2
print(fAB)
print()

try:
    fAB['b1'] = 2
except Exception as e:
    print('That will not work:', type(e), e)
    print()<jupyter_output>factor(A,B)
A   B 
a1  b1    2.00
    b0    0.48
a0  b1    2.00
    b0    1.00
dtype: float64

That will not work: <class 'KeyError'> 'b1'
{'fraction_non_alphanumeric': 0.1561350718823136, 'fraction_numerical': 0.03778000668672685, 'mean_word_length': 3.749206349206349, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '176580', 'license': 'MIT', 'n_tokens_mistral': 1225, 'n_tokens_neox': 1157, 'n_words': 410, 'path': 'notebooks/1. Factors.ipynb', 'repo_name': 'mellesies/thomas-core', 'size': '2991'}