Machine Learning 📂 Supervised Learning · 9 of 19 167 min read

Ridge & Lasso Regression

Master Ridge (L2) and Lasso (L1) regularisation from first principles. Understand the maths, the intuition, the geometry, and every hyperparameter — with full Python examples, visual diagrams, and real-world stories

🌐 Language
▶️
This Tutorial Has Eight Simulators

Look for the interactive boxes: The Overfitting Cure (Section 01), Multicollinearity Lab (02), Geometry Explorer (05), Shrinkage Rules (06), Alpha Tuner (08), Scaling Matters (09), Coefficient Paths (12) and ElasticNet Mixer (13). Every model in them is fitted live in your browser.

Section 01

The Story That Explains Everything

The Over-Eager Student vs the Calm Expert
Two students study for the same exam. Student A memorises everything — every note, every footnote, even one strange example the teacher mentioned once. On the practice paper (training data), Student A scores 100%. On the real exam (test data), only 62%. Student A memorised instead of learning.

Student B reads the same notes but forces himself to keep only the simple, core rules. If an idea needs a long list of special cases, he drops the details. Practice paper: 88%. Real exam: 85%. Almost no gap.

Regularisation is Student B's discipline. It is a penalty that punishes complexity, so the model learns the pattern instead of memorising the noise. Ridge and Lasso are the two most popular forms of this discipline.

Overfitting happens when a model learns the training data too well — including its noise and random ups and downs. It does brilliantly on data it has seen, and badly on data it has not. Regularisation adds a penalty term to the loss function. The penalty grows when the coefficients grow, so the model prefers small, calm coefficients. Such models usually work much better on new data.

💡
The Core Idea

Ridge and Lasso both start from the normal Linear Regression loss and add a penalty for large coefficients. Ridge adds the sum of squared coefficients (the L2 penalty). Lasso adds the sum of absolute coefficients (the L1 penalty). One number, alpha (α), sets how strong the penalty is.

Try It — The Overfitting Cure

A very flexible model (a degree-12 polynomial) is fitted to only 14 points. With no penalty it wiggles wildly. Move the α slider to the right and watch the curve calm down — and the giant coefficients shrink.

💊 The Overfitting CureA 12-degree polynomial on 14 points. Turn up α and watch it calm down.
α (penalty strength)
● 14 training points   ○ new points (test)   — model   - - - true pattern
the 12 coefficients

Section 02

The Problem — Why Plain Linear Regression Can Fail

Ordinary Least Squares (OLS) regression only tries to make the training error as small as possible. It will choose any coefficients that do that — even huge, unstable ones.

📈 OLS Cost Function
RSS = Σ(yᵢ − ŷᵢ)² = Σ(yᵢ − β₀ − β₁x₁ − … − βₚxₚ)²
Nothing stops the β coefficients from growing. With many features, or features that are strongly related, the coefficients can explode → overfitting.
⚠️
Three Situations Where OLS Struggles

1. More features than rows (p > n): OLS has infinitely many perfect solutions, so it cannot choose one.
2. Multicollinearity: when features are strongly correlated, the coefficients swing wildly from one sample to the next.
3. Useless features: OLS gives every feature some weight, so noise features add noise to every prediction.

Try It — Multicollinearity Lab

Here x₁ and x₂ are almost the same feature, and the true answer is β₁ = β₂ = 1. Each dot is the result of fitting one random dataset. Watch how far OLS (orange) wanders compared with Ridge (green).

🔗 Multicollinearity LabTwo features that say almost the same thing. True β = (1, 1).
correlation between x₁ and x₂
Ridge α
each dot = (β₁, β₂) from one random dataset of 30 rows   ● OLS   ● Ridge   ★ truth

Section 03

Ridge Regression — L2 Regularisation

The Leash That Shrinks But Never Snaps
Think of each coefficient as a dog on an elastic leash tied to zero. The further a dog runs from zero, the harder the leash pulls it back. But no dog is ever set free, and no dog is pulled all the way home. Every feature keeps some influence, even if it is small. The leash shrinks — it never cuts. That is Ridge.

