Repository: sdv-dev/SDV

Issue:
Add violin option to get_column_plot function docstrings
### Problem Description

_As a user, I'd like to be able to visualize a pair of columns where one of the column is categorical and the other is numerical._

We added this functionality in SDMetrics, and it should work out of the box for SDV. This issue is just to add the option to the docstring.

### Expected behavior

Update the docstring here
https://github.com/sdv-dev/SDV/blob/82791e920f604c310a50dc69d9c0ea3502fdaf58/sdv/evaluation/multi_table.py#L107-L111
and here
https://github.com/sdv-dev/SDV/blob/82791e920f604c310a50dc69d9c0ea3502fdaf58/sdv/evaluation/single_table.py#L128-L132
to include the new `violin` option.



Discussion:

Patch:
diff --git a/pyproject.toml b/pyproject.toml
index 0e7914926..563fc6917 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,7 +39,7 @@ dependencies = [
     'ctgan>=0.11.0',
     'deepecho>=0.7.0',
     'rdt>=1.17.0',
-    'sdmetrics>=0.20.1',
+    'sdmetrics>=0.21.0.dev0',
     'platformdirs>=4.0',
     'pyyaml>=6.0.1',
 ]
diff --git a/sdv/evaluation/multi_table.py b/sdv/evaluation/multi_table.py
index b93a4daaf..3b18ae9b9 100644
--- a/sdv/evaluation/multi_table.py
+++ b/sdv/evaluation/multi_table.py
@@ -105,10 +105,11 @@ def get_column_pair_plot(
         column_names (list[string]):
             The names of the two columns to plot.
         plot_type (str or None):
-            The plot to be used. Can choose between ``box``, ``heatmap``, ``scatter`` or ``None``.
-            If ``None` select between ``box``, ``heatmap`` or ``scatter`` depending on the data
-            that the column contains, ``scatter`` used for datetime and numerical values,
-            ``heatmap`` for categorical and ``box`` for a mix of both. Defaults to ``None``.
+            The plot to be used. Can choose between ``box``, ``heatmap``, ``scatter``, ``violin``
+            or ``None``. If ``None` select between ``box``, ``heatmap`` or ``scatter`` depending
+            on the data that the column contains, ``scatter`` used for datetime and numerical
+            values, ``heatmap`` for categorical and ``box`` for a mix of both. Defaults to
+            ``None``.
         sample_size (int or None):
             The number of samples to plot. If ``None``, all samples are plotted.
             Defaults to ``None``.
@@ -121,7 +122,12 @@ def get_column_pair_plot(
     real_data = real_data[table_name] if real_data else None
     synthetic_data = synthetic_data[table_name] if synthetic_data else None
     return single_table_visualization.get_column_pair_plot(
-        real_data, synthetic_data, metadata, column_names, sample_size, plot_type
+        real_data=real_data,
+        synthetic_data=synthetic_data,
+        metadata=metadata,
+        column_names=column_names,
+        plot_type=plot_type,
+        sample_size=sample_size,
     )
 
 
diff --git a/sdv/evaluation/single_table.py b/sdv/evaluation/single_table.py
index 950a72592..7c8aa3f09 100644
--- a/sdv/evaluation/single_table.py
+++ b/sdv/evaluation/single_table.py
@@ -126,10 +126,11 @@ def get_column_pair_plot(
         column_names (list[string]):
             The names of the two columns to plot.
         plot_type (str or None):
-            The plot to be used. Can choose between ``box``, ``heatmap``, ``scatter`` or ``None``.
-            If ``None` select between ``box``, ``heatmap`` or ``scatter`` depending on the data
-            that the column contains, ``scatter`` used for datetime and numerical values,
-            ``heatmap`` for categorical and ``box`` for a mix of both. Defaults to ``None``.
+            The plot to be used. Can choose between ``box``, ``heatmap``, ``scatter``, ``violin``
+            or ``None``. If ``None` select between ``box``, ``heatmap`` or ``scatter`` depending
+            on the data that the column contains, ``scatter`` used for datetime and numerical
+            values, ``heatmap`` for categorical and ``box`` for a mix of both. Defaults to
+            ``None``.
         sample_size (int or None):
             The number of samples to use for the plot. If ``None`` use the whole dataset.
             Defaults to ``None``.


Test patch:
diff --git a/tests/integration/evaluation/test_multi_table.py b/tests/integration/evaluation/test_multi_table.py
index 3075a8450..69eb1316f 100644
--- a/tests/integration/evaluation/test_multi_table.py
+++ b/tests/integration/evaluation/test_multi_table.py
@@ -1,6 +1,8 @@
 import pandas as pd
+import pytest
 
-from sdv.evaluation.multi_table import evaluate_quality, run_diagnostic
+from sdv.datasets.demo import download_demo
+from sdv.evaluation.multi_table import evaluate_quality, get_column_pair_plot, run_diagnostic
 from sdv.metadata.metadata import Metadata
 
 
@@ -108,3 +110,25 @@ def test_evaluation_metadata():
             'Score': [1.0, 1.0, 1.0],
         }),
     )
