Repository: pgmpy/pgmpy

Issue:
writing Bayesian model in a file and read it  later
### Subject of the issue
I am using pgmpy package for learning Bayesian Network from data and predicting using it.
I want to save learned Bayesian Network in a file and use it for predicting in another time.
I saw your example on pgmpy_notebook but i understood that ProbModelXMLWriter did not work correctly.
Is there another approach for doing this work?

Thanks.

### Your environment
* pgmpy version 0.1.3
* Python version 2.7
* Operating System Windows


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1216?src=pr&el=h1) Report
> Merging [#1216](https://codecov.io/gh/pgmpy/pgmpy/pull/1216?src=pr&el=desc) into [dev](https://codecov.io/gh/pgmpy/pgmpy/commit/c37dda4401f23ec73fc5d17d957867cd62e588d3?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/pgmpy/pgmpy/pull/1216/graphs/tree.svg?width=650&token=UaJMCdHaEF&height=150&src=pr)](https://codecov.io/gh/pgmpy/pgmpy/pull/1216?src=pr&el=tree)

```diff
@@          Coverage Diff          @@
##             dev   #1216   +/-   ##
=====================================
  Coverage   92.1%   92.1%           
=====================================
  Files        135     135           
  Lines      13167   13167           
=====================================
  Hits       12128   12128           
  Misses      1039    1039
```


| [Impacted Files](https://codecov.io/gh/pgmpy/pgmpy/pull/1216?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pgmpy/tests/test\_readwrite/test\_XMLBIF.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1216/diff?src=pr&el=tree#diff-cGdtcHkvdGVzdHMvdGVzdF9yZWFkd3JpdGUvdGVzdF9YTUxCSUYucHk=) | `95.96% <ø> (ø)` | :arrow_up: |
| [pgmpy/readwrite/XMLBIF.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1216/diff?src=pr&el=tree#diff-cGdtcHkvcmVhZHdyaXRlL1hNTEJJRi5weQ==) | `96.89% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1216?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1216?src=pr&el=footer). Last update [c37dda4...51a12f3](https://codecov.io/gh/pgmpy/pgmpy/pull/1216?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/pgmpy/estimators/BayesianEstimator.py b/pgmpy/estimators/BayesianEstimator.py
index 7e4fbcf2..ac085bb6 100644
--- a/pgmpy/estimators/BayesianEstimator.py
+++ b/pgmpy/estimators/BayesianEstimator.py
@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
 
+from itertools import chain
+
 import numpy as np
 
 from pgmpy.estimators import ParameterEstimator
@@ -66,7 +68,6 @@ class BayesianEstimator(ParameterEstimator):
         <TabularCPD representing P(D:2 | C:2) at 0x7f7b4df822b0>]
         """
         parameters = []
-
         for node in self.model.nodes():
             _equivalent_sample_size = (
                 equivalent_sample_size[node]
@@ -168,7 +169,7 @@ class BayesianEstimator(ParameterEstimator):
             np.array(bayesian_counts),
             evidence=parents,
             evidence_card=parents_cardinalities,
-            state_names=self.state_names,
+            state_names={var: self.state_names[var] for var in chain([node], parents)},
         )
         cpd.normalize()
         return cpd
diff --git a/pgmpy/estimators/MLE.py b/pgmpy/estimators/MLE.py
index 2b1c982e..ce2a513b 100644
--- a/pgmpy/estimators/MLE.py
+++ b/pgmpy/estimators/MLE.py
@@ -1,5 +1,7 @@
 # coding:utf-8
 
+from itertools import chain
+
 import numpy as np
 
 from pgmpy.estimators import ParameterEstimator
@@ -149,7 +151,7 @@ class MaximumLikelihoodEstimator(ParameterEstimator):
             np.array(state_counts),
             evidence=parents,
             evidence_card=parents_cardinalities,
-            state_names=state_names,
+            state_names={var: self.state_names[var] for var in chain([node], parents)},
         )
         cpd.normalize()
         return cpd
diff --git a/pgmpy/readwrite/BIF.py b/pgmpy/readwrite/BIF.py
index cc9cdd35..43a955bb 100644
--- a/pgmpy/readwrite/BIF.py
+++ b/pgmpy/readwrite/BIF.py
@@ -317,6 +317,7 @@ class BIFReader(object):
             delayed(self._get_values_from_block)(block)
             for block in self.probability_block()
         )
+
         variable_cpds = {}
         for var_name, arr in cpd_values:
             variable_cpds[var_name] = arr
@@ -548,7 +549,7 @@ $properties}\n"""
             variable = cpd.variable
             variable_states[variable] = []
             for state in range(cpd.get_cardinality([variable])[variable]):
-                variable_states[variable].append(str(variable) + "_" + str(state))
+                variable_states[variable].append(str(state))
         return variable_states
 
     def get_properties(self):
@@ -602,12 +603,9 @@ $properties}\n"""
          'light-on': ['family-out']}
         """
         cpds = self.model.get_cpds()
-        cpds.sort(key=lambda x: x.variable)
         variable_parents = {}
         for cpd in cpds:
-            variable_parents[cpd.variable] = []
-            for parent in sorted(cpd.variables[:0:-1]):
-                variable_parents[cpd.variable].append(parent)
+            variable_parents[cpd.variable] = cpd.variables[1:]
         return variable_parents
 
     def get_cpds(self):
diff --git a/pgmpy/readwrite/XMLBIF.py b/pgmpy/readwrite/XMLBIF.py
index 989645ce..78bd92fd 100644
--- a/pgmpy/readwrite/XMLBIF.py
+++ b/pgmpy/readwrite/XMLBIF.py
@@ -376,7 +376,7 @@ class XMLBIFWriter(object):
             .transformString(s)
         )
         if not s_fixed[0].isalpha():