Ridge Regression adds the sum of squared coefficients, multiplied by α, to the loss:

📝 Ridge Cost Function
RSS + α × Σβⱼ² = Σ(yᵢ − ŷᵢ)² + α × (β₁² + β₂² + … + βₚ²)
This is exactly the form scikit-learn's Ridge uses.
α = 0
No penalty
Same as plain OLS.
α → ∞
All β → 0
Coefficients shrink toward zero, but for any finite α they are almost never exactly zero.
Intercept
β₀ is not penalised
Only the slopes are pulled toward zero.
🔗
Handles Multicollinearity
Ridge's superpower
When two features are strongly correlated, OLS cannot decide which one to trust, so the coefficients swing. Ridge shares the weight between them and gives stable coefficients.
📉
Shrinks, Does Not Remove
L2 behaviour
The squared penalty is smooth and round. The best answer almost never lands exactly on zero, so every feature stays in the model. Good when you believe most features help a little.
⚡
Exact Formula
Fast to solve
Ridge has a closed-form answer: β̂ = (XᵀX + αI)⁻¹Xᵀy (on centred data). Adding αI makes the matrix invertible even when features are correlated, or when p > n.

Section 04

Lasso Regression — L1 Regularisation

The Gate That Lets Only Strong Features Through
Now picture a gate in front of every feature. The gate gets higher as α grows. A weak feature cannot climb over it — its coefficient becomes exactly zero, and it leaves the model. Strong features climb over, but each one loses a little height on the way. So Lasso shrinks and selects features at the same time. This is what makes it different from Ridge.

Lasso (Least Absolute Shrinkage and Selection Operator) uses the sum of absolute values of the coefficients as its penalty:

📝 Lasso Cost Function (scikit-learn form)
(1 / 2n) × RSS + α × Σ|βⱼ|
Notice the 1/(2n) in front of RSS. scikit-learn's Ridge does not have it. So a "good" α for Lasso is usually much smaller than a good α for Ridge — never copy α from one to the other.
α = 0
No penalty
Same as plain OLS.
α large
Many β = 0
Weak coefficients become exactly zero — a sparse model. Above a certain α, all of them are zero.
Solver
No closed form
Lasso is solved step by step, usually with coordinate descent.
🎯
Lasso's Special Ability — Automatic Feature Selection

The pull of the L2 penalty gets weaker as a coefficient gets close to zero (the slope of β² is 2β). The pull of the L1 penalty does not get weaker (the slope of |β| is always ±1). So Lasso keeps pushing small coefficients until they reach exactly zero. With 100 features, Lasso might keep only 12. That makes the model easier to explain — but the chosen 12 are not always exactly the "true" ones, especially when features are correlated.


Section 05

The Geometry — Why L1 Gives Zeros and L2 Does Not

The clearest way to see the difference is to draw it. Think of the penalty as a budget: the coefficients must stay inside a region. For L1 the region is a diamond. For L2 it is a circle. The loss has oval contour lines (ellipses) around the OLS answer. The regularised answer is the point where the smallest ellipse first touches the region.

Try It — Geometry Explorer

Drag the orange OLS point. Make the budget smaller. Press Grow the ellipse to watch the contour grow until it touches the shape.

📐 Geometry ExplorerDrag the OLS point. Change the budget. See where the ellipse first touches the shape.
budget t (smaller = stronger penalty)
● OLS answer   ◯ loss contours   shaded = allowed region   big dot = regularised answer
🧠
The Geometric Secret

The diamond has sharp corners, and every corner sits on an axis — where one coefficient is exactly zero. Growing ellipses very often hit a corner first, so Lasso often gives zeros. The circle has no corners. The ellipse touches it at a smooth point, almost never exactly on an axis, so Ridge shrinks both coefficients but keeps them non-zero. In more dimensions the diamond has even more corners and edges, so Lasso zeros become even more common.


Section 06

Shrinkage Rules — What Each Penalty Does to One Coefficient

In the simplest case (features that are standardised and not correlated), the two penalties follow very simple rules:

Ridge — proportional shrinkage
β_ridge = β_OLS / (1 + α)
Every coefficient is multiplied by the same factor. Big ones stay big, small ones stay small — none becomes zero.
Lasso — soft thresholding
β_lasso = sign(β_OLS) × max(|β_OLS| − α, 0)
Every coefficient moves α closer to zero, and stops at zero. Anything smaller than α becomes exactly 0.

Try It — Shrinkage Rules

Move the penalty slider. The left chart shows the rule for any coefficient. The bars show what happens to eight example coefficients.

✂️ Shrinkage RulesWhat each penalty does to one coefficient (simple case: uncorrelated, standardised features).
penalty strength
OLS
after Ridge
after Lasso

Section 07

Ridge vs Lasso — Side by Side

PropertyRidge (L2)Lasso (L1)
Penaltyα × Σβⱼ²α × Σ|βⱼ|
Constraint shapeSmooth circle / sphereDiamond with corners
Coefficients become exactly 0?Almost neverYes, often
Feature selection?No — keeps all featuresYes — automatic
Correlated featuresShares weight, stableOften keeps one, drops others — can be unstable
p > n (more features than rows)WorksWorks, but keeps at most n features
Closed-form solution?YesNo — coordinate descent
Pull of the penalty near 0Fades to zero (slope 2αβ)Stays constant (±α)
Best forMany features that all help a little; correlated featuresMany features where only a few really matter

Section 08

The Alpha Hyperparameter — Bias–Variance Trade-off

α is the most important setting in regularised regression. It controls the balance between underfitting and overfitting. As α grows, training error goes up (more bias). Test error usually goes down first (less variance), then up again. The best α is at the bottom of the test-error curve — and cross-validation finds it without touching the test set.

Try It — Alpha Tuner

50 training rows, 20 features, but only 5 features really matter (bold). Slide α and compare training error, test error and 5-fold CV error. Switch between Ridge and Lasso.

🎛️ Alpha Tuner50 training rows, 20 features, only 5 really matter (bold).
α
🔄
α Too Small
Overfitting zone
Almost no penalty. The model memorises training noise. Large, unstable coefficients. A big gap between training and test error. Close to plain OLS.
🎯
α Just Right
Sweet spot
Coefficients are shrunk just enough. The model works well on new data. Find it with k-fold cross-validation, for example RidgeCV or LassoCV.
⛔
α Too Large
Underfitting zone
All coefficients are pushed toward zero. The model becomes too simple — at the extreme it just predicts the mean. Training and test errors are both poor.

Section 09

Why Feature Scaling Is a Must

Measuring in Millimetres vs Kilometres
A house price depends on its area (in square feet, around 600–3000) and its number of bedrooms (1–5). One extra square foot changes the price only a little, so the area coefficient is a tiny number. One extra bedroom changes the price a lot, so the bedrooms coefficient is a big number.

The penalty only looks at the size of the coefficient. So it hardly touches area and hits bedrooms hard — even if both features matter equally. Change the units (area in square metres, or in square kilometres) and the penalty would change, even though the house is the same. That is unfair.

Try It — Scaling Matters

Both features truly matter equally. Increase the Lasso α with and without scaling, and watch which feature the penalty attacks.

📏 Scaling MattersPrice depends equally on area (sq ft) and bedrooms. Lasso with and without scaling.
Lasso α
raw coefficients
effect of +1 std on price
⚠️
Always Scale Features Before Ridge or Lasso

Use StandardScaler (mean 0, standard deviation 1) before Ridge or Lasso. Then the penalty treats every feature fairly — it punishes real complexity, not the choice of units. Put the scaler and the model in one Pipeline, so the scaler learns only from training rows.


Section 10

Python — Ridge Regression with Cross-Validation

This example uses scikit-learn's built-in diabetes dataset: 442 patients, 10 measurements each (age, sex, BMI, blood pressure and six blood tests s1–s6), and a number that says how much the disease progressed after one year. The scaler and the model live in one pipeline, and RidgeCV picks α with 10-fold cross-validation.

import numpy as np
import pandas as pd
from sklearn.datasets import load_diabetes
from sklearn.linear_model import RidgeCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# ── 1. Load data: 442 patients, 10 features, target = disease progression after 1 year ──
X, y = load_diabetes(return_X_y=True, as_frame=True)

# ── 2. Train / test split ───────────────────────────────
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ── 3. Pipeline = scaler + model (the scaler learns from training rows only) ──
alphas = np.logspace(-3, 4, 100)                    # 0.001 … 10,000
model = make_pipeline(StandardScaler(), RidgeCV(alphas=alphas, cv=10))
model.fit(X_train, y_train)
ridge = model[-1]
print(f"Best alpha: {ridge.alpha_:.4f}")

# ── 4. Evaluate on the test set ─────────────────────────
y_pred = model.predict(X_test)
print(f"Test RMSE : {mean_squared_error(y_test, y_pred) ** 0.5:.2f}")
print(f"Test R²   : {r2_score(y_test, y_pred):.4f}")

# ── 5. Coefficients (on standardised features) ──────────
coef = pd.Series(ridge.coef_, index=X.columns).sort_values(key=abs, ascending=False)
print("\nRidge coefficients:")
print(coef.round(2).to_string())
Output
Best alpha: 54.6228 Test RMSE : 53.43 Test R² : 0.4611 Ridge coefficients: bmi 23.44 s5 18.16 bp 14.99 sex -9.43 s3 -9.13 s4 7.03 s1 -4.44 s2 -4.28 s6 4.05 age 2.03
💡
Notice: Every Coefficient Is Non-Zero

Even the smallest coefficient (age, 2.03) is not zero. That is Ridge: every feature stays, just shrunk. Look at s1 and s2: they are strongly correlated (r = 0.90), and Ridge gives them almost the same small weight (−4.44 and −4.28). It shares the weight. RidgeCV tried 100 α values and chose 54.6.


Section 11

Python — Lasso Regression with Feature Selection

Now the same data with Lasso. Watch for coefficients that become exactly zero.

import numpy as np
import pandas as pd
from sklearn.datasets import load_diabetes
from sklearn.linear_model import LassoCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ── LassoCV: tries 100 alphas (by default) with 10-fold cross-validation ──
model = make_pipeline(StandardScaler(),
                      LassoCV(cv=10, max_iter=10000, random_state=42))
model.fit(X_train, y_train)
lasso = model[-1]
print(f"Best alpha: {lasso.alpha_:.4f}")

y_pred = model.predict(X_test)
print(f"Test RMSE : {mean_squared_error(y_test, y_pred) ** 0.5:.2f}")
print(f"Test R²   : {r2_score(y_test, y_pred):.4f}")

# ── Feature selection report ─────────────────────────────
coef = pd.Series(lasso.coef_, index=X.columns)
print(f"\nFeatures kept: {(coef != 0).sum()} / {len(coef)}")
for name, c in coef.items():
    print(f"  {name:4s}: {c + 0.0:8.2f}  {'kept' if c != 0 else 'ZEROED'}")
Output
Best alpha: 0.1440 Test RMSE : 53.69 Test R² : 0.4560 Features kept: 9 / 10 age : 1.69 kept sex : -11.20 kept bmi : 25.91 kept bp : 16.59 kept s1 : -27.10 kept s2 : 11.29 kept s3 : 0.00 ZEROED s4 : 10.37 kept s5 : 28.69 kept s6 : 2.39 kept
✏️
What Lasso Did Here

Lasso removed s3 completely. s3 is strongly linked to s4 (r = −0.74), so most of its information is already in the model.
Now compare s1 and s2 again. Ridge gave them −4.44 and −4.28. Lasso gave them −27.10 and +11.29 — large and with opposite signs. With correlated features, Lasso's choices can be unstable, and individual coefficients are hard to interpret.
Also notice the best α: 0.144 for Lasso vs 54.6 for Ridge. The two penalties are scaled differently in scikit-learn, so α values are not comparable. Test R² is almost the same for both (0.456 vs 0.461). Neither is "always better" — it depends on your data and your goal.


Section 12

Coefficient Paths — How Coefficients Change with Alpha

A regularisation path plots every coefficient as α goes from very small (close to OLS) to very large (everything near zero). It is the best picture of the difference between Ridge and Lasso.