+
+
+@pytest.mark.parametrize('plot_type', ['box', 'heatmap', 'scatter', 'violin'])
+def test_column_pair_plot_different_plot_types(plot_type):
+    """Test the method with all supported plot types."""
+    # Setup
+    real_data, metadata = download_demo(modality='multi_table', dataset_name='fake_hotels')
+
+    # Run
+    fig = get_column_pair_plot(
+        real_data=real_data,
+        synthetic_data=real_data.copy(),
+        table_name='guests',
+        column_names=['room_rate', 'amenities_fee'],
+        metadata=metadata,
+        sample_size=40,
+        plot_type=plot_type,
+    )
+
+    # Assert
+    assert len(fig.data[0].x) == 40
+    assert len(fig.data[1].x) == 40
diff --git a/tests/integration/evaluation/test_single_table.py b/tests/integration/evaluation/test_single_table.py
index d943b02d3..247826910 100644
--- a/tests/integration/evaluation/test_single_table.py
+++ b/tests/integration/evaluation/test_single_table.py
@@ -1,4 +1,5 @@
 import pandas as pd
+import pytest
 
 from sdv.datasets.demo import download_demo
 from sdv.evaluation.single_table import evaluate_quality, get_column_pair_plot, run_diagnostic
@@ -78,3 +79,28 @@ def test_column_pair_plot_sample_size_parameter():
     assert len(synthetic_data) == 500
     assert len(fig.data[0].x) == 40
     assert len(fig.data[1].x) == 40
+
+
+@pytest.mark.parametrize('plot_type', ['box', 'heatmap', 'scatter', 'violin'])
+def test_column_pair_plot_different_plot_types(plot_type):
+    """Test the method with all supported plot types."""
+    # Setup
+    real_data, metadata = download_demo(modality='single_table', dataset_name='fake_hotel_guests')
+    synthesizer = GaussianCopulaSynthesizer(metadata)
+    synthesizer.fit(real_data)
+    synthetic_data = synthesizer.sample(len(real_data))
+
+    # Run
+    fig = get_column_pair_plot(
+        real_data=real_data,
+        synthetic_data=synthetic_data,
+        column_names=['room_rate', 'amenities_fee'],
+        metadata=metadata,
+        sample_size=40,
+        plot_type=plot_type,
+    )
+
+    # Assert
+    assert len(synthetic_data) == 500
+    assert len(fig.data[0].x) == 40
+    assert len(fig.data[1].x) == 40
diff --git a/tests/unit/evaluation/test_multi_table.py b/tests/unit/evaluation/test_multi_table.py
index 2ecdf52d1..291c5c9d0 100644
--- a/tests/unit/evaluation/test_multi_table.py
+++ b/tests/unit/evaluation/test_multi_table.py
@@ -1,4 +1,4 @@
-from unittest.mock import Mock, patch
+from unittest.mock import Mock, call, patch
 
 import pandas as pd
 
@@ -108,11 +108,18 @@ def test_get_column_pair_plot(mock_plot):
     mock_plot.return_value = 'plot'
 
     # Run
-    plot = get_column_pair_plot(data1, data2, metadata, 'table', ['col1', 'col2'], 2)
+    plot = get_column_pair_plot(data1, data2, metadata, 'table', ['col1', 'col2'], sample_size=2)
 
     # Assert
     call_metadata = metadata.tables['table']
-    mock_plot.assert_called_once_with(table1, table2, call_metadata, ['col1', 'col2'], None, 2)
+    mock_plot.assert_called_once_with(
+        real_data=table1,
+        synthetic_data=table2,
+        metadata=call_metadata,
+        column_names=['col1', 'col2'],
+        plot_type=None,
+        sample_size=2,
+    )
     assert plot == 'plot'
 
 
@@ -127,15 +134,28 @@ def test_get_column_pair_plot_only_real_or_synthetic(mock_plot):
     mock_plot.return_value = 'plot'
 
     # Run
-    get_column_pair_plot(data1, None, metadata, 'table', ['col1', 'col2'], 2)
-    get_column_pair_plot(None, data1, metadata, 'table', ['col1', 'col2'], 2)
+    get_column_pair_plot(data1, None, metadata, 'table', ['col1', 'col2'], sample_size=2)
+    get_column_pair_plot(None, data1, metadata, 'table', ['col1', 'col2'], sample_size=2)
 
     # Assert
     call_metadata = metadata.tables['table']
-    mock_plot.assert_has_calls([
-        ((table1, None, call_metadata, ['col1', 'col2'], None, 2), {}),
-        ((None, table1, call_metadata, ['col1', 'col2'], None, 2), {}),
-    ])
+    call1 = call(
+        real_data=table1,
+        synthetic_data=None,
+        metadata=call_metadata,
+        column_names=['col1', 'col2'],
+        plot_type=None,
+        sample_size=2,
+    )
+    call2 = call(
+        real_data=None,
+        synthetic_data=table1,
+        metadata=call_metadata,
+        column_names=['col1', 'col2'],
+        plot_type=None,
+        sample_size=2,
+    )
+    mock_plot.assert_has_calls([call1, call2])
 
 
 @patch('sdmetrics.visualization.get_cardinality_plot')