Repository: biolab/orange3

Issue:
2 x Transpose + Preprocess loses information
**Describe the bug**
Second transpose cannot retrieve the domain after Preprocess.

**To Reproduce**
Steps to reproduce the behavior:
1. File (brown-selected).
2. Transpose.
3. Preprocesss (say Normalize).
4. Transpose.

**Orange version:**
3.24.dev

**Expected behavior**
Second Transpose puts columns names into a string variable.

**Screenshots**
<img width="1232" alt="Screen Shot 2019-11-14 at 09 33 02" src="https://user-images.githubusercontent.com/12524972/68839832-c910d600-06c1-11ea-9286-5bf033a9802f.png">




Discussion:
The problem is in preprocess, more accurately in Normalize. Attributes' attributes are not copied when creating new attributes. I'm not sure this is a bug.

<img width="527" alt="Screenshot 2019-11-15 at 15 34 20" src="https://user-images.githubusercontent.com/2387315/68950857-4705d780-07bd-11ea-91a2-7aaaa309c9e2.png">

But wouldn't you expect this works? I mean former column _names_ (from Input Data) do not appear in any column, which is _wrong_ in my opinion. 
Preprocessing should keep meta attributes and attributes of attributes.

Resolving patch:
diff --git a/Orange/preprocess/normalize.py b/Orange/preprocess/normalize.py
--- a/Orange/preprocess/normalize.py
+++ b/Orange/preprocess/normalize.py
@@ -1,6 +1,6 @@
 import numpy as np
 
-from Orange.data import ContinuousVariable, Domain
+from Orange.data import Domain
 from Orange.statistics import distribution
 from Orange.util import Reprable
 from .preprocess import Normalize
@@ -51,12 +51,7 @@ def normalize_by_sd(self, dist, var):
             compute_val = Norm(var, avg, 1 / sd)
         else:
             compute_val = Norm(var, 0, 1 / sd)
-
-        return ContinuousVariable(
-            var.name,
-            compute_value=compute_val,
-            sparse=var.sparse,
-        )
+        return var.copy(compute_value=compute_val)
 
     def normalize_by_span(self, dist, var):
         dma, dmi = (dist.max(), dist.min()) if dist.shape[1] else (np.nan, np.nan)
@@ -64,12 +59,7 @@ def normalize_by_span(self, dist, var):
         if diff < 1e-15:
             diff = 1
         if self.zero_based:
-            return ContinuousVariable(
-                var.name,
-                compute_value=Norm(var, dmi, 1 / diff),
-                sparse=var.sparse)
+            compute_val = Norm(var, dmi, 1 / diff)
         else:
-            return ContinuousVariable(
-                var.name,
-                compute_value=Norm(var, (dma + dmi) / 2, 2 / diff),
-                sparse=var.sparse)
+            compute_val = Norm(var, (dma + dmi) / 2, 2 / diff)
+        return var.copy(compute_value=compute_val)


Test patch:
diff --git a/Orange/tests/test_normalize.py b/Orange/tests/test_normalize.py
--- a/Orange/tests/test_normalize.py
+++ b/Orange/tests/test_normalize.py
@@ -142,3 +142,18 @@ def test_datetime_normalization(self):
                     [0., '2003-07-23', 'a', 'b', -1., '?', 0., 'b', '?', 'b', 0],
                     [0., '1967-03-12', 'a', 'b', 1., 'b', -1.225, 'c', '?', 'c', 1]]
         self.compare_tables(data_norm, solution)
+
+    def test_retain_vars_attributes(self):
+        data = Table("iris")
+        attributes = {"foo": "foo", "baz": 1}
+        data.domain.attributes[0].attributes = attributes
+        self.assertDictEqual(
+            Normalize(norm_type=Normalize.NormalizeBySD)(
+                data).domain.attributes[0].attributes, attributes)
+        self.assertDictEqual(
+            Normalize(norm_type=Normalize.NormalizeBySpan)(
+                data).domain.attributes[0].attributes, attributes)
+
+
+if __name__ == "__main__":
+    unittest.main()