Repository: wearepal/EthicML

Issue:
Change how synthetic 1 data is generated
from 
```
             +----------------+
             |                |
             |                v
    +-----+  |   +-----+    +---+
    | X_1 |--+-->| X_2 | -->| Y |
    +-----+      +-----+    +---+

                            +---+
                            | S |
                            +---+
```

to this

```
+-----+     +-----+
|     |     |     |
| Y_1 +<----+ X_1 +--+
|     |     |     |  |   +-----+   +-----+
+-----+     +-----+  |   |     |   |     |
                     +-->+ Y_3 |   |  S  |
+-----+     +-----+  |   |     |   |     |
|     |     |     |  |   +-----+   +-----+
| Y_2 +<----+ X_2 +--+
|     |     |     |
+-----+     +-----+
```

Similar, but now we have 3 potential labels, so we can demonstrate transfer learning


Discussion:

Resolving patch:
diff --git a/ethicml/data/csvs/make_synthetic_1.py b/ethicml/data/csvs/make_synthetic_1.py
--- a/ethicml/data/csvs/make_synthetic_1.py
+++ b/ethicml/data/csvs/make_synthetic_1.py
@@ -18,25 +18,26 @@ def main() -> None:
     Scenario 1
     ----------
 
-             +----------------+
-             |                |
-             |                v
-    +-----+  |   +-----+    +---+
-    | X_1 |--+-->| X_2 | -->| Y |
-    +-----+      +-----+    +---+
-
-                            +---+
-                            | S |
-                            +---+
+    +-----+   +-----+
+    | Y_1 +<--+ X_1 +-+
+    +-----+   +-----+ |   +-----+   +---+
+                      +-->+ Y_3 |   | S |
+    +-----+   +-----+ |   +-----+   +---+
+    | Y_2 +<--+ X_2 +-+
+    +-----+   +-----+
 
     In this scenario, there are two input variables, X_1 and X_2.
-    There is one outcome variable, Y.
-    There is one sensitive attribute, S, which is independent of X_1, X_2 & Y.
+    There are three outcome variables, Y_1, Y_2 & Y_3.
+    There is one sensitive attribute, S, which is independent of all X & Y.
 
     We have:
-    X_1 ~ N(0, 1)
-    X_2 ~ N(-1.5, 4) + X_1
-    Y ~ B(sigmoid((X_1 + X_2)/2))
+    X_1 ~ N(3, 2)
+    X_2 ~ N(-1.5, 4)
+
+    Y_1 ~ B(sigmoid(X_1)))
+    Y_2 ~ B(sigmoid(X_2))
+    Y_3 ~ B(sigmoid((X_1 + X_2)/2))
+
     S ~ B(0.5)
 
     """
@@ -45,17 +46,16 @@ def main() -> None:
 
     np.random.seed(seed)
 
-    x_1 = np.random.normal(0, 1, samples)
-    x_2 = np.random.normal(-1.5, 4, samples) + x_1
+    x_1 = np.random.normal(3, 2, samples)
+    x_2 = np.random.normal(-1.5, 4, samples)
 
-    p = sigmoid((x_1 + x_2) / 2)
-    y = np.random.binomial(1, p)
+    y_1 = np.random.binomial(1, sigmoid(x_1))
+    y_2 = np.random.binomial(1, sigmoid(x_2))
+    y_3 = np.random.binomial(1, sigmoid(x_1 + x_2))
 
     s = np.random.binomial(1, 0.5, samples)
 
-    print(s.mean(), y.mean())
-
-    df = pd.DataFrame(data={"x1": x_1, "x2": x_2, "s": s, "y": y,})
+    df = pd.DataFrame(data={"x1": x_1, "x2": x_2, "s": s, "y1": y_1, "y2": y_2, "y3": y_3,})
 
     # Shuffle the data,
     df = df.sample(frac=1.0, random_state=seed).reset_index(drop=True)
diff --git a/ethicml/data/tabular_data/synthetic.py b/ethicml/data/tabular_data/synthetic.py
--- a/ethicml/data/tabular_data/synthetic.py
+++ b/ethicml/data/tabular_data/synthetic.py
@@ -14,18 +14,21 @@ def Synthetic() -> Dataset:  # pylint: disable=invalid-name
     return synthetic()
 
 
-def synthetic(scenario: Literal[1] = 1) -> Dataset:
+def synthetic(scenario: Literal[1] = 1, target: Literal[1, 2, 3] = 1) -> Dataset:
     r"""Dataset with synthetic data.
 
     Scenario 1 = X⊥S & Y⊥S.
     """
-    return Dataset(
-        name="Synthetic - Scenario 1",
-        num_samples=1000,
-        filename_or_path="synthetic_scenario_1.csv",
-        features=["x1", "x2"],
-        cont_features=["x1", "x2"],
-        sens_attr_spec="s",
-        class_label_spec="y",
-        discrete_only=False,
-    )
+    if scenario == 1:
+        return Dataset(
+            name=f"Synthetic - Scenario {scenario}, target {target}",
+            num_samples=1000,
+            filename_or_path="synthetic_scenario_1.csv",
+            features=["x1", "x2"],
+            cont_features=["x1", "x2"],
+            sens_attr_spec="s",
+            class_label_spec=f"y{target}",
+            discrete_only=False,
+        )
+    else:
+        raise NotImplementedError("We'll have more scenarios available soon!")


Test patch:
diff --git a/tests/loading_data_test.py b/tests/loading_data_test.py
--- a/tests/loading_data_test.py
+++ b/tests/loading_data_test.py
@@ -76,7 +76,9 @@ def test_can_load_test_data(data_root: Path):
         (sqf(split="Sex"), 12347, 145, 139, 1, 2, 1, 2, "SQF Sex"),
         (sqf(split="Race"), 12347, 145, 139, 1, 2, 1, 2, "SQF Race"),
         (sqf(split="Race-Sex"), 12347, 144, 138, 1, 4, 1, 2, "SQF Race-Sex"),
-        (synthetic(), 1000, 2, 0, 1, 2, 1, 2, "Synthetic - Scenario 1"),
+        (synthetic(), 1000, 2, 0, 1, 2, 1, 2, "Synthetic - Scenario 1, target 1"),
+        (synthetic(target=2), 1000, 2, 0, 1, 2, 1, 2, "Synthetic - Scenario 1, target 2"),
+        (synthetic(target=3), 1000, 2, 0, 1, 2, 1, 2, "Synthetic - Scenario 1, target 3"),
         (toy(), 400, 10, 8, 1, 2, 1, 2, "Toy"),
     ],
 )