Try It — Coefficient Paths

Five features really matter (coloured, labelled). Three are noise (grey). Press Play to sweep α, and switch between Ridge and Lasso.

📉 Coefficient Paths8 features (5 real, 3 noise). Sweep α from small to large.
α
📉
How to Read a Path

Ridge: the lines glide smoothly toward zero together. They can cross each other, and a coefficient can even change sign, but they do not land on zero.
Lasso: lines hit zero one by one. Each line that lands on zero is a feature leaving the model. Reading the path from right to left shows the order in which features "enter" — a quick ranking of their usefulness.


Section 13

ElasticNet — The Best of Both Worlds

ElasticNet mixes both penalties. You get Lasso's feature selection and Ridge's stability with correlated features. It has two settings: α (total strength) and l1_ratio (how much of the penalty is L1).

⚖️ ElasticNet Cost Function (scikit-learn form)
(1 / 2n) × RSS + α × l1_ratio × Σ|β| + α × (1 − l1_ratio) / 2 × Σβ²
One knob for strength (α), one knob for the mix (l1_ratio).
l1_ratio = 0
Pure L2
Ridge-style penalty.
l1_ratio = 1
Pure L1
Same as Lasso.
l1_ratio = 0.5
Equal mix
A common starting point.

Try It — ElasticNet Mixer

Three features (A1, A2, A3) are near-copies of the same signal. Slide l1_ratio from Ridge to Lasso and watch what happens to the A group.

🧪 ElasticNet MixerA1, A2, A3 are near-copies of one signal. B is a second signal. N1, N2 are noise.
l1_ratio (0 = Ridge, 1 = Lasso)
α

Python — ElasticNetCV

import numpy as np
from sklearn.datasets import load_diabetes
from sklearn.linear_model import ElasticNetCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score

