From a49cb0843d37e04f116d004e74a320612db204cb Mon Sep 17 00:00:00 2001 From: daslu Date: Wed, 18 Sep 2024 12:03:48 +0300 Subject: [PATCH] rerendered docs --- docs/index.html | 41 +++- docs/noj_book.automl.html | 206 +++++++++--------- docs/noj_book.interactions_ols.html | 54 ++--- ...book.visualizing_correlation_matrices.html | 2 +- docs/search.json | 10 +- 5 files changed, 175 insertions(+), 138 deletions(-) diff --git a/docs/index.html b/docs/index.html index 147b5a4..5ee5739 100644 --- a/docs/index.html +++ b/docs/index.html @@ -20,6 +20,40 @@ margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ vertical-align: middle; } +/* CSS for syntax highlighting */ +pre > code.sourceCode { white-space: pre; position: relative; } +pre > code.sourceCode > span { line-height: 1.25; } +pre > code.sourceCode > span:empty { height: 1.2em; } +.sourceCode { overflow: visible; } +code.sourceCode > span { color: inherit; text-decoration: inherit; } +div.sourceCode { margin: 1em 0; } +pre.sourceCode { margin: 0; } +@media screen { +div.sourceCode { overflow: auto; } +} +@media print { +pre > code.sourceCode { white-space: pre-wrap; } +pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; } +} +pre.numberSource code + { counter-reset: source-line 0; } +pre.numberSource code > span + { position: relative; left: -4em; counter-increment: source-line; } +pre.numberSource code > span > a:first-child::before + { content: counter(source-line); + position: relative; left: -1em; text-align: right; vertical-align: baseline; + border: none; display: inline-block; + -webkit-touch-callout: none; -webkit-user-select: none; + -khtml-user-select: none; -moz-user-select: none; + -ms-user-select: none; user-select: none; + padding: 0 4px; width: 4em; + } +pre.numberSource { margin-left: 3em; padding-left: 4px; } +div.sourceCode + { } +@media screen { +pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; } +} @@ -249,8 +283,11 @@

1 Preface

Scinojure (“Noj”) is an entry point to the Clojure stack for data & science.

It collects a few of the main libraries and documents common ways to use them together.

Source: (GitHub repo)

-

Artifact: 2-alpha5-SNAPSHOT

-

Note we are using SNAPSHOT version for now, since a few of the current dependencies are at a snapshot stage for an upcoming release.

+

Deps:

+
org.scicloj/noj {:git/url "https://github.com/scicloj/noj.git"
+                 :git/tag "2-alpha6"
+                 :git/sha "c7a7240"}
+

Note we are using git coordinates at the moment, in order to expose a few relevant features of the current underlying libraries, which are unreleased yet.

Status: Most of the underlying libraries are stable. The experimental parts are marked as such. For some of the libraries, we use a branch for an upcoming release. The main current goal is to provide a clear picture of the direction the stack is going towards, expecting most of it to stabilize around October 2024.

1.1 Existing chapters in this book:

diff --git a/docs/noj_book.automl.html b/docs/noj_book.automl.html index b7ad1ea..bd933ec 100644 --- a/docs/noj_book.automl.html +++ b/docs/noj_book.automl.html @@ -365,38 +365,38 @@

0.0 -2.0 +3.0 0.0 0.0 -0.0 +1.0 3.0 1.0 -0.0 +1.0 -0.0 +1.0 3.0 0.0 -1.0 +0.0 1.0 -3.0 2.0 0.0 +1.0 0.0 -2.0 +3.0 0.0 0.0 0.0 -1.0 -2.0 +3.0 +0.0 0.0 @@ -406,20 +406,20 @@

0.0 -1.0 -1.0 0.0 1.0 +0.0 +0.0 1.0 1.0 -2.0 +0.0 1.0 0.0 -2.0 +1.0 0.0 0.0 @@ -431,9 +431,9 @@

0.0 +1.0 2.0 -0.0 -0.0 +1.0 0.0 @@ -442,26 +442,26 @@

0.0 -0.0 1.0 +3.0 0.0 1.0 -0.0 -3.0 +1.0 +1.0 0.0 1.0 0.0 -3.0 +2.0 0.0 0.0 0.0 -3.0 +2.0 0.0 0.0 @@ -474,26 +474,26 @@

0.0 3.0 -2.0 +0.0 0.0 +0.0 1.0 -1.0 -2.0 -1.0 +0.0 +0.0 -1.0 -1.0 0.0 1.0 +0.0 +0.0 0.0 +3.0 +2.0 1.0 -0.0 -0.0 @@ -502,14 +502,14 @@

:metamorph/mode :fit
#uuid "6c5234a9-66d8-4924-b92d-4b4ae8748b94" {:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)}, :options {:model-type :metamorph.ml/dummy-classifier}, :id #uuid "81fa9f04-0a20-46f1-b892-fe677de3822f", :feature-columns [:sex :pclass :embarked], :target-columns [:survived], :target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {"no" 0, "yes" 1}, :src-column :survived, :result-datatype :float64}}, :scicloj.metamorph.ml/unsupervised? nil}

}

