Bird
Raised Fist0

Implement Train-Test Split with Stratification

CODINGbeginnerInterview freq: high
Choose your preparation mode6 modes available
🤖
Implement Train-Test Split with Stratification
Split data into train and test sets while preserving class distribution.
codingbeginnerFreq: highEvaluation Coding
Prerequisites:Data Splits - Train, Validation, Test
📝
Problem Statement

Implement a function to split a dataset into train and test sets with stratification. The split should preserve the class distribution in both sets.

Input:  X: np.ndarray, y: np.ndarray, test_size: float
Output: X_train: np.ndarray, X_test: np.ndarray, y_train: np.ndarray, y_test: np.ndarray
{
  "input": "X = np.array([[1], [2], [3], [4], [5], [6]]), y = np.array([0, 0, 1, 1, 2, 2]), test_size = 0.33",
  "expected_output": "X_train = np.array([[1], [3], [5], [6]]), X_test = np.array([[2], [4]]), y_train = np.array([0, 1, 2, 2]), y_test = np.array([0, 1])"
}
🎯
Interview Angles
  • Testing understanding of stratification and its importance in model evaluation.
  • Evaluating ability to manipulate arrays and indices using NumPy.
  • Assessing problem-solving skills in ensuring class distribution preservation.
  • Checking knowledge of data preprocessing techniques in machine learning.
🔵
Naive Approach

Randomly shuffle the data and split it into train and test sets. Then, manually adjust the splits to ensure class distribution is preserved.

O(n) time and O(n) space - n elements shuffled and split once.
Why Insufficient
Does not guarantee class distribution preservation - random shuffle can lead to imbalanced splits.
🐍
Naive Code
Python
import numpy as np

def naive_train_test_split(X, y, test_size=0.2):
    n_samples = len(y)
    indices = np.arange(n_samples)
    np.random.shuffle(indices)
    split_idx = int(n_samples * (1 - test_size))
    X_train, X_test = X[indices[:split_idx]], X[indices[split_idx:]]
    y_train, y_test = y[indices[:split_idx]], y[indices[split_idx:]]
    return X_train, X_test, y_train, y_test

# Driver example
X = np.array([[1], [2], [3], [4], [5], [6]])
y = np.array([0, 0, 1, 1, 2, 2])
X_train, X_test, y_train, y_test = naive_train_test_split(X, y, test_size=0.33)
print(X_train, X_test, y_train, y_test)
💡
Key Insight
Group data by class, shuffle each group, and split each group according to the test size. This ensures class distribution is preserved in both train and test sets.
🐍
Optimized Code
Python
import numpy as np

def stratified_train_test_split(X, y, test_size=0.2):
    if len(y) == 0:
        return np.array([]), np.array([]), np.array([]), np.array([])
    unique_classes, y_indices = np.unique(y, return_inverse=True)
    train_indices = []
    test_indices = []
    for class_index in range(len(unique_classes)):
        class_member_indices = np.where(y_indices == class_index)[0]
        np.random.shuffle(class_member_indices)
        split_idx = int(len(class_member_indices) * (1 - test_size))
        train_indices.extend(class_member_indices[:split_idx])
        test_indices.extend(class_member_indices[split_idx:])
    train_indices = np.array(train_indices)
    test_indices = np.array(test_indices)
    return X[train_indices], X[test_indices], y[train_indices], y[test_indices]

# Driver example
X = np.array([[1], [2], [3], [4], [5], [6]])
y = np.array([0, 0, 1, 1, 2, 2])
X_train, X_test, y_train, y_test = stratified_train_test_split(X, y, test_size=0.33)
print(X_train, X_test, y_train, y_test)
🔍
Line-by-Line
unique_classes, y_indices = np.unique(y, return_inverse=True)
Identify unique classes and map each sample to its class index.
train_indices = []
Initialize list to collect indices for training set.
test_indices = []
Initialize list to collect indices for test set.
for class_index in range(len(unique_classes))
Iterate over each class to handle them separately.
class_member_indices = np.where(y_indices == class_index)[0]
Find indices of samples belonging to the current class.
np.random.shuffle(class_member_indices)
Shuffle indices within the class to ensure randomness.
split_idx = int(len(class_member_indices) * (1 - test_size))
Determine split point based on test size.
train_indices.extend(class_member_indices[:split_idx])
Add training indices for the current class.
test_indices.extend(class_member_indices[split_idx:])
Add test indices for the current class.
⚡
Complexity
O(n) time and O(n) space - n elements processed once, with constant-time operations per element.
X_train = [[1], [3], [5], [6]], X_test = [[2], [4]], y_train = [0, 1, 2, 2], y_test = [0, 1]
🔲
Edge Cases
Single class
All samples belong to one class, so the split is straightforward.
unique_classes, y_indices = np.unique(y, return_inverse=True)
Empty dataset
Returns empty train and test sets.
if len(y) == 0: return np.array([]), np.array([]), np.array([]), np.array([])
⚠️
Common Mistakes
Symptom: Leads to non-random splits, potentially biasing the model.
Symptom: Results in incorrect train-test size ratio.
Symptom: Causes runtime errors or incorrect outputs.
Symptom: Leads to incorrect data splits and evaluation results.

