Quant research interviews often end with an open modelling question. There is no single right answer; the interviewer is watching how you go from raw data to a model you would trust. Here is a full pass at a Two Sigma-style case.
Step 1: the main idea
Start simple and say it out loud: a multiple regression of sale price on square footage, beds, baths, year built and location. Everything after this is about making that regression valid and useful.
Step 2: encode each input
- Square footage and year built are integers, but with so many values they behave like continuous variables. Keep them as numbers (for now).
- Beds and baths are small ordered counts. They can go in as numbers too.
- Borough and neighbourhood are categories with no order. Use one-hot encoding: one 0/1 column per category, dropping one to avoid perfect collinearity with the intercept.
Step 3: normalise, and look at the shapes
Put features on comparable scales (subtract the mean, divide by the standard deviation). It avoids numerical trouble and makes coefficients comparable. But first look at each distribution.
Square footage is never negative, clusters around a typical one- or two-storey home, has a short left tail and a long right tail: some mansions are dozens of times the average. That shape is lognormal. Take the logarithm, then standardise. Sale price itself is usually lognormal too, and regressing makes coefficients read as percentage effects.
Step 4: correlations between inputs
Square footage, beds and baths obviously move together. Measure it first. Pearson correlation assumes linear relationships between continuous variables; with a discrete ordered variable (beds) against a continuous one (log size), Spearman’s rank correlation is the better tool.
Step 5: deal with multicollinearity
Correlated inputs inflate standard errors and make coefficients unstable (lesson 10). Options, from simplest:
- Ridge to shrink the correlated coefficients together, or lasso to keep the strongest of the group and drop the others (lesson 11).
- Successive orthogonalisation: regress the most important variable first, then put in the others’ residuals after removing it, so each coefficient means “extra information beyond the earlier ones”.
- Interpretation pass: compute t-statistics, F-test the weak group, and consider subset selection to drop what isn’t pulling its weight.
What a strong answer adds
- Validation: hold out recent sales and measure error on them, since the goal is predicting future prices.
- Location done properly: neighbourhood dummies are crude. Latitude and longitude with a smooth function, or nearby sale prices (a kernel average, like the next case), capture it better.
- Time: prices drift, so add a sale-date trend or month effects.
In code
import numpy as np
import pandas as pd
from sklearn.linear_model import RidgeCV
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(12)
n = 5000
sqft = np.exp(rng.normal(np.log(900), 0.55, n))
beds = np.clip(np.round(sqft / 450 + rng.normal(0, 0.7, n)), 0, 6)
baths = np.clip(np.round(beds * 0.7 + rng.normal(0, 0.5, n)), 1, 5)
boro = rng.choice(["Manhattan", "Brooklyn", "Queens", "Bronx", "Staten Island"], n)
prem = pd.Series(boro).map({"Manhattan": 0.6, "Brooklyn": 0.3, "Queens": 0.1, "Bronx": -0.1, "Staten Island": 0.0})
log_price = 5.3 + 0.9 * np.log(sqft) + 0.05 * baths + prem.values + rng.normal(0, 0.2, n)
X = pd.get_dummies(pd.DataFrame({"log_sqft": np.log(sqft), "beds": beds, "baths": baths, "boro": boro}),
columns=["boro"], drop_first=True, dtype=float)
X = (X - X.mean()) / X.std()
print(pd.DataFrame({"sqft": sqft, "beds": beds, "baths": baths}).corr(method="spearman").round(2))
Xtr, Xte, ytr, yte = train_test_split(X, log_price, random_state=0)
model = RidgeCV(alphas=np.logspace(-3, 3, 13)).fit(Xtr, ytr)
print("held-out R^2:", round(model.score(Xte, yte), 3))
Check yourself
You regress log(price) on log(square feet) and get a coefficient of 0.9. What does it mean?