+
:metamorph/mode :fit
#uuid "76c03ad5-9843-477d-9cb9-481bcfd41181" {:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)}, :options {:model-type :metamorph.ml/dummy-classifier}, :id #uuid "b009d482-6b86-45fc-be73-839ecdabdde9", :feature-columns [:sex :pclass :embarked], :target-columns [:survived], :target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {"no" 0, "yes" 1}, :src-column :survived, :result-datatype :float64}}, :scicloj.metamorph.ml/unsupervised? nil}

}

(keys ctx-after-train)
(:metamorph/data
  :metamorph/mode
- #uuid "6c5234a9-66d8-4924-b92d-4b4ae8748b94")
+ #uuid "76c03ad5-9843-477d-9cb9-481bcfd41181")

This context map has the “data”, the “mode” and an UUID for each operation (we had only one in this pipeline)

@@ -528,38 +528,38 @@

0.0 -2.0 +3.0 0.0 0.0 -0.0 +1.0 3.0 1.0 -0.0 +1.0 -0.0 +1.0 3.0 0.0 -1.0 +0.0 1.0 -3.0 2.0 0.0 +1.0 0.0 -2.0 +3.0 0.0 0.0 0.0 -1.0 -2.0 +3.0 +0.0 0.0 @@ -569,20 +569,20 @@

0.0 -1.0 -1.0 0.0 1.0 +0.0 +0.0 1.0 1.0 -2.0 +0.0 1.0 0.0 -2.0 +1.0 0.0 0.0 @@ -594,9 +594,9 @@

0.0 +1.0 2.0 -0.0 -0.0 +1.0 0.0 @@ -605,26 +605,26 @@

0.0 -0.0 1.0 +3.0 0.0 1.0 -0.0 -3.0 +1.0 +1.0 0.0 1.0 0.0 -3.0 +2.0 0.0 0.0 0.0 -3.0 +2.0 0.0 0.0 @@ -637,33 +637,33 @@

0.0 3.0 -2.0 +0.0 0.0 +0.0 1.0 -1.0 -2.0 -1.0 +0.0 +0.0 -1.0 -1.0 0.0 1.0 +0.0 +0.0 0.0 +3.0 +2.0 1.0 -0.0 -0.0

:fit
 
{:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)},
  :options {:model-type :metamorph.ml/dummy-classifier},
- :id #uuid "81fa9f04-0a20-46f1-b892-fe677de3822f",
+ :id #uuid "b009d482-6b86-45fc-be73-839ecdabdde9",
  :feature-columns [:sex :pclass :embarked],
  :target-columns [:survived],
  :target-categorical-maps
@@ -787,7 +787,7 @@ 

-
#uuid "6c5234a9-66d8-4924-b92d-4b4ae8748b94"
+
#uuid "76c03ad5-9843-477d-9cb9-481bcfd41181"
@@ -828,53 +828,53 @@

-0.0 +1.0 3.0 -2.0 +0.0 -0.0 1.0 +3.0 0.0 -0.0 -3.0 1.0 +3.0 +0.0 -0.0 1.0 -0.0 +3.0 +1.0 0.0 -1.0 +3.0 0.0 -0.0 -2.0 +1.0 +3.0 0.0 0.0 -3.0 +2.0 0.0 -0.0 -3.0 +1.0 +2.0 0.0 -0.0 -3.0 +1.0 +2.0 0.0 0.0 -1.0 +2.0 0.0 @@ -889,18 +889,18 @@

0.0 -1.0 -2.0 - - -0.0 3.0 0.0 + +1.0 +2.0 +2.0 + 0.0 3.0 -1.0 +0.0 1.0 @@ -908,19 +908,19 @@

0.0 -0.0 -3.0 1.0 +1.0 +2.0 -0.0 -3.0 +1.0 +1.0 0.0 -1.0 -1.0 0.0 +3.0 +1.0 0.0 @@ -929,8 +929,8 @@

0.0 -1.0 -1.0 +3.0 +0.0 0.0 @@ -948,7 +948,7 @@

:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)}

-
:id #uuid "81fa9f04-0a20-46f1-b892-fe677de3822f"
+
:id #uuid "b009d482-6b86-45fc-be73-839ecdabdde9"
@@ -971,19 +971,19 @@

-

+ - + - + @@ -992,13 +992,13 @@

0.0

- + - + - + @@ -1007,22 +1007,22 @@

0.0

- + - + - + - + - + @@ -1245,7 +1245,7 @@

:metamorph/mode :fit
#uuid "fc6e975b-ddd9-4257-8c58-9148d3d6111c" {:model-data {:majority-class 1, :distinct-labels (0.0 1.0)}, :options {:model-type :metamorph.ml/dummy-classifier}, :id #uuid "1d778a5d-2ba3-421f-827b-44186c59db00", :feature-columns [:sex :pclass :embarked], :target-columns [:survived], :target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {"no" 0, "yes" 1}, :src-column :survived, :result-datatype :float64}}, :scicloj.metamorph.ml/unsupervised? nil}

}

+
:metamorph/mode :fit
#uuid "2052bc2d-77e1-45ae-9252-29965eb0c5d4" {:model-data {:majority-class 1, :distinct-labels (0.0 1.0)}, :options {:model-type :metamorph.ml/dummy-classifier}, :id #uuid "4b86f1d9-6dcb-41ef-a333-b649c7ebafd9", :feature-columns [:sex :pclass :embarked], :target-columns [:survived], :target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {"no" 0, "yes" 1}, :src-column :survived, :result-datatype :float64}}, :scicloj.metamorph.ml/unsupervised? nil}

}