Practice

(1/5)
1.
import numpy as np
from scipy.spatial import distance

X_train = np.array([[1, 2], [3, 4], [5, 6]])
X_test = np.array([[1, 2]])

# Calculate distances
D = distance.cdist(X_test, X_train, 'euclidean')

print(D)
What does the above code output?
easy
A. array([[0., 2.828, 5.656]])
B. array([[0., 2., 4.]])
C. array([[0., 2.236, 4.472]])
D. array([[0., 2.828, 4.472]])

Solution

  1. Step 1: Understand the function

    The cdist function computes the distance between each pair of the two collections of inputs.
  2. Step 2: Calculate the distances

    The Euclidean distance from [1, 2] to each point in X_train is calculated as [0., 2.828, 4.472].
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Euclidean distance formula: √((x2-x1)² + (y2-y1)²) [OK]
Hint: Euclidean distance formula: √((x2-x1)² + (y2-y1)²) [OK]
Common Mistakes:
2. You implemented a 2D convolution but the output is all zeros. What is the most likely cause?
medium
A. The kernel is not normalized.
B. The kernel is filled with zeros.
C. The image is padded incorrectly.
D. The stride is set too high.

Solution

  1. Step 1: Identify the zero output cause

    If the kernel is filled with zeros, every convolution operation will result in zero, regardless of the image content.
  2. Step 2: Eliminate wrong options

    The kernel is not normalized. affects scaling, not zero output. The image is padded incorrectly. affects boundary handling, not zero output. The stride is set too high. affects output size, not zero output.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Zero kernel leads to zero output. [OK]
Hint: Zero kernel leads to zero output. [OK]
Common Mistakes:
3. In a multi-class classification problem, how should you modify the confusion matrix implementation to handle more than two classes?
medium
A. Use a 2x2 matrix and sum over classes.
B. Use a 3D matrix to capture all interactions.
C. Use separate matrices for each class.
D. Use a matrix of size (n_classes, n_classes).

Solution

  1. Step 1: Understand multi-class needs

    Each class needs its own row and column in the matrix.
  2. Step 2: Choose correct matrix size

    A (n_classes, n_classes) matrix captures all class interactions.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Multi-class needs (n_classes, n_classes) matrix. [OK]
Hint: Multi-class needs (n_classes, n_classes) matrix. [OK]
Common Mistakes:
4. In the context of logistic regression, which approach is correct for handling a dataset with highly imbalanced classes?
medium
A. Use accuracy as the primary evaluation metric.
B. Apply SMOTE to balance the dataset.
C. Increase the learning rate to improve minority class prediction.
D. Use a linear activation function instead of sigmoid.

Solution

  1. Step 1: Identify the problem with imbalanced classes

    Imbalanced classes can lead to biased predictions towards the majority class.
  2. Step 2: Choose a method to address imbalance

    SMOTE (Synthetic Minority Over-sampling Technique) is effective for balancing datasets.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    SMOTE balances classes effectively [OK]
Hint: SMOTE balances classes effectively [OK]
Common Mistakes:
5. How can you optimize a decision tree implementation to handle large datasets more efficiently?
hard
A. Implement parallel processing for tree construction.
B. Use a single-threaded approach to avoid overhead.
C. Increase the maximum depth of the tree.
D. Use a more complex splitting criterion.

Solution

  1. Step 1: Consider parallel processing

    Parallel processing can significantly speed up tree construction on large datasets.
  2. Step 2: Eliminate wrong options

    Complex criteria (A) slow down; increasing depth (C) can overfit; single-threaded (D) is less efficient.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Large data? Use parallel processing [OK]
Hint: Large data? Use parallel processing [OK]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard