Refactor FunctionalCPD to use dedicated Tabular and LinearGaussian adapters#62
Refactor FunctionalCPD to use dedicated Tabular and LinearGaussian adapters#62
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the FunctionalCPD class by delegating its tabular and linear fitting logic to new TabularAdapter and LinearGaussianAdapter classes, which improves modularity and simplifies the main class. The review feedback identifies a potential division-by-zero issue in TabularAdapter when processing empty or NaN data and suggests adding a missing numpy import to support the fix. Additionally, a formatting improvement is recommended for the LinearGaussianAdapter string representation to handle cases without evidence variables more cleanly.
| variable_states = sorted(data[self.variable].dropna().unique()) | ||
| if not self.parents: | ||
| counts = data[self.variable].value_counts().reindex(variable_states, fill_value=0) | ||
| probs = counts.values / counts.values.sum() |
There was a problem hiding this comment.
If counts.values.sum() is zero (e.g., when the input data for the variable is empty or all NaN), this division will result in NaN values and a RuntimeWarning. The case with parents handles division by zero by using .replace(0, 1). A similar safeguard should be added here. Using np.nan_to_num will safely convert NaN to 0.0.
| probs = counts.values / counts.values.sum() | |
| probs = np.nan_to_num(counts.values / counts.values.sum()) |
| return ( | ||
| f"<LinearGaussianAdapter representing P({cpd.variable} | {', '.join(cpd.evidence)}) " | ||
| f"~ N({beta_str}, std={cpd.std:.3f}) at {hex(id(self))}>" | ||
| ) |
There was a problem hiding this comment.
The current __repr__ format for a fitted model without evidence variables results in P(variable | ), which is slightly awkward. It would be cleaner to omit the | when there are no evidence variables, similar to how it's handled in TabularAdapter.
| return ( | |
| f"<LinearGaussianAdapter representing P({cpd.variable} | {', '.join(cpd.evidence)}) " | |
| f"~ N({beta_str}, std={cpd.std:.3f}) at {hex(id(self))}>" | |
| ) | |
| evidence_str = f" | {', '.join(cpd.evidence)}" if cpd.evidence else "" | |
| return ( | |
| f"<LinearGaussianAdapter representing P({cpd.variable}{evidence_str}) " | |
| f"~ N({beta_str}, std={cpd.std:.3f}) at {hex(id(self))}>" | |
| ) |
| @@ -0,0 +1,60 @@ | |||
| from pgmpy.estimators import MaximumLikelihoodEstimator as MLE | |||
There was a problem hiding this comment.
The file uses numpy features (e.g., counts.values.sum()), and a suggested fix below requires an explicit np reference. It's best practice to add import numpy as np at the top of the file for clarity and to support the fix.
| from pgmpy.estimators import MaximumLikelihoodEstimator as MLE | |
| import numpy as np | |
| from pgmpy.estimators import MaximumLikelihoodEstimator as MLE |
Motivation
FunctionalCPD_Refactorby extracting tabular and linear fitting/representation logic into small adapter classes following the existingSkproAdapterpattern.Description
TabularAdapter(pgmpy/factors/hybrid/TabularAdapter.py) to encapsulate tabular counting, normalization andTabularCPDconstruction and representation.LinearGaussianAdapter(pgmpy/factors/hybrid/LinearGaussianAdapter.py) to encapsulate OLS/MLE coefficient estimation, variance/std computation andLinearGaussianCPDconstruction and representation.FunctionalCPD_Refactorto delegate_fit_tabularand_fit_linearto the new adapters and to simplify fitted__repr__to include the adapter's representation (keepsSkproAdapterusage unchanged for external models).pgmpy/tests/test_models/test_FunctionalBayesianNetwork_Refactor.py) to assert that fitted objects are adapter instances and to validate numeric results viaadapter.fitted_cpd_to preserve existing coverage.Testing
pytest -v pgmpy/tests/test_models/test_FunctionalBayesianNetwork_Refactor.py, which completed with 2 passed and 1 skipped tests.pre-commit run --all-filesbut thepre-commitcommand was not available in the execution environment, so hooks were not run here.Codex Task