◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude Code
⟐ Claude Code/2026-05-03Advanced

Claude Code for Data Science — pandas, scikit-learn, and ML Workflows with AI Pair Programming

A hands-on guide to using Claude Code across the full data science and machine learning workflow. From EDA and feature engineering to model evaluation, hyperparameter tuning with Optuna, SHAP analysis, and MLOps basics — with working Python code throughout.

claude-code132data-sciencemachine-learningpython22pandasscikit-learnmlops

✦ Premium Article

Most data scientists I know use Claude Code for quick scripts and code completions. Far fewer have tried letting it drive the full pipeline — from raw data through EDA, feature engineering, model training, hyperparameter tuning, and into MLOps tooling.

I was in the first camp for a while. My data analysis lived in Jupyter notebooks, edited by hand, with Claude Code used occasionally for specific snippets. It took actually committing to the full workflow to realize how well-suited data science is for AI pair programming.

The reason is structural. Data science is fundamentally a loop: hypothesis → code → observe → revise. Claude Code excels at compressing that loop. The faster you can move from "I wonder if this feature helps" to "here's the validation curve showing it does," the better your work gets.

This article walks through the patterns that work in practice, with complete working code for each phase.

1. Project Setup: Using CLAUDE.md to Encode Data Science Constraints

Before writing a single line of analysis code, invest a few minutes in a CLAUDE.md that tells Claude Code how this project works. The payoff is substantial: Claude will automatically apply constraints you'd otherwise have to re-state in every prompt.

Installing the Stack

# Prompt to Claude Code:
"Create a requirements.txt for a data science project and install it
in a virtual environment. We need: pandas, numpy, scikit-learn,
matplotlib, seaborn, jupyterlab, optuna, shap, joblib"

Generated requirements.txt:

pandas==2.2.0
numpy==1.26.3
scikit-learn==1.4.0
matplotlib==3.8.2
seaborn==0.13.1
jupyterlab==4.1.0
optuna==3.5.0
shap==0.44.0
joblib==1.3.2
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -r requirements.txt

The CLAUDE.md That Changes Everything

The constraints below aren't obvious to Claude Code without being told. Once they're in CLAUDE.md, you stop having to re-state them:

# Data Science Project Guidelines
 
## Stack
- Python 3.12, pandas 2.2, scikit-learn 1.4, matplotlib/seaborn
- Data: data/raw/ (source), data/processed/ (transformed)
- Models: models/ (pkl via joblib)
 
## Rules — Always Apply
- Always .copy() DataFrames before mutation
- Use RANDOM_STATE = 42 everywhere, consistently
- Split data BEFORE fitting any preprocessor (prevents data leakage)
- Save models with joblib.dump, load with joblib.load
- For datasets >500k rows, use chunked reading

With this in place, Claude Code will include .copy() on every DataFrame operation, set seeds across every library, and structure preprocessing to avoid leakage — without being asked each time.

2. EDA: Getting Claude Code to Do the Boring Parts

Exploratory data analysis is where projects start, and where a lot of time gets lost to boilerplate. The goal here is to get to insights fast, not to hand-write histogram loops.

A Complete EDA Report Generator

# Prompt to Claude Code:
# "Write src/eda_report.py that performs basic EDA on data/raw/sales.csv:
# - dtypes, missing values, summary statistics for numeric columns
# - histograms and boxplots for numeric features
# - value counts (top 10) for categorical columns
# - correlation heatmap
# Save all figures to reports/eda/ as PNG files."

Generated script:

# src/eda_report.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
 
RANDOM_STATE = 42
REPORTS_DIR = Path("reports/eda")
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
 
def generate_eda_report(filepath: str) -> dict:
    """Run EDA and save figures. Returns summary dict."""
    df = pd.read_csv(filepath)
    report = {}
 
    report["shape"] = df.shape
    report["missing_pct"] = (df.isnull().sum() / len(df) * 100).to_dict()
 
    numeric_cols = df.select_dtypes(include=np.number).columns.tolist()
    report["stats"] = df[numeric_cols].describe().to_dict()
 
    # Correlation heatmap
    if len(numeric_cols) > 1:
        fig, ax = plt.subplots(figsize=(12, 10))
        sns.heatmap(
            df[numeric_cols].corr(),
            annot=True, fmt=".2f",
            cmap="coolwarm", center=0, ax=ax
        )
        ax.set_title("Feature Correlation Matrix")
        fig.savefig(REPORTS_DIR / "correlation_heatmap.png",
                    dpi=150, bbox_inches="tight")
        plt.close(fig)
        print("[OK] Correlation heatmap saved.")
 
    # Histograms
    n_cols = 3
    n_rows = (len(numeric_cols) + n_cols - 1) // n_cols
    fig, axes = plt.subplots(n_rows, n_cols, figsize=(15, 5 * n_rows))
    axes = axes.flatten()
 
    for i, col in enumerate(numeric_cols):
        axes[i].hist(df[col].dropna(), bins=30, edgecolor="black", alpha=0.7)
        axes[i].set_title(
            f"{col} (missing: {report['missing_pct'][col]:.1f}%)"
        )
        axes[i].set_xlabel(col)
        axes[i].set_ylabel("Count")
 
    for j in range(i + 1, len(axes)):
        axes[j].set_visible(False)
 
    plt.tight_layout()
    fig.savefig(REPORTS_DIR / "histograms.png", dpi=150, bbox_inches="tight")
    plt.close(fig)
    print(f"[OK] Histograms saved ({len(numeric_cols)} features)")
 
    return report
 
if __name__ == "__main__":
    report = generate_eda_report("data/raw/sales.csv")
    print(f"\nShape: {report['shape']}")
    print("\nMissing values (%):")
    for col, pct in report["missing_pct"].items():
        if pct > 0:
            print(f"  {col}: {pct:.1f}%")

Outlier Detection: Comparing Three Strategies

Rather than picking an outlier strategy upfront, generate a comparison script that shows all three options side by side:

# src/outlier_handling.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
 
def detect_outliers_iqr(series: pd.Series, multiplier: float = 1.5) -> pd.Series:
    """Return Boolean mask of IQR-based outliers."""
    Q1, Q3 = series.quantile(0.25), series.quantile(0.75)
    IQR = Q3 - Q1
    return (series < Q1 - multiplier * IQR) | (series > Q3 + multiplier * IQR)
 
def compare_outlier_strategies(
    df: pd.DataFrame, col: str, output_dir: str = "reports/eda"
) -> dict:
    """Compare drop / clip / log-transform and save a side-by-side plot."""
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    mask = detect_outliers_iqr(df[col])
    print(f"Outliers detected: {mask.sum()} / {len(df)} "
          f"({mask.sum()/len(df)*100:.1f}%)")
 
    Q1, Q3 = df[col].quantile(0.25), df[col].quantile(0.75)
    IQR = Q3 - Q1
    lower, upper = Q1 - 1.5 * IQR, Q3 + 1.5 * IQR
 
    df_drop = df[~mask].copy()
    df_clip = df.copy()
    df_clip[col] = df_clip[col].clip(lower=lower, upper=upper)
    df_log = None
    if df[col].min() > 0:
        df_log = df.copy()
        df_log[f"{col}_log"] = np.log1p(df_log[col])
 
    fig, axes = plt.subplots(1, 3, figsize=(18, 5))
    axes[0].hist(df_drop[col], bins=30, alpha=0.7, color="steelblue")
    axes[0].set_title(f"① Drop (n={len(df_drop)})")
    axes[1].hist(df_clip[col], bins=30, alpha=0.7, color="orange")
    axes[1].set_title(f"② Clip [{lower:.1f}, {upper:.1f}]")
    if df_log is not None:
        axes[2].hist(df_log[f"{col}_log"], bins=30, alpha=0.7, color="green")
        axes[2].set_title("③ Log Transform (log1p)")
    else:
        axes[2].text(0.5, 0.5, "Log transform unavailable\n(non-positive values)",
                     ha="center", va="center", transform=axes[2].transAxes)
    plt.suptitle(f"Outlier Strategy Comparison: '{col}'", fontsize=14)
    plt.tight_layout()
    fig.savefig(f"{output_dir}/outlier_comparison_{col}.png",
                dpi=150, bbox_inches="tight")
    plt.close(fig)
 
    return {"drop_n": len(df_drop), "lower": lower, "upper": upper}
✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦A reproducible layout that turns an exploratory notebook into six tested modules in ~25 minutes
✦Wiring data-leakage detection into CLAUDE.md — a real case that caught leakage in 3 of 11 features
✦A full production workflow: Optuna 100 trials (RMSE 0.0421->0.0388), pytest, and a single run script
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

⟐ Claude Code2026-08-28
A small script that catches invisible characters in plugin names before you install
Control characters and zero-width characters hiding in plugin or skill names are invisible in a terminal listing. I built six deliberately confusing names, compared what ls actually shows, and wrote a short check you can run before installing anything — plus an honest note on what that check misses.
⟐ Claude Code2026-08-26
I decide the prompt cache TTL by where I come back to, not how long I was away
Claude Code v2.1.242 added promptCacheTtl and subagentPromptCacheTtl. Whether the one-hour cache pays off depends on returning to the same directory and the same git state, not on how long the break was. Here is the order I check things in.
⟐ Claude Code2026-06-12
Untangling Android Back-Button Ad Gates: A Parallel, Priority-Ordered Redesign with Claude Code
Nested back-button ad gates fired at the wrong moments. The parallel, priority-ordered redesign we shipped in v2.1.0, with Claude Code, Kotlin, and tests.