9 multiple choice at 2 marks and 3 fill-in-the-blank at 5, marked exactly as Round 1 marks them. Each answer is explained as soon as you check it. The clock is shown, not enforced.
CAIO tests Python in two quite different ways. Round 1 asks you to read code and say what it does, with no interpreter. Round 2 asks you to write a preprocessing pipeline in ninety minutes.
Round 1 questions frequently show a short snippet and ask for the output. The recurring traps:
a = [1, 2, 3]
b = a # a reference, not a copy
b.append(4)
print(a) # [1, 2, 3, 4]
def f(x, acc=[]): # mutable default, evaluated once at definition
acc.append(x)
return acc
print(f(1), f(2)) # [1, 2] [1, 2]
print([i*2 for i in range(3)]) # [0, 2, 4]
print(list(zip([1,2,3], "ab"))) # [(1,'a'), (2,'b')] - zip stops short
Integer division versus true division, list slicing with negative steps, and the difference between is and == round out the usual set. These are worth drilling because they are pure marks and take seconds once you know them.
Ninety minutes means you should not be inventing your preprocessing approach on the day. Have a sequence you run:
df.shape, df.dtypes
df.isna().sum()
df[target].value_counts(normalize=True) # imbalance check
df.describe().T
Then decide, in this order: what to do with missing values, how to encode categoricals, whether to scale, and whether the target is imbalanced.
The fast defaults that are defensible: median for numeric columns, most-frequent for categorical. Add a binary "was missing" indicator when the missingness might itself carry signal - a column missing only for a particular group is information, not noise.
from sklearn.impute import SimpleImputer
num = SimpleImputer(strategy="median", add_indicator=True)
cat = SimpleImputer(strategy="most_frequent")
One-hot for low-cardinality categoricals; ordinal encoding only when a real order exists. Scale for anything distance- or gradient-based - kNN, SVM, logistic regression, neural networks - and do not bother for trees.
Round 2 is scored partly on code quality. A ColumnTransformer inside a Pipeline is both the correct way to prevent leakage and visibly better code than a sequence of ad-hoc statements:
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
pre = ColumnTransformer([
("num", Pipeline([("imp", SimpleImputer(strategy="median")),
("sc", StandardScaler())]), num_cols),
("cat", Pipeline([("imp", SimpleImputer(strategy="most_frequent")),
("oh", OneHotEncoder(handle_unknown="ignore"))]), cat_cols),
])
model = Pipeline([("pre", pre), ("clf", HistGradientBoostingClassifier())])
handle_unknown="ignore" matters: a category present only in the test set would otherwise raise at prediction time and cost you the submission.
Drill Python output questions - mutable defaults and aliasing are the usual traps. Have a fixed exploration sequence so Round 2 does not start with improvisation.
Pipelines prevent leakage and earn code-quality marks at the same time. Set handle_unknown="ignore" so an unseen category cannot break your submission.
xs = [1, 2, 3]; ys = xs; ys.pop(); print(xs). What is printed?
ys = xs is an alias. pop mutates the one list, so xs is [1, 2].
Select an answer
A frame has two numeric columns, each with at least one missing cell. SimpleImputer(strategy="median", add_indicator=True) is fitted on them. How many extra columns does the indicator block add?
One binary column per input that had missingness during fit. Both columns did, so 2.
How many pairs does list(zip("abcd", [9, 8, 7])) contain? Run it if you want; the blank wants the count.
zip stops at the shortest input. The list has three items.