Marcona Plant — defect classification
The plant logs one row per production batch: twenty columns of process settings and
sensor readings. Quality wants the defect type predicted for every batch in
data/holdout.csv so the rework cell can be staffed.
Time: 90 minutes. Write everything in solution.py.
Four classes:
none(≈62%),scratch(≈18%),void(≈13%),warp(≈7%). Calling everythingnoneis 62% accurate.warpis the one that costs money.
Files
| File | What it is |
|---|---|
data/batches.csv |
9 000-odd batches with defect_type. |
data/holdout.csv |
8 000-odd batches to classify. No defect_type. |
Columns
| Column | Notes |
|---|---|
batch_id |
Batch identifier. Not unique in the file — see rule 1. |
machine_id |
M-1 … M-6. |
shift |
A, B, C. |
temperature |
Thermocouple reading. The unit is whatever temp_unit says — see rule 2. |
temp_unit |
C or F, per row. |
pressure |
kPa. Some rows are blank. |
line_speed |
Metres per second. |
humidity |
Relative humidity, %. |
sensor_01 … sensor_12 |
Process sensors, already standardised. |
calibration_flag |
Written by the logger. |
defect_type |
Target. batches.csv only. |
Known defects, stated as rules
- Re-measured batches. When a batch is measured twice the logger appends the second
pass, so some
batch_idvalues appear twice with slightly different sensor readings. The rows are not identical, so de-duplicating on every column removes nothing. - Mixed temperature units. Roughly a third of the rows are Fahrenheit;
temp_unitsays which. Celsius = (F − 32) × 5/9. - Dead weight in the table. Some columns never vary, and one column is another column rescaled — an exact linear function of it, correlation 1.0.
Tasks
T1 — useless_columns(df) -> list[str] (15 pts)
Return the columns that carry no information: every column that never varies, plus, for each pair of numeric columns whose absolute correlation is essentially 1, the one that appears later in the column order. Do not include the id or the target.
T2 — deduplicate(df) -> pd.DataFrame (10 pts)
Return a frame with exactly one row per batch_id.
T3 — normalise_units(df) -> pd.DataFrame (15 pts)
Return the frame with every temperature in Celsius. Convert only the rows
temp_unit marks as F. No rows dropped.
T4 — class_counts(df) -> dict (10 pts)
Return {defect_type: number of batches} — one row per batch, so rule 1 has to be
dealt with first.
T5 — stratified_split(df, valid_share) -> (train, valid) (10 pts)
Split into train and validation so that valid is valid_share of the rows and the
class mix of valid matches the whole frame to within 0.02 per class.
Every row goes to exactly one side.
T6 — fit_predict(train, holdout) -> np.ndarray (40 pts)
Both frames arrive raw. Return a class label per holdout row — one of none,
scratch, void, warp — in file order. Not probabilities.
Graded on macro F1 (every class weighted equally, so the 7% class counts as much as
the 62% one): >= 0.300 beats calling everything none, >= 0.430 is competent,
>= 0.510 means the rare class is genuinely being found. Measured across 21 daily
seeds, balancing the class weights is worth about 0.03 of that on its own.
Allowed: pandas, numpy, scikit-learn.