-            s_fixed = "state" + s_fixed
+            s_fixed = s_fixed
         return s_fixed
 
     def get_properties(self):
@@ -430,8 +430,8 @@ class XMLBIFWriter(object):
         for cpd in cpds:
             definition_tag[cpd.variable] = etree.SubElement(self.network, "DEFINITION")
             etree.SubElement(definition_tag[cpd.variable], "FOR").text = cpd.variable
-            for child in sorted(cpd.variables[:0:-1]):
-                etree.SubElement(definition_tag[cpd.variable], "GIVEN").text = child
+            for parent in cpd.variables[1:]:
+                etree.SubElement(definition_tag[cpd.variable], "GIVEN").text = parent
 
         return definition_tag
 


Test patch:
diff --git a/pgmpy/tests/test_readwrite/test_BIF.py b/pgmpy/tests/test_readwrite/test_BIF.py
index 95d94104..726a171f 100644
--- a/pgmpy/tests/test_readwrite/test_BIF.py
+++ b/pgmpy/tests/test_readwrite/test_BIF.py
@@ -1,3 +1,4 @@
+import os
 import unittest
 
 import numpy as np
@@ -11,7 +12,6 @@ from pgmpy.factors.discrete import TabularCPD
 
 class TestBIFReader(unittest.TestCase):
     def setUp(self):
