Skip to content

Multinomial Simpson Paradox

this notebook shows a model for a multinomial Simpson paradox.

Creative Commons LicenseaGrUMinteractive online version
import pandas as pd
import pyagrum as gum
import pyagrum.lib.notebook as gnb
## building a model including a Simpson's paradox
import scipy.stats as stats
bn = gum.fastBN("A[0,90]->B[0:40:200]<-C[0,5]->A")
bn.cpt("C").fillFromDistribution(stats.uniform, loc=0, scale=5)
bn.cpt("A").fillFromDistribution(stats.uniform, loc="C*12", scale=30)
bn.cpt("B").fillFromDistribution(stats.norm, loc="5+C*4-int(A/8)", scale=3);

As a site note we are dealing here with quite large models already :

bn.memoryFootprint()
878016
help(bn.memoryFootprint)
Help on method memoryFootprint in module pyagrum.pyagrum:
memoryFootprint() -> int method of pyagrum.pyagrum.BayesNet instance
Returns the memory footprint in bytes of the Bayesian network.
Returns
-------
int
the memory footprint in bytes of the Bayesian network
# generating a CSV, taking this model as the causal one.
gum.generateSample(bn, 400, "out/sample.csv", with_labels=False)
df = pd.read_csv("out/sample.csv")
df.plot.scatter(x="A", y="B", c="C", colormap="tab20");

svg

cm = gum.CausalModel(bn)
_, p, _ = gum.causalImpact(cm, on="B", doing="A")
## building an Markov-equivalent model, generating a CSV, taking this model as the causal one.
bn2 = gum.BayesNet(bn)
bn2.reverseArc("C", "A")
bn2
G A A B B A->B C C A->C C->B
gum.generateSample(bn2, 400, "out/sample2.csv", with_labels=False)
df2 = pd.read_csv("out/sample2.csv")
cm2 = gum.CausalModel(bn2)
_, p2, _ = gum.causalImpact(cm2, on="B", doing="A")

The observationnal model and its paradoxal structure (exactly the same with the second Markov-equivalent model)

Section titled “The observationnal model and its paradoxal structure (exactly the same with the second Markov-equivalent model)”
gnb.flow.row(
gnb.getBN(bn),
df.plot.scatter(x="A", y="B"),
df.plot.scatter(x="A", y="B", c="C", colormap="tab20"),
captions=["the observationnal model", "the trend is increasing", "the trend is decreasing for any value for C !"],
)
gnb.flow.row(
gnb.getBN(bn2),
df2.plot.scatter(x="A", y="B"),
df2.plot.scatter(x="A", y="B", c="C", colormap="tab20"),
captions=["the Markov-equivalent model", "the trend is increasing", "the trend is decreasing for any value for C !"],
)
gnb.flow.display()
G A A B B A->B C C A->C C->B
the Markov-equivalent model
PyAgrum inline image
the trend is increasing
PyAgrum inline image
the trend is decreasing for any value for C !

The paradox is revealed in the trend of the inferred means : the means are increasing with the value of AA except for any value of CC

Section titled “The paradox is revealed in the trend of the inferred means : the means are increasing with the value of AA except for any value of CC …”
gum.config["notebook", "histogram_epsilon"] = 0.001
gum.config["notebook", "histogram_discretized_scale"] = 0.4
for a in [10, 20, 30]:
gnb.flow.add_html(gnb.getPosterior(bn, target="B", evs={"A": a}), f"$P(B|A={a})$")
gnb.flow.new_line()
for a in [10, 20, 30]:
gnb.flow.add_html(gnb.getPosterior(bn, target="B", evs={"A": a, "C": 0}), f"P(B | $A={a},C=0)$")
gnb.flow.new_line()
for a in [10, 20, 30]:
gnb.flow.add_html(gnb.getPosterior(bn, target="B", evs={"A": a, "C": 2}), f"P(B | $A={a},C=2$)")
gnb.flow.new_line()
for a in [10, 20, 30]:
gnb.flow.add_html(gnb.getPosterior(bn, target="B", evs={"A": a, "C": 4}), f"P(B | $A={a},C=4$)")
gnb.flow.display()
PyAgrum inline image
$P(B|A=10)$
PyAgrum inline image
$P(B|A=20)$
PyAgrum inline image
$P(B|A=30)$

PyAgrum inline image
P(B | $A=10,C=0)$
PyAgrum inline image
P(B | $A=20,C=0)$
PyAgrum inline image
P(B | $A=30,C=0)$

PyAgrum inline image
P(B | $A=10,C=2$)
PyAgrum inline image
P(B | $A=20,C=2$)
PyAgrum inline image
P(B | $A=30,C=2$)

PyAgrum inline image
P(B | $A=10,C=4$)
PyAgrum inline image
P(B | $A=20,C=4$)
PyAgrum inline image
P(B | $A=30,C=4$)

Now that the paradoxal structure is understood and the paradox is revealed, will we choose to observe CC (or not) before deciding to increase or decrease AA (with the goal to maximize BB) ?

Section titled “Now that the paradoxal structure is understood and the paradox is revealed, will we choose to observe CC (or not) before deciding to increase or decrease AA (with the goal to maximize BB) ?”

Of course, it depends on the causal structure of the problem !

If CC is cause for AA, observing CC really gives a new information about BB.

Section titled “If CC is cause for AA, observing CC really gives a new information about BB.”
gnb.flow.add_html(gnb.getCausalModel(cm), "the first causal model")
gnb.flow.new_line()
for v in [10, 20, 30]:
gnb.flow.add_html(gnb.getProba(p.extract({"A": v})), f"Doing $A={v}$")
gnb.flow.display()
A A B B A->B C C C->A C->B
the first causal model

PyAgrum inline image
Doing $A=10$
PyAgrum inline image
Doing $A=20$
PyAgrum inline image
Doing $A=30$

if AA is cause for CC, observing CC may lead to misinterpretations about the causal role of AA.

Section titled “if AA is cause for CC, observing CC may lead to misinterpretations about the causal role of AA.”
gnb.flow.add_html(gnb.getCausalModel(cm2), "the second causal model")
gnb.flow.new_line()
for v in [10, 20, 30]:
gnb.flow.add_html(gnb.getProba(p2.extract({"A": v})), f"Doing $A={v}$")
gnb.flow.display()
A A B B A->B C C A->C C->B
the second causal model

PyAgrum inline image
Doing $A=10$
PyAgrum inline image
Doing $A=20$
PyAgrum inline image
Doing $A=30$