Repository: sdv-dev/SDV

Issue:
GaussianCopula is not reporting the correct distribution name in the case of a fallback
### Environment Details
Please indicate the following details about the environment in which you found the bug:

- SDV version: 1.18.0
- Python version: 3.11.11
- Operating System: Linux (Google Colab)

### Error Description
The GaussianCopulaSynthesizer starts by estimating the shape of each individual column (marginal distribution). Sometimes, for reasons outside of our control, this estimation fails to converge for a particular set of data. In such cases, Copulas has a built-in fallback in place: It tries again using `'norm'` (standard normal distribution); this distribution's parameters can be directly computed via a closed-form formula, so it should not fail.

When this happens, I expected that the [get_learned_distributions function](https://docs.sdv.dev/sdv/single-table-data/modeling/synthesizers/gaussiancopulasynthesizer#get_learned_distributions) will correctly report that the distribution has fallen back to `'norm'`. But in practice, I see that **the distribution name is not correctly updating**. This can cause confusion about what the synthesizer has really learned.

### Steps to reproduce
First, fit a synthesizer with some data that is known to fail for the `'beta'` distribution.

```python
import pandas as pd
import numpy as np

from sdv.single_table import GaussianCopulaSynthesizer
from sdv.metadata import Metadata

data = pd.DataFrame(data={
    'A': np.concatenate([np.zeros(29), np.ones(21)])
})

metadata = Metadata.load_from_dict({
    'tables': {
        'table': {
            'columns': {
                'A': { 'sdtype': 'numerical'}}}}})

synthesizer = GaussianCopulaSynthesizer(metadata, default_distribution='beta')
synthesizer.fit(data)
```
Now, you can inspect the internals of the model to see that the `'norm'` distribution was used instead (GaussianUnivariate is the name for normal distribution):
```python
>>> synthesizer._model.to_dict()['univariates']
[{'loc': 0.42,
  'scale': 0.49355850717012256,
  'type': 'copulas.univariate.gaussian.GaussianUnivariate'}]
```

However, notice that the `get_learned_distributions()` method continues to report the distribution name as `'beta'` instead of `'norm'` (as expected). **This is a bug.** 
```python
>>> synthesizer.get_learned_distributions()
{'A': {'distribution': 'beta',
  'learned_parameters': {'loc': 0.42, 'scale': 0.49355850717012256}}}
```
(Note that the learned parameters themselves are correct. Just the name of the distribution is wrong. It should be `'norm'`.)

Discussion:

Solution patch:
diff --git a/sdv/multi_table/utils.py b/sdv/multi_table/utils.py
index 069d9d62..527b9301 100644
--- a/sdv/multi_table/utils.py
+++ b/sdv/multi_table/utils.py
@@ -443,7 +443,7 @@ def _drop_rows(data, metadata, drop_missing_values):
 
         if data[table].empty:
             raise InvalidDataError([
-                f"All references in table '{table}' are unknown and must be dropped."
+                f"All references in table '{table}' are unknown and must be dropped. "
                 'Try providing different data for this table.'
             ])
 
@@ -558,7 +558,7 @@ def _subsample_parent(
     parent_table = parent_table.drop(unreferenced_data_to_drop.index)
     if parent_table.empty:
         raise InvalidDataError([
-            f"All references in table '{parent_primary_key}' are unknown and must be dropped."
+            f"All references in table '{parent_primary_key}' are unknown and must be dropped. "
             'Try providing different data for this table.'
         ])
 
diff --git a/sdv/single_table/copulas.py b/sdv/single_table/copulas.py
index 44f60d00..8f4e5ebd 100644
--- a/sdv/single_table/copulas.py
+++ b/sdv/single_table/copulas.py
@@ -224,10 +224,11 @@ class GaussianCopulaSynthesizer(BaseSingleTableSynthesizer):
         univariates = deepcopy(parameters['univariates'])
         learned_distributions = {}
         valid_columns = self._get_valid_columns_from_metadata(columns)
+        distribution_names = {v.__name__: k for k, v in self._DISTRIBUTIONS.items()}
         for column, learned_params in zip(columns, univariates):
             if column in valid_columns:
-                distribution = self.numerical_distributions.get(column, self.default_distribution)
-                learned_params.pop('type')
+                distribution_name = learned_params.pop('type').split('.')[-1]
+                distribution = distribution_names[distribution_name]
                 learned_distributions[column] = {
                     'distribution': distribution,
                     'learned_parameters': learned_params,


Test patch:
diff --git a/tests/integration/single_table/test_copulas.py b/tests/integration/single_table/test_copulas.py
index b43b139b..39b5d821 100644
--- a/tests/integration/single_table/test_copulas.py
+++ b/tests/integration/single_table/test_copulas.py
@@ -522,3 +522,35 @@ def test_user_warning_for_unused_numerical_distribution():
     )
     with pytest.warns(UserWarning, match=message):
         synthesizer.fit(data)
+
+
+def test_get_learned_distributions_fallback_distribution():
+    """Test it when the fallback distribution is used GH#2394."""
+    # Setup
+    data = pd.DataFrame(data={'A': np.concatenate([np.zeros(29), np.ones(21)])})
+    metadata = Metadata.load_from_dict({
+        'tables': {
+            'table': {
+                'columns': {
+                    'A': {
+                        'sdtype': 'numerical',
+                    },
+                },
+            },
+        },
+    })
+
+    # Run
+    synthesizer = GaussianCopulaSynthesizer(metadata, default_distribution='beta')
+    synthesizer.fit(data)
+
+    # Assert
+    assert synthesizer.get_learned_distributions() == {
+        'A': {
+            'distribution': 'norm',
+            'learned_parameters': {
+                'loc': 0.42,
+                'scale': 0.4935585071701226,
+            },
+        },
+    }
diff --git a/tests/unit/multi_table/test_utils.py b/tests/unit/multi_table/test_utils.py
index a08ddfb3..6fb908a7 100644
--- a/tests/unit/multi_table/test_utils.py
+++ b/tests/unit/multi_table/test_utils.py
@@ -469,7 +469,7 @@ def test_drop_unknown_references_drop_all_rows(mock_get_rows_to_drop):
     # Run and Assert
     expected_message = re.escape(
         'The provided data does not match the metadata:\n'
-        "All references in table 'child' are unknown and must be dropped."
+        "All references in table 'child' are unknown and must be dropped. "
         'Try providing different data for this table.'
     )
     with pytest.raises(InvalidDataError, match=expected_message):
diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py
index 96e6085f..c510967a 100644
--- a/tests/unit/utils/test_utils.py
+++ b/tests/unit/utils/test_utils.py
@@ -351,7 +351,7 @@ def test_drop_unknown_references_drop_all_rows(mock_get_rows_to_drop):
     # Run and Assert
     expected_message = re.escape(
         'The provided data does not match the metadata:\n'
-        "All references in table 'child' are unknown and must be dropped."
+        "All references in table 'child' are unknown and must be dropped. "
         'Try providing different data for this table.'
     )
     with pytest.raises(InvalidDataError, match=expected_message):