{"id":"l-ml-2-1","courseId":"ml-crash-course","lang":"en","kind":"lesson","summary":"Fit a linear regression with scikit-learn, read its intercept and coefficients, and judge it with MAE, RMSE and R² against a mean-predicting baseline.","html":"<p>Churn is a yes/no question, and you’ll come back to it in section 3. Regression comes first because its simplest model, a straight line, lets you see exactly what “learning” means: picking numbers so that predictions miss by as little as possible.</p>\n<p>The data is scikit-learn’s diabetes dataset: 442 patients, ten baseline measurements each (age, sex, body mass index, blood pressure and six blood serum readings <code dir=\"ltr\">s1</code>–<code dir=\"ltr\">s6</code>), and a target that measures how far the disease progressed one year later. The features are already centred and scaled, which is why they look like small decimals.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">One feature, one line</h2>\n<p>Start with body mass index alone. Linear regression predicts <code dir=\"ltr\">progression = intercept + coef * bmi</code>, and <code dir=\"ltr\">fit</code> chooses the two numbers.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"0\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.datasets </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_diabetes</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.linear_model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> LinearRegression</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X, y </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_diabetes(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">return_X_y</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">as_frame</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X_train, X_test, y_train, y_test </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split(X, y, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">test_size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.25</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">line </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> LinearRegression().fit(X_train[[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bmi\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]], y_train)</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"intercept:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(line.intercept_, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coef for bmi:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(line.coef_[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"first predictions:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, line.predict(X_test[[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bmi\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]])[:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].round(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>The intercept (152) is the prediction for an average BMI, since the feature is centred at 0. The coefficient (975) is the slope: one unit of this scaled BMI adds 975 to the prediction. Note the double brackets in <code dir=\"ltr\">X_train[[&quot;bmi&quot;]]</code>: scikit-learn wants a 2D table even for one feature.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Residuals and the loss</h2>\n<p>How did <code dir=\"ltr\">fit</code> choose those numbers? For each patient, the <strong>residual</strong> is the actual value minus the predicted value: the vertical gap between the point and the line. Ordinary least squares picks the intercept and slope that make the sum of squared residuals as small as possible. That sum, the thing being minimised, is the <strong>loss</strong>.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 640 280\" role=\"img\" aria-labelledby=\"l-ml-2-1-t1\"><defs><marker id=\"arrow-l-ml-2-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-ml-2-1-t1\">Scatter of points around a rising line. Dashed vertical segments join each point to the line; these residuals are squared and summed, and least squares picks the line that makes that sum smallest.</title>\n  <line class=\"d-line\" x1=\"60\" y1=\"240\" x2=\"600\" y2=\"240\"/>\n  <line class=\"d-line\" x1=\"60\" y1=\"240\" x2=\"60\" y2=\"20\"/>\n  <text class=\"d-label-muted\" x=\"560\" y=\"262\">bmi</text>\n  <text class=\"d-label-muted\" x=\"68\" y=\"34\">progression</text>\n  <path class=\"d-arrow\" d=\"M80 215 L580 55\"/>\n  <line class=\"d-dashed d-line\" x1=\"130\" y1=\"199\" x2=\"130\" y2=\"168\"/>\n  <circle class=\"d-dot\" cx=\"130\" cy=\"168\" r=\"6\"/>\n  <line class=\"d-dashed d-line\" x1=\"200\" y1=\"177\" x2=\"200\" y2=\"214\"/>\n  <circle class=\"d-dot\" cx=\"200\" cy=\"214\" r=\"6\"/>\n  <line class=\"d-dashed d-line\" x1=\"270\" y1=\"154\" x2=\"270\" y2=\"120\"/>\n  <circle class=\"d-dot\" cx=\"270\" cy=\"120\" r=\"6\"/>\n  <line class=\"d-dashed d-line\" x1=\"340\" y1=\"132\" x2=\"340\" y2=\"160\"/>\n  <circle class=\"d-dot\" cx=\"340\" cy=\"160\" r=\"6\"/>\n  <line class=\"d-dashed d-line\" x1=\"420\" y1=\"106\" x2=\"420\" y2=\"62\"/>\n  <circle class=\"d-dot\" cx=\"420\" cy=\"62\" r=\"6\"/>\n  <line class=\"d-dashed d-line\" x1=\"500\" y1=\"81\" x2=\"500\" y2=\"120\"/>\n  <circle class=\"d-dot\" cx=\"500\" cy=\"120\" r=\"6\"/>\n  <text class=\"d-label\" x=\"430\" y=\"90\">residual</text>\n</svg><figcaption>Residuals are the vertical gaps between points and the line</figcaption></figure>\n<p>Squaring does two jobs: it makes every miss count as positive, and it makes big misses count much more than small ones. A miss of 20 costs 400; a miss of 40 costs 1,600. Keep that in mind, because it explains the difference between two of the metrics below.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">All ten features</h2>\n<p>Multiple regression is the same idea with more terms: one coefficient per feature, plus the intercept.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"1\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.datasets </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_diabetes</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.linear_model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> LinearRegression</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X, y </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_diabetes(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">return_X_y</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">as_frame</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X_train, X_test, y_train, y_test </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split(X, y, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">test_size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.25</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> LinearRegression().fit(X_train, y_train)</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> name, coef </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> zip</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(X.columns, model.coef_):</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:>4}</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> {</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">coef</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:8.1f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Reading coefficients as importance or cause</span></p>\n<p><code dir=\"ltr\">s1</code> gets -918 and <code dir=\"ltr\">s2</code> gets +508. Both measure related cholesterol quantities, so they rise and fall together, and the model can trade weight between them almost freely. Huge opposite coefficients on correlated features are a sign of that, not evidence that <code dir=\"ltr\">s1</code> protects patients. Coefficients describe this fitted model; they say nothing about what would happen if you changed a patient’s blood chemistry.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Measuring a regression</h2>\n<p><code dir=\"ltr\">score</code> gives R², but you should know three metrics and when each one fits.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"python\" data-run=\"python\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Python</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"python\" data-index=\"2\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.datasets </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_diabetes</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.dummy </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> DummyRegressor</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.linear_model </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> LinearRegression</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.metrics </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> mean_absolute_error, root_mean_squared_error, r2_score</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sklearn.model_selection </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X, y </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> load_diabetes(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">return_X_y</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">as_frame</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">True</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">X_train, X_test, y_train, y_test </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> train_test_split(X, y, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">test_size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.25</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">random_state</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">models </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"mean (dummy)\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (DummyRegressor(), X.columns),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"bmi only\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (LinearRegression(), [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bmi\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]),</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">    \"all features\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (LinearRegression(), X.columns),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> name, (model, cols) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">in</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> models.items():</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    pred </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> model.fit(X_train[cols], y_train).predict(X_test[cols])</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    print</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:13}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> MAE </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">mean_absolute_error(y_test, pred)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:5.1f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  \"</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">          f</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"RMSE </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">root_mean_squared_error(y_test, pred)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:5.1f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  R2 </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">r2_score(y_test, pred)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:6.3f}</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<ul>\n<li><strong>MAE</strong> (mean absolute error) is the average size of a miss, in the target’s units. “On average we’re off by 42 points” is a sentence a doctor or a manager understands. Use it to communicate.</li>\n<li><strong>RMSE</strong> (root mean squared error) is the square root of the average squared miss, also in target units. Because of the squaring it’s always at least as large as MAE, and much larger when a few predictions are badly wrong. Use it when big misses are disproportionately costly.</li>\n<li><strong>R²</strong> compares the model’s squared error with that of always predicting the mean: 1 is perfect, 0 is no better than predicting the mean, and negative is worse than the mean. It’s unitless, so it’s handy for comparing models on the same data, but it hides how big the misses are in real terms.</li>\n</ul>\n<p>The full model cuts MAE from 65.5 (the mean baseline) to 41.5 and explains about half the variance. That’s honest progress, and also a reminder that half of what drives this disease isn’t in these ten columns.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Report MAE next to R²</span></p>\n<p>“R² = 0.48” sounds abstract and, to non-specialists, weak. “Typical miss of 42 points on a scale where patients range from 25 to 346, down from 66 for the naive guess” tells the same story in terms people can act on.</p>\n</aside>\n<p>You’ve used <code dir=\"ltr\">fit</code> as a black box that returns the best line. Next lesson opens the box: you’ll write the search for the best line yourself, with gradient descent, the same idea that trains neural networks.</p>\n","toc":[{"id":"sec-1","text":"One feature, one line","level":2},{"id":"sec-2","text":"Residuals and the loss","level":2},{"id":"sec-3","text":"All ten features","level":2},{"id":"sec-4","text":"Measuring a regression","level":2}],"takeaways":["Linear regression predicts <code dir=\"ltr\">intercept + coef1 * x1 + coef2 * x2 + …</code> and picks the numbers that minimise the sum of squared residuals.","A residual is actual minus predicted; every regression metric is a different summary of the residuals.","MAE is the typical miss in the target’s own units; RMSE is in the same units but punishes big misses more.","R² is the share of variance explained compared with always predicting the mean: 0 means no better than the mean, and it can go negative.","Coefficients of correlated features can be large and opposite in sign, so don’t read them as importance or cause."],"further":[{"title":"Linear Models: Ordinary Least Squares","url":"https://scikit-learn.org/stable/modules/linear_model.html#ordinary-least-squares"},{"title":"Regression metrics","url":"https://scikit-learn.org/stable/modules/model_evaluation.html#regression-metrics"},{"title":"Diabetes dataset","url":"https://scikit-learn.org/stable/datasets/toy_dataset.html#diabetes-dataset"}],"quiz":[{"q":"<p>A model predicting next-year spend has MAE = $40 and RMSE = $150. What does the gap suggest?</p>","options":[{"text":"The model is biased and predicts too high on average.","why":"Neither metric shows direction; both use absolute or squared misses. You’d look at the mean residual for bias."},{"text":"Most misses are small, but a few are very large.","why":"Correct. Squaring makes rare big misses dominate RMSE, while MAE weighs every miss equally."},{"text":"RMSE was computed on the training set and MAE on the test set.","why":"Nothing in the numbers says that. RMSE is always at least MAE on the same data; a large gap is about the shape of the errors."}],"answer":1},{"q":"<p>A model scores R² = -0.2 on the test set. What does that mean?</p>","options":[{"text":"It explains 20% of the variance, in the negative direction.","why":"R² has no direction. A negative value is a comparison with the mean predictor, not a signed share."},{"text":"The calculation is broken, because R² is a square and can’t be negative.","why":"The name is historical. On test data R² is 1 minus a ratio of errors, and the ratio can exceed 1."},{"text":"Its predictions are worse than predicting the mean for everyone.","why":"Correct. R² compares the model’s squared error with that of always predicting the mean of the true values; below 0 means the model loses."}],"answer":2},{"q":"<p>In the diabetes model, <code dir=\"ltr\">s1</code> has coefficient -918 and <code dir=\"ltr\">s2</code> has +508. Both are cholesterol-related blood measurements. What’s the safest reading?</p>","options":[{"text":"<code dir=\"ltr\">s1</code> strongly lowers disease progression, so raising it would help patients.","why":"Coefficients describe the fitted model, not cause and effect, and correlated inputs make them unstable."},{"text":"<code dir=\"ltr\">s1</code> is the most important feature because its coefficient is the largest.","why":"Size alone misleads when features are correlated; the two terms largely cancel each other."},{"text":"The two features are correlated, so the model balanced large opposite weights; their individual values aren’t reliable.","why":"Correct. With correlated features many coefficient combinations fit almost equally well. Regularisation in this section’s last lesson tames this."}],"answer":2}],"exercise":{"kind":"python","starter":"from sklearn.datasets import load_diabetes\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn.metrics import mean_absolute_error, root_mean_squared_error, r2_score\nfrom sklearn.model_selection import train_test_split\n\nX, y = load_diabetes(return_X_y=True, as_frame=True)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)\ncols = [\"bmi\", \"bp\", \"s5\"]\n\n# 1. Fit a LinearRegression on the three columns in `cols`\nmodel = LinearRegression()\n\n# 2. Predict on the test rows and compute the three metrics\npred = None\nmae = None\nrmse = None\nr2 = None\n","solution":"from sklearn.datasets import load_diabetes\nfrom sklearn.linear_model import LinearRegression\nfrom sklearn.metrics import mean_absolute_error, root_mean_squared_error, r2_score\nfrom sklearn.model_selection import train_test_split\n\nX, y = load_diabetes(return_X_y=True, as_frame=True)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)\ncols = [\"bmi\", \"bp\", \"s5\"]\n\nmodel = LinearRegression().fit(X_train[cols], y_train)\n\npred = model.predict(X_test[cols])\nmae = mean_absolute_error(y_test, pred)\nrmse = root_mean_squared_error(y_test, pred)\nr2 = r2_score(y_test, pred)\nprint(f\"MAE {mae:.1f}  RMSE {rmse:.1f}  R2 {r2:.3f}\")\n","checks":[{"id":"fitted","test":"hasattr(model, 'coef_') and len(model.coef_) == 3"},{"id":"pred","test":"pred is not None and len(pred) == len(y_test) and abs(float(pred[0]) - float(LinearRegression().fit(X_train[['bmi', 'bp', 's5']], y_train).predict(X_test[['bmi', 'bp', 's5']])[0])) < 1e-6"},{"id":"mae","test":"mae is not None and abs(float(mae) - float((y_test - LinearRegression().fit(X_train[['bmi', 'bp', 's5']], y_train).predict(X_test[['bmi', 'bp', 's5']])).abs().mean())) < 1e-6"},{"id":"rmse","test":"rmse is not None and abs(float(rmse) - float(((y_test - LinearRegression().fit(X_train[['bmi', 'bp', 's5']], y_train).predict(X_test[['bmi', 'bp', 's5']])) ** 2).mean() ** 0.5)) < 1e-6"},{"id":"r2","test":"r2 is not None and 0.3 < float(r2) < 0.6 and float(rmse) >= float(mae)"}],"title":"A three-feature regression, measured three ways","prompt":"<p>Fit a <code dir=\"ltr\">LinearRegression</code> on body mass index, blood pressure and <code dir=\"ltr\">s5</code> (the columns in <code dir=\"ltr\">cols</code>) and measure it on the test set.</p>\n<ul>\n<li><code dir=\"ltr\">model</code>: the fitted regression.</li>\n<li><code dir=\"ltr\">pred</code>: its predictions for <code dir=\"ltr\">X_test[cols]</code>.</li>\n<li><code dir=\"ltr\">mae</code>, <code dir=\"ltr\">rmse</code>, <code dir=\"ltr\">r2</code>: the three metrics from this lesson, computed on the test set.</li>\n</ul>\n","hints":["Fit with <code dir=\"ltr\">model = LinearRegression().fit(X_train[cols], y_train)</code>.","The metric functions take <code dir=\"ltr\">(y_true, y_pred)</code> in that order.","<code dir=\"ltr\">root_mean_squared_error</code> is in <code dir=\"ltr\">sklearn.metrics</code>; it’s already imported."],"explanation":"<p>Three well-chosen features get MAE about 42 and R² about 0.50, a slightly higher R² than all ten features reach on this split (0.49). That’s common: a few features carry most of the signal, and extra correlated features add noise to the coefficients. RMSE is larger than MAE because a handful of patients are predicted badly, and squaring makes those misses count more.</p>\n","checkLabels":{"fitted":"model is a fitted regression on three features","pred":"pred holds the test predictions","mae":"mae is the mean absolute error","rmse":"rmse is the root mean squared error","r2":"r2 is the test R² (and RMSE ≥ MAE)"}},"words":661,"readMinutes":3}