To show the power of pipelines, I start with doing the simplest possible pipeline, and expand then on it.

we can already chain train and test with usual functions:

diff --git a/docs/noj_book.interactions_ols.html b/docs/noj_book.interactions_ols.html index 2738fcb..78b33da 100644 --- a/docs/noj_book.interactions_ols.html +++ b/docs/noj_book.interactions_ols.html @@ -378,24 +378,24 @@

- - - - + + + + - - + + - + - - + + - +
0.01.0
0.0
0.01.0
1.0
1.00.0
0.0
0.01.0
0.01.0
0.01.0
...
1.00.0
0.01.0
0.0
1.00.0
0.01.0
0.01.0
1.0
:sales8.914265623.788581223.77475828E-150.425002067.559816553.367896216.43263220E-120.44549973
:youtube27.308172850.0476276026.526132460.04631401 0.00000000E+000.001744080.00174598
:facebook16.467642100.1686824418.302402570.18780113 0.00000000E+000.010243270.01026101
@@ -406,14 +406,14 @@

(-> evaluations flatten first :test-transform :metric)
-
2.0189400553227923
+
1.8099427897410054

\(R^2\)

(-> evaluations flatten first :test-transform :other-metrices first :metric)
-
0.8846834579752657
+
0.9029194967783648

@@ -462,31 +462,31 @@

:sales -20.16528893 -7.91021487 +21.10801252 +8.30687162 0.00000000 -0.39226886 +0.39354115 :youtube -9.96093974 -0.01970855 +9.66767797 +0.01846977 0.00000000 -0.00197858 +0.00191047 :facebook -2.23590405 -0.02730447 -0.02707669 -0.01221182 +1.41780951 +0.01730603 +0.15865741 +0.01220618 :youtube*facebook -15.35678259 -0.00092404 +16.75921373 +0.00093545 0.00000000 -0.00006017 +0.00005582 @@ -497,14 +497,14 @@

(-> evaluations flatten first :test-transform :metric)
-
0.8599045611943678
+
1.0086632883477964

\(R^2\)

(-> evaluations flatten first :test-transform :other-metrices first :metric)
-
0.9851756958691773
+
0.9698489441122488

\(RMSE\) and \(R^2\) of the intercation model are sligtly better.

These results suggest that the model with the interaction term is better than the model that contains only main effects. So, for this specific data, we should go for the model with the interaction model.

diff --git a/docs/noj_book.visualizing_correlation_matrices.html b/docs/noj_book.visualizing_correlation_matrices.html index 819d6ea..a22930a 100644 --- a/docs/noj_book.visualizing_correlation_matrices.html +++ b/docs/noj_book.visualizing_correlation_matrices.html @@ -546,7 +546,7 @@

Note the slider control and the tooltips.

Here is an example with an actual correlation matrix.