X, y = load_diabetes(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ── ElasticNetCV searches alpha AND l1_ratio together ──
model = make_pipeline(StandardScaler(), ElasticNetCV(
    l1_ratio=[.1, .3, .5, .7, .9, .95, 1.0],
    alphas=np.logspace(-3, 2, 60),
    cv=10, max_iter=10000, random_state=42))
model.fit(X_train, y_train)
enet = model[-1]

print(f"Best alpha     : {enet.alpha_:.4f}")
print(f"Best l1_ratio  : {enet.l1_ratio_:.2f}")
print(f"Test R²        : {r2_score(y_test, model.predict(X_test)):.4f}")
print(f"Features zeroed: {(enet.coef_ == 0).sum()} / {len(enet.coef_)}")
Output
Best alpha : 0.0495 Best l1_ratio : 0.90 Test R² : 0.4554 Features zeroed: 1 / 10
💡
Interpretation

On the diabetes data, cross-validation chose l1_ratio = 0.90 — close to Lasso — and one feature was zeroed. Test R² (0.455) is almost the same as Ridge and Lasso. That is normal: with only 10 features, the three methods give similar accuracy. ElasticNet matters most when you have many features in correlated groups, such as genes or word counts.


Section 14

When to Use Ridge, Lasso or ElasticNet

SituationGood first choiceWhy
Many features, most probably help a littleRidgeShrinks all coefficients, keeps every feature
Many features, only a few really matterLassoAutomatic sparse feature selection
Strongly correlated featuresRidge or ElasticNetShare weight inside the group; more stable
p > n (more features than rows)Ridge or ElasticNetBoth work; Lasso can keep at most n features
You need a short, easy-to-explain modelLassoFew non-zero coefficients
Correlated groups AND you want selectionElasticNetKeeps groups together, still removes noise
Genes, text, very wide dataElasticNet or LassoThousands of features, few useful
Not sureElasticNetCVLet cross-validation choose α and l1_ratio

Section 15

Real-World Example — Many Features, Few Matter

The Spreadsheet with 100 Columns
A data scientist builds a house-price model. After feature engineering she has 100 columns: neighbourhood codes, ratios, ages, interaction terms. She suspects only a handful really matter. Plain OLS fits the training data almost perfectly and fails on new houses. Ridge helps a lot. Lasso helps even more and hands her a short list of features to explain to her manager.

The code below recreates this situation with synthetic data where we know the truth: 100 features, only 10 of them real.
import numpy as np
from sklearn.datasets import make_regression
from sklearn.linear_model import LinearRegression, RidgeCV, LassoCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score

# ── 150 rows, 100 features, but only 10 features really matter ──
X, y, true_coef = make_regression(n_samples=150, n_features=100, n_informative=10,
                                  noise=20, coef=True, random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)

models = {
    "OLS  ": make_pipeline(StandardScaler(), LinearRegression()),
    "Ridge": make_pipeline(StandardScaler(), RidgeCV(alphas=np.logspace(-3, 4, 50))),
    "Lasso": make_pipeline(StandardScaler(), LassoCV(cv=5, max_iter=20000, random_state=0)),
}
for name, m in models.items():
    m.fit(X_train, y_train)
    print(f"{name}  train R² = {r2_score(y_train, m.predict(X_train)):.3f}   "
          f"test R² = {r2_score(y_test, m.predict(X_test)):.3f}")

lasso = models["Lasso"][-1]
kept = np.flatnonzero(lasso.coef_)
real = np.flatnonzero(true_coef)
print(f"\nLasso kept {len(kept)} of 100 features")
print(f"Real features found: {len(set(kept) & set(real))} of {len(real)}")
Output
OLS train R² = 0.999 test R² = 0.271 Ridge train R² = 0.999 test R² = 0.893 Lasso train R² = 0.984 test R² = 0.979 Lasso kept 10 of 100 features Real features found: 7 of 10
✅
Reading the Result

OLS: training R² 0.999, test R² 0.271 — classic overfitting. Ridge: test R² 0.893. Lasso: test R² 0.979, using only 10 of 100 features. But look closely: only 7 of the 10 real features were found. Lasso is a great filter, not a perfect one — always check the chosen features with domain knowledge.


Section 16

Common Mistakes — And How to Fix Them

⚠️
Mistake 1 — Not Scaling
Unfair penalty
Features with large units escape the penalty; features with small units get crushed. Always use StandardScaler first.
⚠️
Mistake 2 — Default α = 1.0
Guessing
The default α is rarely right. Use RidgeCV, LassoCV or ElasticNetCV to search for α with cross-validation.
⚠️
Mistake 3 — Leaky Scaling
Data leakage
Fitting the scaler on all data before splitting lets test rows leak in. Split first, or better, put the scaler inside a Pipeline.
🚫
Three More Mistakes

Mistake 4 — Penalising the intercept: β₀ should not be penalised. scikit-learn handles this for you (fit_intercept=True), but watch out if you write the maths yourself.
Mistake 5 — Comparing raw coefficients: coefficients are only comparable if the features were standardised. Otherwise a big coefficient may just mean a feature with small units.
Mistake 6 — Copying α between models: scikit-learn scales the Ridge and Lasso losses differently, so α = 1 means something very different in each.


Section 17

Golden Rules

🌲 Ridge & Lasso — Rules to Remember
1
Always scale features first. Use StandardScaler inside a Pipeline, so it learns only from training rows. Without scaling, the penalty depends on units, not on usefulness.
2
Never trust the default α. Search a log-spaced grid (for example 0.001 to 10,000 for Ridge) with RidgeCV or LassoCV. The best α changes a lot from dataset to dataset.
3
Correlated features? Prefer Ridge or ElasticNet. Lasso tends to keep one feature from a correlated group and drop the rest, and its choice can change with small changes in the data.
4
Need a short model? Use Lasso or ElasticNet. When most features are noise, removing them makes the model easier to explain and cheaper to run.
5
Give Lasso enough iterations. Lasso is solved step by step. If you see a ConvergenceWarning, raise max_iter (for example to 10,000).
6
Use a Pipeline to avoid leakage. Inside cross-validation, the scaler must be re-fitted on each training fold. A Pipeline does this automatically.
7
When in doubt, try ElasticNetCV. Give it several l1_ratio values between 0.1 and 1.0 and let cross-validation choose. It includes Lasso as a special case and is a safe default to compare against.