Chapter 12 Interactive Notebooks: Professional Data Science Workflows
12.1 Why Professional Notebooks Matter
Notebooks can be: - ❌ Bad: Messy, unexplainable, breaks every run - ✅ Professional: Reproducible, documented, production-quality
This chapter shows you how to build the professional kind.
12.2 The 3-Section Notebook Structure
Every professional notebook has this structure:
SECTION 1: SETUP
- Imports
- Configuration
- Data loading
SECTION 2: ANALYSIS
- Exploration
- Transformation
- Modeling
SECTION 3: OUTPUTS
- Results
- Visualizations
- Save/export
12.2.1 Section 1: Setup
Purpose: Everything needed to run the notebook
# SECTION 1: SETUP
# ====================================
# 1.1 Imports
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
from sklearn.dummy import DummyClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import balanced_accuracy_score, classification_report
from sklearn.model_selection import train_test_split
# 1.2 Configuration
CONFIG = {
'random_state': 42,
'test_size': 0.2,
'figsize': (12, 8),
'data_path': Path('../data/customers.csv'),
'output_dir': Path('../output')
}
# 1.3 Load Data
df_raw = pd.read_csv(CONFIG['data_path'])
# 1.4 Quick sanity check
print(f"Data shape: {df_raw.shape}")
print(f"Missing values:\n{df_raw.isnull().sum()}")Key Principle: Someone should be able to run Section 1 and have everything working.
12.2.2 Section 2: Analysis
Purpose: Your actual work (exploration, modeling, analysis)
# SECTION 2: ANALYSIS
# ====================================
# 2.1 Data Exploration
print("=" * 50)
print("EXPLORATORY DATA ANALYSIS")
print("=" * 50)
# Describe the data
print("\nNumerical Summary:")
print(df_raw.describe())
# Check distributions
df_raw.hist(figsize=CONFIG['figsize'])
plt.tight_layout()
plt.show()
# 2.2 Data Preparation
print("\n" + "=" * 50)
print("DATA PREPARATION")
print("=" * 50)
# Feature engineering
df_model = df_raw.copy()
source_required = {
'customer_id', 'days_since_purchase',
'transactions_last_90_days', 'purchased_flag'
}
missing_columns = source_required.difference(df_model.columns)
if missing_columns:
raise ValueError(f"Missing required columns: {sorted(missing_columns)}")
# These example features must be measured before the prediction outcome window.
df_model['recency'] = df_model['days_since_purchase'].astype(float)
df_model['frequency'] = df_model['transactions_last_90_days'].astype(float)
# Handle missing values
required_columns = ['customer_id', 'recency', 'frequency', 'purchased_flag']
df_model = df_model[required_columns].dropna().copy()
# 2.3 Modeling
print("\n" + "=" * 50)
print("MODEL TRAINING")
print("=" * 50)
# Prepare features and target
X = df_model[['recency', 'frequency']]
y = df_model['purchased_flag']
# Split before fitting any learned preprocessing or model.
# Use a temporal or grouped split instead when random rows would leak information.
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=CONFIG['test_size'],
random_state=CONFIG['random_state'],
stratify=y
)
# Establish a simple benchmark.
baseline = DummyClassifier(strategy='most_frequent')
baseline.fit(X_train, y_train)
baseline_predictions = baseline.predict(X_test)
baseline_score = balanced_accuracy_score(y_test, baseline_predictions)
# Train model
model = RandomForestClassifier(
n_estimators=100,
random_state=CONFIG['random_state']
)
model.fit(X_train, y_train)
test_predictions = model.predict(X_test)
test_score = balanced_accuracy_score(y_test, test_predictions)
print("\nModel trained and evaluated on held-out data.")
print(f"Baseline balanced accuracy: {baseline_score:.3f}")
print(f"Model balanced accuracy: {test_score:.3f}")
print(classification_report(y_test, test_predictions, zero_division=0))12.2.3 Section 3: Outputs
Purpose: Results, visualizations, saved files
# SECTION 3: OUTPUTS
# ====================================
# 3.1 Results Summary
print("=" * 50)
print("RESULTS SUMMARY")
print("=" * 50)
results = {
'rows_processed': len(df_model),
'features_used': len(X.columns),
'baseline_balanced_accuracy': baseline_score,
'test_balanced_accuracy': test_score,
'feature_importance': pd.DataFrame({
'feature': X.columns,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
}
print(f"\nRows processed: {results['rows_processed']}")
print(f"Baseline balanced accuracy: {results['baseline_balanced_accuracy']:.3f}")
print(f"Test balanced accuracy: {results['test_balanced_accuracy']:.3f}")
print(f"\nTop features:\n{results['feature_importance']}")
# 3.2 Visualizations
fig, axes = plt.subplots(1, 2, figsize=CONFIG['figsize'])
# Feature importance
results['feature_importance'].plot(
x='feature', y='importance', kind='barh', ax=axes[0]
)
axes[0].set_title('Feature Importance')
# Model performance
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, test_predictions)
axes[1].imshow(cm, cmap='Blues')
axes[1].set_title('Confusion Matrix')
plt.tight_layout()
plt.show()
# 3.3 Save Results
output_results = pd.DataFrame({
'customer_id': df_model.loc[X_test.index, 'customer_id'],
'actual_purchase': y_test,
'predicted_purchase': test_predictions,
'confidence': model.predict_proba(X_test).max(axis=1)
})
CONFIG['output_dir'].mkdir(parents=True, exist_ok=True)
output_path = CONFIG['output_dir'] / 'test_predictions.csv'
output_results.to_csv(output_path, index=False)
print(f"\nHeld-out predictions saved to '{output_path}'")12.3 Best Practices for Each Section
12.3.1 Section 1: Setup Best Practices
✅ DO:
- ✓ Import only what you need
- ✓ Use CONFIG dict for paths, parameters
- ✓ Set random seeds for reproducibility
- ✓ Add quick sanity checks
- ✓ Document data sources
- ✓ Record package versions and a data snapshot or version identifier
❌ DON’T: - ✗ Install packages in notebooks (do it separately) - ✗ Hardcode paths (use CONFIG) - ✗ Scatter imports throughout - ✗ Load data multiple times
12.3.2 Section 2: Analysis Best Practices
✅ DO: - ✓ Add section headers - ✓ Print progress (“Starting model training…”) - ✓ Show intermediate results - ✓ Use meaningful variable names - ✓ Add markdown cells explaining what you’re doing - ✓ Split data before fitting preprocessing or models - ✓ Compare against a meaningful baseline - ✓ Match the split strategy and metrics to the decision problem
❌ DON’T: - ✗ Hide long-running cells without progress, caching, or documentation - ✗ Modify input dataframes (use copies) - ✗ Use magic numbers (put in CONFIG) - ✗ Skip validation checks - ✗ Report training-set performance as evidence of generalization - ✗ Fit transformations on the complete dataset before splitting
12.3.3 Section 3: Outputs Best Practices
✅ DO: - ✓ Print summary statistics - ✓ Create visualizations - ✓ Save results to files - ✓ Document what was saved and where - ✓ Include metrics, uncertainty, and relevant subgroup diagnostics
❌ DON’T: - ✗ Leave outputs scattered in cells - ✗ Save without documenting - ✗ Create messy visualizations - ✗ Forget to save key results
12.4 Debugging Notebook Issues
12.4.1 Issue: Notebook “works” first run, then fails
Cause: Cells run out of order, or state isn’t clean
Fix:
1. Kernel → Restart Kernel
2. Run All (Ctrl+Shift+Enter)
3. If still fails, there’s an order dependency
Prevention: - Design for top-to-bottom execution - Clear Section 1 handles all setup - Don’t rely on cell order
12.5 Asking Claude About Notebooks
I'm building a data science notebook for [task].
Current structure:
SECTION 1: [what's in setup]
SECTION 2: [what's in analysis]
SECTION 3: [what's in outputs]
Requirements:
- [feature 1]
- [feature 2]
- [must be reproducible]
Can you:
1. Review the structure
2. Suggest improvements
3. Show example cells
Keep it professional and well-commented.
12.6 Exercise: Restructure an Existing Notebook
Take one of your notebooks:
- Identify the sections (setup, analysis, outputs)
- Reorganize by section (even if messy)
- Add CONFIG dict with all paths and parameters
- Add section headers (markdown cells)
- Test: Restart kernel, run all, verify it works
Compare before/after. Notice how much more organized it is!
12.7 Performance Tips
12.8 Key Takeaways
✅ Use 3-section structure (Setup, Analysis, Output)
✅ Make Section 1 complete and standalone
✅ Run all from top every time
✅ Set random seeds and record environment versions for reproducibility
✅ Prevent leakage by splitting before learned preprocessing
✅ Compare held-out performance with a baseline
✅ Add progress messages and headers
✅ Save and document outputs
✅ Test with “Restart & Run All”