diff --git a/docs/search.json b/docs/search.json index 4e8be59..8a0de64 100644 --- a/docs/search.json +++ b/docs/search.json @@ -4,7 +4,7 @@ "href": "index.html", "title": "Scinojure Documentation", "section": "", - "text": "1 Preface\nScinojure (“Noj”) is an entry point to the Clojure stack for data & science.\nIt collects a few of the main libraries and documents common ways to use them together.\nSource:\nArtifact: 2-alpha5-SNAPSHOT\nNote we are using SNAPSHOT version for now, since a few of the current dependencies are at a snapshot stage for an upcoming release.\nStatus: Most of the underlying libraries are stable. The experimental parts are marked as such. For some of the libraries, we use a branch for an upcoming release. The main current goal is to provide a clear picture of the direction the stack is going towards, expecting most of it to stabilize around October 2024.", + "text": "1 Preface\nScinojure (“Noj”) is an entry point to the Clojure stack for data & science.\nIt collects a few of the main libraries and documents common ways to use them together.\nSource:\nDeps:\nNote we are using git coordinates at the moment, in order to expose a few relevant features of the current underlying libraries, which are unreleased yet.\nStatus: Most of the underlying libraries are stable. The experimental parts are marked as such. For some of the libraries, we use a branch for an upcoming release. The main current goal is to provide a clear picture of the direction the stack is going towards, expecting most of it to stabilize around October 2024.", "crumbs": [ "1  Preface" ] @@ -189,7 +189,7 @@ "href": "noj_book.automl.html#the-metamorph-pipeline-abstraction", "title": "7  AutoML using metamorph pipelines", "section": "", - "text": "(require '[scicloj.metamorph.ml :as ml]\n '[scicloj.metamorph.core :as mm]\n '[tablecloth.api :as tc])\n\n\n\n(def titanic ml-basic/numeric-titanic-data)\n\n\n\n(def splits (first (tc/split->seq titanic)))\n\n\n(def train-ds (:train splits))\n\n\n(def test-ds (:test splits))\n\n\n\n\n(def my-pipeline\n (mm/pipeline\n (ml/model {:model-type :metamorph.ml/dummy-classifier})))\n\n\n\nmy-pipeline\n\n\n#function[clojure.core/partial/fn--5908]\n\n\n\n\n\n(def ctx-after-train\n (my-pipeline {:metamorph/data train-ds\n :metamorph/mode :fit}))\n\n\nctx-after-train\n\n{\n\n\n\n\n\n\n\n\n:metamorph/data\n\n\n\nGroup: 0 [711 4]:\n\n\n\n:sex\n:pclass\n:embarked\n:survived\n\n\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n3.0\n1.0\n0.0\n\n\n0.0\n3.0\n0.0\n1.0\n\n\n1.0\n3.0\n2.0\n0.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n1.0\n2.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n1.0\n1.0\n2.0\n1.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n...\n...\n...\n...\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n3.0\n2.0\n0.0\n\n\n1.0\n1.0\n2.0\n1.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n\n\n\n\n\n\n:metamorph/mode :fit#uuid \"6c5234a9-66d8-4924-b92d-4b4ae8748b94\" {:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)}, :options {:model-type :metamorph.ml/dummy-classifier}, :id #uuid \"81fa9f04-0a20-46f1-b892-fe677de3822f\", :feature-columns [:sex :pclass :embarked], :target-columns [:survived], :target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {\"no\" 0, \"yes\" 1}, :src-column :survived, :result-datatype :float64}}, :scicloj.metamorph.ml/unsupervised? nil}}\n\n(keys ctx-after-train)\n\n\n(:metamorph/data\n :metamorph/mode\n #uuid \"6c5234a9-66d8-4924-b92d-4b4ae8748b94\")\n\n\n\n(vals ctx-after-train)\n\n(Group: 0 [711 4]:\n\n\n\n:sex\n:pclass\n:embarked\n:survived\n\n\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n3.0\n1.0\n0.0\n\n\n0.0\n3.0\n0.0\n1.0\n\n\n1.0\n3.0\n2.0\n0.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n1.0\n2.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n1.0\n1.0\n2.0\n1.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n...\n...\n...\n...\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n3.0\n2.0\n0.0\n\n\n1.0\n1.0\n2.0\n1.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n\n:fit\n{:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)},\n :options {:model-type :metamorph.ml/dummy-classifier},\n :id #uuid \"81fa9f04-0a20-46f1-b892-fe677de3822f\",\n :feature-columns [:sex :pclass :embarked],\n :target-columns [:survived],\n :target-categorical-maps\n {:survived\n {:lookup-table {\"no\" 0, \"yes\" 1},\n :src-column :survived,\n :result-datatype :float64}},\n :scicloj.metamorph.ml/unsupervised? nil}\n)\n\n\n\n(def ctx-after-predict\n (my-pipeline (assoc ctx-after-train\n :metamorph/mode :transform\n :metamorph/data test-ds)))\n\n\nctx-after-predict\n\n{\n\n\n\n\n\n\n\n\n:metamorph/data\n\n\n\n_unnamed [178 1]:\n\n\n\n:survived\n\n\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n...\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n\n\n\n\n\n\n:metamorph/mode :transform\n\n\n\n\n\n\n\n\n#uuid \"6c5234a9-66d8-4924-b92d-4b4ae8748b94\"\n\n\n\n{\n\n\n:feature-columns [:sex :pclass :embarked]\n\n\n:target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {\"no\" 0, \"yes\" 1}, :src-column :survived, :result-datatype :float64}}\n\n\n:target-columns [:survived]\n\n\n:scicloj.metamorph.ml/unsupervised? nil\n\n\n\n\n\n\n\n\n\n:scicloj.metamorph.ml/feature-ds\n\n\n\nGroup: 0 [178 3]:\n\n\n\n:sex\n:pclass\n:embarked\n\n\n\n\n0.0\n3.0\n2.0\n\n\n0.0\n1.0\n0.0\n\n\n0.0\n3.0\n1.0\n\n\n0.0\n1.0\n0.0\n\n\n0.0\n1.0\n0.0\n\n\n0.0\n2.0\n0.0\n\n\n0.0\n3.0\n0.0\n\n\n0.0\n3.0\n0.0\n\n\n0.0\n3.0\n0.0\n\n\n0.0\n1.0\n0.0\n\n\n...\n...\n...\n\n\n0.0\n3.0\n0.0\n\n\n0.0\n1.0\n2.0\n\n\n0.0\n3.0\n0.0\n\n\n0.0\n3.0\n1.0\n\n\n1.0\n3.0\n0.0\n\n\n0.0\n3.0\n1.0\n\n\n0.0\n3.0\n0.0\n\n\n1.0\n1.0\n0.0\n\n\n0.0\n3.0\n0.0\n\n\n0.0\n1.0\n1.0\n\n\n0.0\n3.0\n0.0\n\n\n\n\n\n\n\n\n\n:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)}\n\n\n:id #uuid \"81fa9f04-0a20-46f1-b892-fe677de3822f\"\n\n\n\n\n\n\n\n\n\n:scicloj.metamorph.ml/target-ds\n\n\n\nGroup: 0 [178 1]:\n\n\n\n:survived\n\n\n\n\n0.0\n\n\n0.0\n\n\n0.0\n\n\n1.0\n\n\n1.0\n\n\n0.0\n\n\n0.0\n\n\n0.0\n\n\n0.0\n\n\n0.0\n\n\n...\n\n\n0.0\n\n\n1.0\n\n\n0.0\n\n\n0.0\n\n\n1.0\n\n\n0.0\n\n\n0.0\n\n\n1.0\n\n\n0.0\n\n\n0.0\n\n\n0.0\n\n\n\n\n\n\n\n\n\n:options {:model-type :metamorph.ml/dummy-classifier}\n\n\n}\n\n\n\n\n\n}\n\n\n\n(-> ctx-after-predict :metamorph/data :survived)\n\n\n#tech.v3.dataset.column<float64>[178]\n:survived\n[1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000...]", + "text": "(require '[scicloj.metamorph.ml :as ml]\n '[scicloj.metamorph.core :as mm]\n '[tablecloth.api :as tc])\n\n\n\n(def titanic ml-basic/numeric-titanic-data)\n\n\n\n(def splits (first (tc/split->seq titanic)))\n\n\n(def train-ds (:train splits))\n\n\n(def test-ds (:test splits))\n\n\n\n\n(def my-pipeline\n (mm/pipeline\n (ml/model {:model-type :metamorph.ml/dummy-classifier})))\n\n\n\nmy-pipeline\n\n\n#function[clojure.core/partial/fn--5908]\n\n\n\n\n\n(def ctx-after-train\n (my-pipeline {:metamorph/data train-ds\n :metamorph/mode :fit}))\n\n\nctx-after-train\n\n{\n\n\n\n\n\n\n\n\n:metamorph/data\n\n\n\nGroup: 0 [711 4]:\n\n\n\n:sex\n:pclass\n:embarked\n:survived\n\n\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n1.0\n1.0\n\n\n1.0\n3.0\n0.0\n0.0\n\n\n1.0\n2.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n...\n...\n...\n...\n\n\n0.0\n1.0\n2.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n0.0\n1.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n3.0\n2.0\n1.0\n\n\n\n\n\n\n\n\n:metamorph/mode :fit#uuid \"76c03ad5-9843-477d-9cb9-481bcfd41181\" {:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)}, :options {:model-type :metamorph.ml/dummy-classifier}, :id #uuid \"b009d482-6b86-45fc-be73-839ecdabdde9\", :feature-columns [:sex :pclass :embarked], :target-columns [:survived], :target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {\"no\" 0, \"yes\" 1}, :src-column :survived, :result-datatype :float64}}, :scicloj.metamorph.ml/unsupervised? nil}}\n\n(keys ctx-after-train)\n\n\n(:metamorph/data\n :metamorph/mode\n #uuid \"76c03ad5-9843-477d-9cb9-481bcfd41181\")\n\n\n\n(vals ctx-after-train)\n\n(Group: 0 [711 4]:\n\n\n\n:sex\n:pclass\n:embarked\n:survived\n\n\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n1.0\n1.0\n\n\n1.0\n3.0\n0.0\n0.0\n\n\n1.0\n2.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n...\n...\n...\n...\n\n\n0.0\n1.0\n2.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n0.0\n1.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n3.0\n2.0\n1.0\n\n\n\n:fit\n{:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)},\n :options {:model-type :metamorph.ml/dummy-classifier},\n :id #uuid \"b009d482-6b86-45fc-be73-839ecdabdde9\",\n :feature-columns [:sex :pclass :embarked],\n :target-columns [:survived],\n :target-categorical-maps\n {:survived\n {:lookup-table {\"no\" 0, \"yes\" 1},\n :src-column :survived,\n :result-datatype :float64}},\n :scicloj.metamorph.ml/unsupervised? nil}\n)\n\n\n\n(def ctx-after-predict\n (my-pipeline (assoc ctx-after-train\n :metamorph/mode :transform\n :metamorph/data test-ds)))\n\n\nctx-after-predict\n\n{\n\n\n\n\n\n\n\n\n:metamorph/data\n\n\n\n_unnamed [178 1]:\n\n\n\n:survived\n\n\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n...\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n\n\n\n\n\n\n:metamorph/mode :transform\n\n\n\n\n\n\n\n\n#uuid \"76c03ad5-9843-477d-9cb9-481bcfd41181\"\n\n\n\n{\n\n\n:feature-columns [:sex :pclass :embarked]\n\n\n:target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {\"no\" 0, \"yes\" 1}, :src-column :survived, :result-datatype :float64}}\n\n\n:target-columns [:survived]\n\n\n:scicloj.metamorph.ml/unsupervised? nil\n\n\n\n\n\n\n\n\n\n:scicloj.metamorph.ml/feature-ds\n\n\n\nGroup: 0 [178 3]:\n\n\n\n:sex\n:pclass\n:embarked\n\n\n\n\n1.0\n3.0\n0.0\n\n\n1.0\n3.0\n0.0\n\n\n1.0\n3.0\n0.0\n\n\n1.0\n3.0\n1.0\n\n\n0.0\n3.0\n0.0\n\n\n1.0\n3.0\n0.0\n\n\n0.0\n2.0\n0.0\n\n\n1.0\n2.0\n0.0\n\n\n1.0\n2.0\n0.0\n\n\n0.0\n2.0\n0.0\n\n\n...\n...\n...\n\n\n0.0\n3.0\n0.0\n\n\n0.0\n3.0\n0.0\n\n\n1.0\n2.0\n2.0\n\n\n0.0\n3.0\n0.0\n\n\n1.0\n3.0\n0.0\n\n\n1.0\n1.0\n2.0\n\n\n1.0\n1.0\n0.0\n\n\n0.0\n3.0\n1.0\n\n\n0.0\n3.0\n0.0\n\n\n0.0\n3.0\n0.0\n\n\n0.0\n3.0\n0.0\n\n\n\n\n\n\n\n\n\n:model-data {:majority-class 1.0, :distinct-labels (0.0 1.0)}\n\n\n:id #uuid \"b009d482-6b86-45fc-be73-839ecdabdde9\"\n\n\n\n\n\n\n\n\n\n:scicloj.metamorph.ml/target-ds\n\n\n\nGroup: 0 [178 1]:\n\n\n\n:survived\n\n\n\n\n1.0\n\n\n0.0\n\n\n1.0\n\n\n1.0\n\n\n0.0\n\n\n0.0\n\n\n0.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n...\n\n\n0.0\n\n\n0.0\n\n\n1.0\n\n\n0.0\n\n\n0.0\n\n\n1.0\n\n\n1.0\n\n\n1.0\n\n\n0.0\n\n\n0.0\n\n\n0.0\n\n\n\n\n\n\n\n\n\n:options {:model-type :metamorph.ml/dummy-classifier}\n\n\n}\n\n\n\n\n\n}\n\n\n\n(-> ctx-after-predict :metamorph/data :survived)\n\n\n#tech.v3.dataset.column<float64>[178]\n:survived\n[1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000...]", "crumbs": [ "Tutorials", "7  AutoML using metamorph pipelines" @@ -200,7 +200,7 @@ "href": "noj_book.automl.html#use-metamorph-pipelines-to-do-model-training-with-higher-level-api", "title": "7  AutoML using metamorph pipelines", "section": "7.2 Use metamorph pipelines to do model training with higher level API", - "text": "7.2 Use metamorph pipelines to do model training with higher level API\nAs user of metamorph.ml we do not need to deal with this low-level details of how metamorph works, we have convenience functions which hide this\nThe following code will do the same as train, but return a context object, which contains the trained model, so it will execute the pipeline, and not only create it.\nIt uses a convenience function mm/fit which generates compliant context maps internally and executes the pipeline as well.\nThe ctx acts a collector of everything “learned” during :fit, mainly the trained model, but it could be as well other information learned from the data during :fit and to be applied at :transform .\n\n(def train-ctx\n (mm/fit titanic\n (ml/model {:model-type :metamorph.ml/dummy-classifier})))\n\n(The dummy-classifier model does not have a lot of state, so there is little to see)\n\ntrain-ctx\n\n{\n\n\n\n\n\n\n\n\n:metamorph/data\n\n\n\n_unnamed [889 4]:\n\n\n\n:sex\n:pclass\n:embarked\n:survived\n\n\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n1.0\n2.0\n1.0\n\n\n1.0\n3.0\n0.0\n1.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n3.0\n1.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n0.0\n1.0\n\n\n1.0\n2.0\n2.0\n1.0\n\n\n...\n...\n...\n...\n\n\n1.0\n2.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n0.0\n0.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n1.0\n0.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n1.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n2.0\n1.0\n\n\n0.0\n3.0\n1.0\n0.0\n\n\n\n\n\n\n\n\n:metamorph/mode :fit#uuid \"fc6e975b-ddd9-4257-8c58-9148d3d6111c\" {:model-data {:majority-class 1, :distinct-labels (0.0 1.0)}, :options {:model-type :metamorph.ml/dummy-classifier}, :id #uuid \"1d778a5d-2ba3-421f-827b-44186c59db00\", :feature-columns [:sex :pclass :embarked], :target-columns [:survived], :target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {\"no\" 0, \"yes\" 1}, :src-column :survived, :result-datatype :float64}}, :scicloj.metamorph.ml/unsupervised? nil}}\nTo show the power of pipelines, I start with doing the simplest possible pipeline, and expand then on it.\nwe can already chain train and test with usual functions:\n\n(->>\n (ml/train train-ds {:model-type :metamorph.ml/dummy-classifier})\n (ml/predict test-ds)\n :survived)\n\n\n#tech.v3.dataset.column<float64>[178]\n:survived\n[1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000...]\n\nthe same with pipelines\n\n(def pipeline\n (mm/pipeline (ml/model {:model-type :metamorph.ml/dummy-classifier})))\n\n\n(->>\n (mm/fit-pipe train-ds pipeline)\n (mm/transform-pipe test-ds pipeline)\n :metamorph/data :survived)\n\n\n#tech.v3.dataset.column<float64>[178]\n:survived\n[1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000...]", + "text": "7.2 Use metamorph pipelines to do model training with higher level API\nAs user of metamorph.ml we do not need to deal with this low-level details of how metamorph works, we have convenience functions which hide this\nThe following code will do the same as train, but return a context object, which contains the trained model, so it will execute the pipeline, and not only create it.\nIt uses a convenience function mm/fit which generates compliant context maps internally and executes the pipeline as well.\nThe ctx acts a collector of everything “learned” during :fit, mainly the trained model, but it could be as well other information learned from the data during :fit and to be applied at :transform .\n\n(def train-ctx\n (mm/fit titanic\n (ml/model {:model-type :metamorph.ml/dummy-classifier})))\n\n(The dummy-classifier model does not have a lot of state, so there is little to see)\n\ntrain-ctx\n\n{\n\n\n\n\n\n\n\n\n:metamorph/data\n\n\n\n_unnamed [889 4]:\n\n\n\n:sex\n:pclass\n:embarked\n:survived\n\n\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n1.0\n2.0\n1.0\n\n\n1.0\n3.0\n0.0\n1.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n0.0\n3.0\n1.0\n0.0\n\n\n0.0\n1.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n0.0\n1.0\n\n\n1.0\n2.0\n2.0\n1.0\n\n\n...\n...\n...\n...\n\n\n1.0\n2.0\n0.0\n1.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n0.0\n0.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n0.0\n3.0\n0.0\n0.0\n\n\n1.0\n3.0\n1.0\n0.0\n\n\n0.0\n2.0\n0.0\n0.0\n\n\n1.0\n1.0\n0.0\n1.0\n\n\n1.0\n3.0\n0.0\n0.0\n\n\n0.0\n1.0\n2.0\n1.0\n\n\n0.0\n3.0\n1.0\n0.0\n\n\n\n\n\n\n\n\n:metamorph/mode :fit#uuid \"2052bc2d-77e1-45ae-9252-29965eb0c5d4\" {:model-data {:majority-class 1, :distinct-labels (0.0 1.0)}, :options {:model-type :metamorph.ml/dummy-classifier}, :id #uuid \"4b86f1d9-6dcb-41ef-a333-b649c7ebafd9\", :feature-columns [:sex :pclass :embarked], :target-columns [:survived], :target-categorical-maps {:survived #tech.v3.dataset.categorical.CategoricalMap{:lookup-table {\"no\" 0, \"yes\" 1}, :src-column :survived, :result-datatype :float64}}, :scicloj.metamorph.ml/unsupervised? nil}}\nTo show the power of pipelines, I start with doing the simplest possible pipeline, and expand then on it.\nwe can already chain train and test with usual functions:\n\n(->>\n (ml/train train-ds {:model-type :metamorph.ml/dummy-classifier})\n (ml/predict test-ds)\n :survived)\n\n\n#tech.v3.dataset.column<float64>[178]\n:survived\n[1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000...]\n\nthe same with pipelines\n\n(def pipeline\n (mm/pipeline (ml/model {:model-type :metamorph.ml/dummy-classifier})))\n\n\n(->>\n (mm/fit-pipe train-ds pipeline)\n (mm/transform-pipe test-ds pipeline)\n :metamorph/data :survived)\n\n\n#tech.v3.dataset.column<float64>[178]\n:survived\n[1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000...]", "crumbs": [ "Tutorials", "7  AutoML using metamorph pipelines" @@ -266,7 +266,7 @@ "href": "noj_book.interactions_ols.html#additive-model", "title": "8  Ordinary least squares with interactions", "section": "", - "text": "(def linear-model-config {:model-type :fastmath/ols})\n\n\n(def additive-pipeline\n (mm/pipeline\n {:metamorph/id :model}\n (ml/model linear-model-config)))\n\n\n\n(def evaluations\n (ml/evaluate-pipelines\n [additive-pipeline]\n (tc/split->seq preprocessed-data :holdout)\n loss/rmse\n :loss\n {:other-metrices [{:name :r2\n :metric-fn fmstats/r2-determination}]}))\n\n\n\n\n(-> evaluations flatten first :fit-ctx :model ml/tidy)\n\n\n_unnamed [3 5]:\n\n\n\n:term\n:statistic\n:estimate\n:p.value\n:std.error\n\n\n\n\n:sales\n8.91426562\n3.78858122\n3.77475828E-15\n0.42500206\n\n\n:youtube\n27.30817285\n0.04762760\n0.00000000E+00\n0.00174408\n\n\n:facebook\n16.46764210\n0.16868244\n0.00000000E+00\n0.01024327\n\n\n\n\n\n\n\n(-> evaluations flatten first :test-transform :metric)\n\n\n2.0189400553227923\n\n\n\n(-> evaluations flatten first :test-transform :other-metrices first :metric)\n\n\n0.8846834579752657", + "text": "(def linear-model-config {:model-type :fastmath/ols})\n\n\n(def additive-pipeline\n (mm/pipeline\n {:metamorph/id :model}\n (ml/model linear-model-config)))\n\n\n\n(def evaluations\n (ml/evaluate-pipelines\n [additive-pipeline]\n (tc/split->seq preprocessed-data :holdout)\n loss/rmse\n :loss\n {:other-metrices [{:name :r2\n :metric-fn fmstats/r2-determination}]}))\n\n\n\n\n(-> evaluations flatten first :fit-ctx :model ml/tidy)\n\n\n_unnamed [3 5]:\n\n\n\n:term\n:statistic\n:estimate\n:p.value\n:std.error\n\n\n\n\n:sales\n7.55981655\n3.36789621\n6.43263220E-12\n0.44549973\n\n\n:youtube\n26.52613246\n0.04631401\n0.00000000E+00\n0.00174598\n\n\n:facebook\n18.30240257\n0.18780113\n0.00000000E+00\n0.01026101\n\n\n\n\n\n\n\n(-> evaluations flatten first :test-transform :metric)\n\n\n1.8099427897410054\n\n\n\n(-> evaluations flatten first :test-transform :other-metrices first :metric)\n\n\n0.9029194967783648", "crumbs": [ "Tutorials", "8  Ordinary least squares with interactions" @@ -277,7 +277,7 @@ "href": "noj_book.interactions_ols.html#interaction-effects", "title": "8  Ordinary least squares with interactions", "section": "8.2 Interaction effects", - "text": "8.2 Interaction effects\nNow we add interaction effects to it, resulting in this model equation: \\[sales = b0 + b1 * youtube + b2 * facebook + b3 * (youtube * facebook)\\]\n\n(def pipe-interaction\n (mm/pipeline\n (tcpipe/add-column :youtube*facebook (fn [ds] (tcc/* (ds :youtube) (ds :facebook))))\n {:metamorph/id :model} (ml/model linear-model-config)))\n\nAgain we evaluate the model,\n\n(def evaluations\n (ml/evaluate-pipelines\n [pipe-interaction]\n (tc/split->seq preprocessed-data :holdout)\n loss/rmse\n :loss\n {:other-metrices [{:name :r2\n :metric-fn fmstats/r2-determination}]}))\n\nand print it and the performance metrics:\n\n(-> evaluations flatten first :fit-ctx :model ml/tidy)\n\n\n_unnamed [4 5]:\n\n\n\n\n\n\n\n\n\n\n:term\n:statistic\n:estimate\n:p.value\n:std.error\n\n\n\n\n:sales\n20.16528893\n7.91021487\n0.00000000\n0.39226886\n\n\n:youtube\n9.96093974\n0.01970855\n0.00000000\n0.00197858\n\n\n:facebook\n2.23590405\n0.02730447\n0.02707669\n0.01221182\n\n\n:youtube*facebook\n15.35678259\n0.00092404\n0.00000000\n0.00006017\n\n\n\n\nAs the multiplcation of youtube*facebook is as well statistically relevant, it suggests that there is indeed an interaction between these 2 predictor variables youtube and facebook.\n\\(RMSE\\)\n\n(-> evaluations flatten first :test-transform :metric)\n\n\n0.8599045611943678\n\n\\(R^2\\)\n\n(-> evaluations flatten first :test-transform :other-metrices first :metric)\n\n\n0.9851756958691773\n\n\\(RMSE\\) and \\(R^2\\) of the intercation model are sligtly better.\nThese results suggest that the model with the interaction term is better than the model that contains only main effects. So, for this specific data, we should go for the model with the interaction model.\n\nsource: notebooks/noj_book/interactions_ols.clj", + "text": "8.2 Interaction effects\nNow we add interaction effects to it, resulting in this model equation: \\[sales = b0 + b1 * youtube + b2 * facebook + b3 * (youtube * facebook)\\]\n\n(def pipe-interaction\n (mm/pipeline\n (tcpipe/add-column :youtube*facebook (fn [ds] (tcc/* (ds :youtube) (ds :facebook))))\n {:metamorph/id :model} (ml/model linear-model-config)))\n\nAgain we evaluate the model,\n\n(def evaluations\n (ml/evaluate-pipelines\n [pipe-interaction]\n (tc/split->seq preprocessed-data :holdout)\n loss/rmse\n :loss\n {:other-metrices [{:name :r2\n :metric-fn fmstats/r2-determination}]}))\n\nand print it and the performance metrics:\n\n(-> evaluations flatten first :fit-ctx :model ml/tidy)\n\n\n_unnamed [4 5]:\n\n\n\n\n\n\n\n\n\n\n:term\n:statistic\n:estimate\n:p.value\n:std.error\n\n\n\n\n:sales\n21.10801252\n8.30687162\n0.00000000\n0.39354115\n\n\n:youtube\n9.66767797\n0.01846977\n0.00000000\n0.00191047\n\n\n:facebook\n1.41780951\n0.01730603\n0.15865741\n0.01220618\n\n\n:youtube*facebook\n16.75921373\n0.00093545\n0.00000000\n0.00005582\n\n\n\n\nAs the multiplcation of youtube*facebook is as well statistically relevant, it suggests that there is indeed an interaction between these 2 predictor variables youtube and facebook.\n\\(RMSE\\)\n\n(-> evaluations flatten first :test-transform :metric)\n\n\n1.0086632883477964\n\n\\(R^2\\)\n\n(-> evaluations flatten first :test-transform :other-metrices first :metric)\n\n\n0.9698489441122488\n\n\\(RMSE\\) and \\(R^2\\) of the intercation model are sligtly better.\nThese results suggest that the model with the interaction term is better than the model that contains only main effects. So, for this specific data, we should go for the model with the interaction model.\n\nsource: notebooks/noj_book/interactions_ols.clj", "crumbs": [ "Tutorials", "8  Ordinary least squares with interactions"