-
         self.reader = BIFReader(
             string="""
                 // Bayesian Network in the Interchange Format
@@ -219,22 +219,60 @@ class TestBIFReader(unittest.TestCase):
             "light-on": {"weight": None, "position": "(218, 195)"},
         }
         cpds_expected = [
-            np.array([[0.01], [0.99]]),
-            np.array([[0.99, 0.97, 0.9, 0.3], [0.01, 0.03, 0.1, 0.7]]),
-            np.array([[0.15], [0.85]]),
-            np.array([[0.7, 0.01], [0.3, 0.99]]),
-            np.array([[0.6, 0.05], [0.4, 0.95]]),
+            TabularCPD(
+                variable="bowel-problem",
+                variable_card=2,
+                values=np.array([[0.01], [0.99]]),
+                state_names={"bowel-problem": ["true", "false"]},
+            ),
+            TabularCPD(
+                variable="dog-out",
+                variable_card=2,
+                values=np.array([[0.99, 0.97, 0.9, 0.3], [0.01, 0.03, 0.1, 0.7]]),
+                evidence=["bowel-problem", "family-out"],
+                evidence_card=[2, 2],
+                state_names={
+                    "dog-out": ["true", "false"],
+                    "bowel-problem": ["true", "false"],
+                    "family-out": ["true", "false"],
+                },
+            ),
+            TabularCPD(
+                variable="family-out",
+                variable_card=2,
+                values=np.array([[0.15], [0.85]]),
+                state_names={"family-out": ["true", "false"]},
+            ),
+            TabularCPD(
+                variable="hear-bark",
+                variable_card=2,
+                values=np.array([[0.7, 0.01], [0.3, 0.99]]),
+                evidence=["dog-out"],
+                evidence_card=[2],
+                state_names={
+                    "hear-bark": ["true", "false"],
+                    "dog-out": ["true", "false"],
+                },
+            ),
+            TabularCPD(
+                variable="light-on",
+                variable_card=2,
+                values=np.array([[0.6, 0.05], [0.4, 0.95]]),
+                evidence=["family-out"],
+                evidence_card=[2],
+                state_names={
+                    "light-on": ["true", "false"],
+                    "family-out": ["true", "false"],
+                },
+            ),
         ]
         model = self.reader.get_model()
-        for cpd_index in range(0, len(cpds_expected)):
-            np_test.assert_array_equal(
-                model.get_cpds()[cpd_index].get_values(), cpds_expected[cpd_index]
-            )
+        model_cpds = model.get_cpds()
+        for cpd_index in range(5):
+            self.assertEqual(model_cpds[cpd_index], cpds_expected[cpd_index])
+
         self.assertDictEqual(dict(model.nodes), node_expected)
-        if nx.__version__.startswith("1"):
-            self.assertDictEqual(model.edge, edge_expected)
-        else:
-            self.assertDictEqual(dict(model.adj), edge_expected)
+        self.assertDictEqual(dict(model.adj), edge_expected)
 
         self.assertListEqual(sorted(model.nodes()), sorted(nodes_expected))
         self.assertListEqual(sorted(model.edges()), sorted(edges_expected))
@@ -288,7 +326,7 @@ class TestBIFWriter(unittest.TestCase):
         parents = {
             "kid": [],
             "bowel-problem": [],
-            "dog-out": ["family-out", "bowel-problem"],
+            "dog-out": ["bowel-problem", "family-out"],
             "family-out": [],
             "hear-bark": ["dog-out"],
             "light-on": ["family-out"],
@@ -333,32 +371,32 @@ class TestBIFWriter(unittest.TestCase):
         self.expected_string = """network unknown {
 }
 variable bowel-problem {
-    type discrete [ 2 ] { bowel-problem_0, bowel-problem_1 };
+    type discrete [ 2 ] { 0, 1 };
     property position = (335, 99) ;
     property weight = None ;
 }
 variable dog-out {
-    type discrete [ 2 ] { dog-out_0, dog-out_1 };
+    type discrete [ 2 ] { 0, 1 };
     property position = (300, 195) ;
     property weight = None ;
 }
 variable family-out {
-    type discrete [ 2 ] { family-out_0, family-out_1 };
+    type discrete [ 2 ] { 0, 1 };
     property position = (257, 99) ;
     property weight = None ;
 }
 variable hear-bark {
-    type discrete [ 2 ] { hear-bark_0, hear-bark_1 };
+    type discrete [ 2 ] { 0, 1 };
     property position = (296, 268) ;
     property weight = None ;
 }
 variable kid {
-    type discrete [ 2 ] { kid_0, kid_1 };
+    type discrete [ 2 ] { 0, 1 };
     property position = (100, 165) ;
     property weight = None ;
 }
 variable light-on {
-    type discrete [ 2 ] { light-on_0, light-on_1 };
+    type discrete [ 2 ] { 0, 1 };
     property position = (218, 195) ;
     property weight = None ;
 }
@@ -383,3 +421,13 @@ probability ( light-on | family-out ) {
 """
         self.maxDiff = None
         self.assertEqual(self.writer.__str__(), self.expected_string)
+
+    def test_write_read_equal(self):
+        self.writer.write_bif("test_bif.bif")
+        reader = BIFReader("test_bif.bif")
+        read_model = reader.get_model()
+        self.assertEqual(sorted(self.model.nodes()), sorted(read_model.nodes()))
+        self.assertEqual(sorted(self.model.edges()), sorted(read_model.edges()))
+        for var in self.model.nodes():
+            self.assertEqual(self.model.get_cpds(var), read_model.get_cpds(var))
+        os.remove("test_bif.bif")
diff --git a/pgmpy/tests/test_readwrite/test_XMLBIF.py b/pgmpy/tests/test_readwrite/test_XMLBIF.py
index 3631cac7..2e63b8e9 100644
--- a/pgmpy/tests/test_readwrite/test_XMLBIF.py
+++ b/pgmpy/tests/test_readwrite/test_XMLBIF.py
@@ -367,11 +367,11 @@ class TestXMLBIFWriterMethodsString(unittest.TestCase):
         self.assert_models_equivelent(self.model_stateless, model)
         self.assertDictEqual(
             {
-                "G": ["state0", "state1", "state2"],
-                "I": ["state0", "state1"],
-                "D": ["state0", "state1"],
-                "S": ["state0", "state1"],
-                "L": ["state0", "state1"],
+                "G": ["0", "1", "2"],
+                "I": ["0", "1"],
+                "D": ["0", "1"],
+                "S": ["0", "1"],
+                "L": ["0", "1"],
             },
             model.get_cpds("D").state_names,
         )
@@ -381,8 +381,8 @@ class TestXMLBIFWriterMethodsString(unittest.TestCase):
         self.assertSetEqual(set(expected.nodes()), set(got.nodes()))
         for node in expected.nodes():
             self.assertListEqual(
-                list(expected.get_parents(node)), list(got.get_parents(node))
+                sorted(expected.get_parents(node)), sorted(got.get_parents(node))
             )
             cpds_expected = expected.get_cpds(node=node)
             cpds_got = got.get_cpds(node=node)
-            np_test.assert_array_equal(cpds_expected.values, cpds_got.values)
+            self.assertEqual(cpds_expected, cpds_got)