List Comprehensions
# squares of even numbers
sq = [x**2 for x in range(10) if x % 2 == 0]
# dict comprehension
label_map = {k: i for i, k in enumerate(classes)}
Lambda & Functional
normalize = lambda x: (x - x.min()) / (x.max() - x.min())
result = list(map(lambda x: x**2, data))
filtered = list(filter(lambda x: x > 0, data))
Classes for ML
class LinearModel:
def __init__(self, lr=0.01):
self.lr = lr
self.w = None
def fit(self, X, y):
self.w = np.zeros(X.shape[1])
for _ in range(1000):
pred = X @ self.w
grad = X.T @ (pred - y) / len(y)
self.w -= self.lr * grad
def predict(self, X):
return X @ self.w
Best Practice: Always write ML models as classes with fit() and predict() methods — this follows scikit-learn's API and makes your code composable in pipelines.
NumPy: Arrays & Broadcasting
Essential Operations
import numpy as np
# Creation
a = np.array([1,2,3])
X = np.zeros((100,10)) # 100×10 matrix of zeros
X = np.random.randn(100,10) # standard normal init
I = np.eye(5) # 5×5 identity matrix
# Shape manipulation
X.reshape(-1, 1) # column vector
X.T # transpose
np.concatenate([A, B], axis=1) # horizontal stack
np.vstack([A, B]) # vertical stack
# Math ops (vectorized, no for-loops!)
dot = A @ B # matrix multiply
norm = np.linalg.norm(v) # L2 norm
eig = np.linalg.eig(A) # eigendecomposition
inv = np.linalg.inv(A) # matrix inverse
# Broadcasting example
X = np.random.randn(1000, 10) # 1000 samples, 10 features
mu = X.mean(axis=0) # shape (10,)
X_centered = X - mu # broadcasts across 1000 rows!
Indexing & Slicing
X[0] # first row
X[:, 2] # third column
X[5:10, :3] # rows 5-9, cols 0-2
X[mask] # boolean indexing
X[[0,3,7]] # fancy indexing
Performance Tips
- Avoid Python for-loops — use vectorized ops
np.dot calls BLAS (10-100× faster)
- Use
float32 for GPU: saves memory
np.einsum for complex tensor ops
- Profile with
%timeit in Jupyter
import pandas as pd
df = pd.read_csv('data.csv')
# Exploration
df.head(); df.info(); df.describe()
df.isnull().sum() # missing values per column
df.value_counts('label') # class distribution
# Filtering & Selection
df[df['age'] > 25] # boolean filter
df[['name', 'salary']] # select columns
df.loc[0:5, 'age':'salary'] # label-based
df.iloc[0:5, 2:5] # position-based
# Cleaning
df.dropna(subset=['salary']) # drop rows with NaN in salary
df['age'].fillna(df['age'].median(), inplace=True)
df.drop_duplicates(inplace=True)
# Feature Engineering
df['age_bucket'] = pd.cut(df['age'], bins=[0,18,35,60,100])
df['log_salary'] = np.log1p(df['salary'])
# GroupBy (most common in interviews!)
agg = df.groupby('dept').agg({'salary': ['mean','std','count']})
pivot = df.pivot_table(values='revenue', index='region', columns='quarter', aggfunc='sum')
# Merging
merged = pd.merge(df1, df2, on='user_id', how='left')
Interview Pattern: Most data science interviews test GroupBy + Merge. Practice: "find the department with highest average salary for employees hired after 2020."
Visualization: Matplotlib & Seaborn
import matplotlib.pyplot as plt
import seaborn as sns
# Training loss curve
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].plot(train_losses, label='Train', color='#0d6efd')
axes[0].plot(val_losses, label='Val', color='#dc3545', linestyle='--')
axes[0].set_title('Loss Curve'); axes[0].legend()
# Confusion matrix
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=axes[1])
axes[1].set_title('Confusion Matrix')
plt.tight_layout()
plt.savefig('results.png', dpi=150, bbox_inches='tight')
# Distribution analysis
sns.histplot(df['salary'], kde=True, bins=30)
sns.boxplot(x='dept', y='salary', data=df)
sns.heatmap(df.corr(), annot=True, cmap='coolwarm')
Scikit-learn: Pipelines & APIs
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score, GridSearchCV
from sklearn.metrics import classification_report, roc_auc_score
# Build preprocessing pipeline
num_pipe = Pipeline([('scaler', StandardScaler())])
cat_pipe = Pipeline([('ohe', OneHotEncoder(handle_unknown='ignore'))])
preprocessor = ColumnTransformer([
('num', num_pipe, num_features),
('cat', cat_pipe, cat_features)
])
# Full pipeline with model
pipe = Pipeline([
('prep', preprocessor),
('clf', RandomForestClassifier(n_estimators=200, random_state=42))
])
# Cross-validation
scores = cross_val_score(pipe, X_train, y_train, cv=5, scoring='roc_auc')
print(f"AUC: {scores.mean():.3f} ± {scores.std():.3f}")
# Hyperparameter tuning
params = {'clf__n_estimators': [100,200], 'clf__max_depth': [None,10,20]}
gs = GridSearchCV(pipe, params, cv=5, scoring='roc_auc', n_jobs=-1)
gs.fit(X_train, y_train)
print(gs.best_params_)
# Evaluation
y_pred = gs.predict(X_test)
print(classification_report(y_test, y_pred))
print(f"Test AUC: {roc_auc_score(y_test, gs.predict_proba(X_test)[:,1]):.3f}")
Always use Pipelines — they prevent data leakage (fitting the scaler on test data), make deployment easy (one pipe.predict() call), and let you tune preprocessing hyperparameters alongside model hyperparameters.