Repository: pgmpy/pgmpy

Issue:
About BN's Incremental update’
### Subject of the issue
 If we had an established Bayesian Network, Now we have some datas about it, Can we use this datas update the established Bayesian Network, instead of re-learning all the datas(the original datas and the datas we just mentiond) to Caculate the Bayesian Network's parameters?

### Your environment
* pgmpy version
* Python version
* Operating System

### Steps to reproduce
Tell us how to reproduce this issue. Please provide a **minimal reproducible code** of
the issue you are facing if possible.

### Expected behaviour
Tell us what should happen

### Actual behaviour
Tell us what happens instead


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1434?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) Report
> Merging [#1434](https://codecov.io/gh/pgmpy/pgmpy/pull/1434?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) (49306a5) into [dev](https://codecov.io/gh/pgmpy/pgmpy/commit/564cd7ce1f4c42918b36365ecf6cdf60dd7ec7a0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) (564cd7c) will **decrease** coverage by `0.05%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/graphs/tree.svg?width=650&height=150&src=pr&token=UaJMCdHaEF&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy)](https://codecov.io/gh/pgmpy/pgmpy/pull/1434?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy)

```diff
@@            Coverage Diff             @@
##              dev    #1434      +/-   ##
==========================================
- Coverage   94.03%   93.97%   -0.06%     
==========================================
  Files         134      134              
  Lines       14491    14463      -28     
==========================================
- Hits        13626    13592      -34     
- Misses        865      871       +6     
```


| [Impacted Files](https://codecov.io/gh/pgmpy/pgmpy/pull/1434?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) | Coverage Δ | |
|---|---|---|
| [pgmpy/models/BayesianModel.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvbW9kZWxzL0JheWVzaWFuTW9kZWwucHk=) | `94.83% <100.00%> (+0.28%)` | :arrow_up: |
| [pgmpy/tests/test\_models/test\_BayesianModel.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvdGVzdHMvdGVzdF9tb2RlbHMvdGVzdF9CYXllc2lhbk1vZGVsLnB5) | `100.00% <100.00%> (ø)` | |
| [.../test\_models/test\_LinearGaussianBayesianNetwork.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvdGVzdHMvdGVzdF9tb2RlbHMvdGVzdF9MaW5lYXJHYXVzc2lhbkJheWVzaWFuTmV0d29yay5weQ==) | `39.06% <0.00%> (-3.59%)` | :arrow_down: |
| [...actors/test\_continuous/test\_Linear\_Gaussain\_CPD.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvdGVzdHMvdGVzdF9mYWN0b3JzL3Rlc3RfY29udGludW91cy90ZXN0X0xpbmVhcl9HYXVzc2Fpbl9DUEQucHk=) | `40.00% <0.00%> (-2.86%)` | :arrow_down: |
| [pgmpy/base/DAG.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvYmFzZS9EQUcucHk=) | `92.41% <0.00%> (-2.07%)` | :arrow_down: |
| [pgmpy/factors/continuous/discretize.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvZmFjdG9ycy9jb250aW51b3VzL2Rpc2NyZXRpemUucHk=) | `34.21% <0.00%> (-1.69%)` | :arrow_down: |
| [pgmpy/tests/test\_estimators/test\_SEMEstimator.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvdGVzdHMvdGVzdF9lc3RpbWF0b3JzL3Rlc3RfU0VNRXN0aW1hdG9yLnB5) | `79.66% <0.00%> (-1.59%)` | :arrow_down: |
| [pgmpy/factors/base.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvZmFjdG9ycy9iYXNlLnB5) | `84.21% <0.00%> (-0.79%)` | :arrow_down: |
| [...gmpy/factors/distributions/GaussianDistribution.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvZmFjdG9ycy9kaXN0cmlidXRpb25zL0dhdXNzaWFuRGlzdHJpYnV0aW9uLnB5) | `69.00% <0.00%> (-0.61%)` | :arrow_down: |
| [pgmpy/independencies/Independencies.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvaW5kZXBlbmRlbmNpZXMvSW5kZXBlbmRlbmNpZXMucHk=) | `87.39% <0.00%> (-0.41%)` | :arrow_down: |
| ... and [14 more](https://codecov.io/gh/pgmpy/pgmpy/pull/1434/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1434?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1434?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy). Last update [564cd7c...49306a5](https://codecov.io/gh/pgmpy/pgmpy/pull/1434?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy).


Patch:
diff --git a/pgmpy/models/BayesianModel.py b/pgmpy/models/BayesianModel.py
index b89c2f31..4b3232c7 100644
--- a/pgmpy/models/BayesianModel.py
+++ b/pgmpy/models/BayesianModel.py
@@ -506,6 +506,10 @@ class BayesianModel(DAG):
             for large networks (>100 nodes). For smaller networks might reduce
             performance.
 
+        Returns
+        -------
+        None: Modifies the network inplace and adds the `cpds` property.
+
         Examples
         --------
         >>> import pandas as pd
@@ -536,6 +540,63 @@ class BayesianModel(DAG):
         cpds_list = _estimator.get_parameters(n_jobs=n_jobs, **kwargs)
         self.add_cpds(*cpds_list)
 
+    def fit_update(self, data, n_prev_samples=None, n_jobs=-1):
+        """
+        Method to update the parameters of the BayesianModel with more data.
+        Internally, uses BayesianEstimator with dirichlet prior, and uses
+        the current CPDs (along with `n_prev_samples`) to compute the pseudo_counts.
+
+        Parameters
+        ----------
+        data: pandas.DataFrame
+            The new dataset which to use for updating the model.
+
+        n_prev_samples: int
+            The number of samples/datapoints on which the model was trained before.
+            This parameter determines how much weight should the new data be given.
+            If None, n_prev_samples = nrow(data).
+
+        n_jobs: int (default: -1)
+            Number of threads/processes to use for estimation. It improves speed only
+            for large networks (>100 nodes). For smaller networks might reduce
+            performance.
+
+        Returns
+        -------
+        None: Modifies the network inplace
+
+        Examples
+        --------
+        >>> from pgmpy.utils import get_example_model
+        >>> from pgmpy.sampling import BayesianModelSampling
+        >>> model = get_example_model('alarm')
+        >>> # Generate some new data.
+        >>> data = BayesianModelSampling(model).forward_sample(int(1e3))
+        >>> model.fit_update(data)
+        """
+        from pgmpy.estimators import BayesianEstimator
+
+        if n_prev_samples is None:
+            n_prev_samples = data.shape[0]
+
+        # Step 1: Compute the pseudo_counts for the dirichlet prior.
+        pseudo_counts = {
+            var: self.get_cpds(var).get_values() * n_prev_samples
+            for var in data.columns
+        }
+
+        # Step 2: Get the current order of state names for aligning pseudo counts.
+        state_names = {}
+        for var in data.columns:
+            state_names.update(self.get_cpds(var).state_names)
+
+        # Step 3: Estimate the new CPDs.
+        _est = BayesianEstimator(self, data, state_names=state_names)
+        cpds = _est.get_parameters(
+            prior_type="dirichlet", pseudo_counts=pseudo_counts, n_jobs=n_jobs
+        )
+        self.add_cpds(*cpds)
+
     def predict(self, data, stochastic=False, n_jobs=-1):
         """
         Predicts states of all the missing variables.


Test patch:
diff --git a/pgmpy/tests/test_models/test_BayesianModel.py b/pgmpy/tests/test_models/test_BayesianModel.py
index 6f98d259..300f6dfc 100644
--- a/pgmpy/tests/test_models/test_BayesianModel.py
+++ b/pgmpy/tests/test_models/test_BayesianModel.py
@@ -20,6 +20,8 @@ from pgmpy.estimators import (
     MaximumLikelihoodEstimator,
 )
 from pgmpy.base import DAG
+from pgmpy.utils import get_example_model
+from pgmpy.sampling import BayesianModelSampling
 
 
 class TestBaseModelCreation(unittest.TestCase):
@@ -688,6 +690,23 @@ class TestBayesianModelFitPredict(unittest.TestCase):
             self.model2.get_cpds("B"), TabularCPD("B", 2, [[11.0 / 15], [4.0 / 15]])
         )
 
+    def test_fit_update(self):
+        model = get_example_model("asia")
+        model_copy = model.copy()
+        data = BayesianModelSampling(model).forward_sample(int(1e3))
+        model.fit_update(data, n_prev_samples=int(1e3))
+        for var in model.nodes():
+            self.assertTrue(
+                model_copy.get_cpds(var).__eq__(model.get_cpds(var), atol=0.1)
+            )
+
+        model = model_copy.copy()
+        model.fit_update(data)
+        for var in model.nodes():
+            self.assertTrue(
+                model_copy.get_cpds(var).__eq__(model.get_cpds(var), atol=0.1)
+            )
+
     def test_fit_missing_data(self):
         self.model2.fit(
             self.data2, state_names={"C": [0, 1]}, complete_samples_only=False