Repository: biolab/orange3

Issue:
Merge Data: can't select what to match by
##### Orange version
3.8.dev

##### Expected behavior
If two datasets have columns that can be matched, one can set to match them by these two columns.


##### Actual behavior
When one sets the first variable to match by, often the second option remains only to merge by Position.
For example, I can't match by Datum, even though the column is present in the second data and does have corresponding column to match by in the original data.


##### Steps to reproduce the behavior
File (iris) + File (1) (iris) - Merge Data. Try to merge by sepal length and sepal length. Not possible in current version.


##### Additional info (worksheets, data, screenshots, ...)
I am working with datetime features, but I think this applies to other uses as well. If the second data set can be matched with only certain rows (not all datetime values are present in the second data set), then Orange should match the corresponding datatime and leave the unmatching rows empty (i.e. fill with nans).




Discussion:
This behaviour is documented in the help file, although it could be made clearer. The paragraph I am referring to is:

> Merging is done by values of selected (merging) attributes. First, the value of the merging attribute from Data is taken and instances from Extra Data are searched for matching values. If more than a single instance from Extra Data was to be found, the attribute is removed from available merging attributes.

The second option only shows variables where every value is unique. If multiple rows in the second dataset could match one row in the first dataset, it is not clear which of them should be merged with so the feature is not listed in the drop-down.

The least we could do is show a warning why some variables are hidden. Or show them and display an error if such case would actually occur.

Resolving patch:
diff --git a/Orange/widgets/data/owmergedata.py b/Orange/widgets/data/owmergedata.py
--- a/Orange/widgets/data/owmergedata.py
+++ b/Orange/widgets/data/owmergedata.py
@@ -52,6 +52,12 @@ class Outputs:
 
     class Warning(widget.OWWidget.Warning):
         duplicate_names = widget.Msg("Duplicate variable names in output.")
+        non_unique_variables = widget.Msg(
+            "Columns with non-unique values can't be used for matching.\n"
+            "The second variable must uniquely define the row in the second "
+            "table. Variables with non-unique values ({}) "
+            "are thus unsuitable as keys for merging."
+        )
 
     def __init__(self):
         super().__init__()
@@ -128,12 +134,13 @@ def change_merging(self):
         self.set_merging()
         self._invalidate()
 
-    @staticmethod
-    def _set_unique_model(data, model):
+    def _set_unique_model(self, data, model):
+        self.Warning.non_unique_variables.clear()
         if data is None:
             model[:] = []
             return
         m = [INDEX]
+        non_unique = []
         for attr in chain(data.domain.variables, data.domain.metas):
             col = data.get_column_view(attr)[0]
             if attr.is_primitive():
@@ -143,6 +150,10 @@ def _set_unique_model(data, model):
                 col = col[~(col == "")]
             if len(np.unique(col)) == len(col):
                 m.append(attr)
+            else:
+                non_unique.append(attr.name)
+        if non_unique:
+            self.Warning.non_unique_variables(", ".join(non_unique))
         model[:] = m
 
     @staticmethod


Test patch:
diff --git a/Orange/widgets/data/tests/test_owmergedata.py b/Orange/widgets/data/tests/test_owmergedata.py
--- a/Orange/widgets/data/tests/test_owmergedata.py
+++ b/Orange/widgets/data/tests/test_owmergedata.py
@@ -473,3 +473,13 @@ def test_enable_auto_apply(self):
         self.send_signal(self.widget.Inputs.data, self.dataA)
         self.send_signal(self.widget.Inputs.extra_data, self.dataB)
         self.assertIsNotNone(self.get_output(self.widget.Outputs.data))
+
+    def test_non_unique_warning(self):
+        self.widget.auto_apply = True
+        self.send_signal(self.widget.Inputs.data, self.dataA)
+        self.send_signal(self.widget.Inputs.extra_data, self.dataB)
+        self.assertFalse(self.widget.Warning.non_unique_variables.is_shown())
+        self.send_signal(self.widget.Inputs.data, Table("iris"))
+        self.assertTrue(self.widget.Warning.non_unique_variables.is_shown())
+        self.send_signal(self.widget.Inputs.data, None)
+        self.assertFalse(self.widget.Warning.non_unique_variables.is_shown())