BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

ML-01-002-P · Primer — Features vs Labels: The Shape of Your Data

The idea in one sentence

Every supervised ML problem starts by splitting your data into two things: X, the measurements you get to see, and y, the answer you want to predict.

Why it exists

Before a model can learn anything, it needs your data in a shape it recognizes, and scikit-learn expects one very specific shape. Think of a spreadsheet: each row is one example (one customer, one fruit, one house), and each column is one measurable property of that example. That table of properties is the feature matrix X. Off to the side sits a single column of "right answers" — the price, the category, the yes/no — and that is the label vector y. Almost every confusing error a beginner hits ("expected 2D array", "inconsistent number of samples") comes from getting this shape wrong, so learning to see X and y clearly is the highest-leverage skill in your first week of ML.

How it works

The convention is simple and universal: X has shape (n_samples, n_features) — rows are samples, columns are features — and y has shape (n_samples,), one label per row, lined up in the same order. You never mix the answer into X; the whole point is that the model learns to map X to y so it can predict y for rows it has never seen. In scikit-learn, every estimator follows the same rhythm: model.fit(X, y) learns the pattern, and model.predict(X_new) applies it. Below, six fruits become six rows; each fruit has two features (weight and width), so X is (6, 2), and y is (6,) with 1 for apple and 0 for lemon. We fit a classifier, then ask it about a brand-new fruit. Notice how X stays 2D and y stays 1D — that alignment is the entire contract.

import numpy as np
from sklearn.linear_model import LogisticRegression

# Tiny dataset: 6 fruits. Columns = features [weight_g, width_cm].
# Each ROW is one sample (one fruit).
X = np.array([
    [150, 7.0],   # apple
    [170, 7.3],   # apple
    [140, 6.8],   # apple
    [120, 4.5],   # lemon
    [110, 4.2],   # lemon
    [130, 4.8],   # lemon
])
# Labels y: the answer we want to predict (1 = apple, 0 = lemon).
y = np.array([1, 1, 1, 0, 0, 0])

print(X.shape)   # (6, 2)  -> 6 samples, 2 features
print(y.shape)   # (6,)    -> 6 labels, one per row

model = LogisticRegression()
model.fit(X, y)

# Predict a new fruit: 160 g, 7.1 cm wide.
print(model.predict([[160, 7.1]]))   # [1]  -> apple
print(model.score(X, y))             # 1.0 -> perfect on this tiny training set

The one gotcha

scikit-learn wants X to be 2D and y to be 1D — always. If you pass a single sample as a flat list like model.predict([160, 7.1]), you'll get the infamous Expected 2D array, got 1D array error; wrap it as a list-of-lists, [[160, 7.1]], so it reads as "one row, two features." And if X has 6 rows but y has 5, sklearn raises "inconsistent numbers of samples" — every row in X must line up with exactly one label in y, in the same order.

See it — one row in, one prediction out

flowchart LR
  A["Raw data table<br/>rows = samples<br/>cols = features"] --> B["Split"]
  B --> C["X = features<br/>shape (n, features)"]
  B --> D["y = labels<br/>shape (n,)"]
  C --> E["model.fit(X, y)"]
  D --> E
  E --> F["model.predict<br/>(new row)"]
  F --> G["predicted label<br/>e.g. apple"]

The one-line takeaway

X is the 2D grid of what you measure (rows = samples, columns = features); y is the 1D column of what you predict — keep them aligned and 90% of shape errors vanish.

Where you'll meet it

Every single supervised model you ever build — spam filters, price predictors, churn detectors, medical classifiers — begins with this X/y split, and every scikit-learn tutorial, dataset loader (load_iris(return_X_y=True)), and train_test_split call assumes you already think in samples-by-features. Master this shape now and the rest of the AIM track (feature engineering, pipelines, deep learning tensors) is just this same idea, scaled up.

Named concepts: features (X), labels (y), rows as samples, columns as features