Tested demoQuality 97/100

terrylica/cc-skills/plugins/devops-tools/skills/ml-data-pipeline-architecture/SKILL.md

ml-data-pipeline-architecture

Patterns for efficient ML data pipelines using Polars, Arrow, and ClickHouse. TRIGGERS - data pipeline, polars vs pandas, arrow format

Source repository stars
62
Declared platforms
0
Static risk flags
0
Last source update
2026-08-24
Source checked
2026-08-25

Decision brief

What it does: where it fits

Patterns for efficient ML data pipelines using Polars, Arrow, and ClickHouse.

Best for

  • Deciding between Polars and Pandas for a data pipeline
  • Optimizing memory usage with zero-copy Arrow patterns
  • Loading data from ClickHouse into PyTorch DataLoaders

Not for

  • DON'T: Mix APIs Unnecessarily
  • DON'T: Collect Too Early
Controlled single-run demoChecked 2026-08-20

What changed when the Skill was used

In this controlled same-task single run, enabling ml-data-pipeline-architecture changed the output from 2320 non-whitespace characters and 20 headings to 2322 characters and 21 headings. Matches among 8 signals extracted from the pinned source changed from 3 to 2. Both actual outputs are shown; this is a structural observation, not a quality score or a universal performance claim.

Same test task

Analyze a small SaaS churn scenario and produce a concrete analysis plan with data checks, method choices, expected outputs, and validation steps. The deliverable must specifically reflect this user intent: Patterns for efficient ML data pipelines using Polars, Arrow, and ClickHouse. TRIGGERS - data pipeline, polars vs pandas, arrow format

Without the Skill
Screenshot of the actual model output for ml-data-pipeline-architecture without the Skill

Baseline: 2320 non-whitespace characters, 20 headings, and 55 list items.

With the Skill
Screenshot of the actual model output for ml-data-pipeline-architecture with the Skill

With Skill: 2322 non-whitespace characters, 21 headings, and 60 list items.

ObservationWithout SkillWith Skill
Source-signal coverage3/8: pipeline, polars, pandas2/8: polars, pandas
Output structure2320 chars · 20 headings · 55 list items · 0 code blocks2322 chars · 21 headings · 60 list items · 0 code blocks
Verification and caution signals16 verification signals · 7 risk/limitation signals20 verification signals · 8 risk/limitation signals

A prompt you can use

Use the ml-data-pipeline-architecture Skill pinned at 2c1efdaeb5ce for my task. Follow its source-specific constraints around `ml-data-pipeline-architecture`, `pipeline`, `architecture`, `decision`, then return the finished deliverable with explicit assumptions, verification, failure conditions, and limits. Do not treat the Skill text as a factual source or claim that a single demonstration proves universal performance.

Method and limitationsExpand

Test method

  • Baseline and treatment used the same task, model (gpt-5.3-codex-low), and runner; the only planned difference was whether the complete target Skill text was injected.
  • The treatment used snapshot 265421d2dad27dc034dc395a973e4c0f1acd8762; the current source commit 2c1efdaeb5ce75c47eece5b8c5fcb19b5d21538c was verified against content hash 36041664df56. The baseline explicitly prohibited loading any Skill or external rule file.
  • The same deterministic script counted characters, headings, lists, code blocks, verification terms, caution terms, and source signals in both artifacts. Source signals: `ml-data-pipeline-architecture`, `pipeline`, `architecture`, `decision`, `polars`, `pandas`, `zero-copy`, `problem`.
  • The visuals are local screenshots of the actual Markdown artifacts in a fixed 1200 × 800 evidence canvas, not recreated product mockups. Raw JSON artifacts and request records are retained in the research directory.

Do not over-read this demo

  • This is one controlled demonstration per condition, not a multi-run statistical benchmark; the model is stochastic.
  • Character, structure, and keyword counts show observable differences but cannot by themselves prove correctness, originality, or business impact.
  • The task is a representative test designed for repeatability, not every real-world use of the Skill; rerun after a material source change.
Editorial review
SkillSignal editorial
Runner
Cursor Agent 2026.08.04-aaa8809
Model
gpt-5.3-codex-low
Refresh due
2026-11-18
Reviewed commit
2c1efdaeb5ce75c47eece5b8c5fcb19b5d21538c
Test snapshot
265421d2dad27dc034dc395a973e4c0f1acd8762

Compatibility matrix

Platform support, with evidence labels

PlatformStatusEvidenceWhat to check
CodexNot declaredNo explicit evidencePortability before use
Claude CodeNot declaredNo explicit evidencePortability before use
CursorNot declaredNo explicit evidencePortability before use
Gemini CLINot declaredNo explicit evidencePortability before use
Open the compatibility checker

Installation

Inspect first. Install second.

The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

Source-detected install commandSource
npx skills add https://github.com/terrylica/cc-skills --skill "plugins/devops-tools/skills/ml-data-pipeline-architecture"
Safe inspection promptEditorial

Inspect the Agent Skill "ml-data-pipeline-architecture" from https://github.com/terrylica/cc-skills/blob/a5f847b22ee5afa35677e446973a903d098cd1d4/plugins/devops-tools/skills/ml-data-pipeline-architecture/SKILL.md at commit a5f847b22ee5afa35677e446973a903d098cd1d4. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

Workflow

What the source asks the agent to do

  1. 01

    Usage

    df = queryarrow(client, "SELECT FROM bars WHERE ts = '2024-01-01'") python

    df = queryarrow(client, "SELECT FROM bars WHERE ts = '2024-01-01'") python
  2. 02

    Process file in chunks (never loads full file)

    def processlargefile(path: str, chunksize: int = 100000): reader = pl.scanparquet(path)

    def processlargefile(path: str, chunksize: int = 100000): reader = pl.scanparquet(path)for batch in reader.iterbatches(nrows=chunksize): Process each chunk features = computefeatures(batch) yield features.tonumpy() python from pydantic import BaseModel, fieldvalidatorclass FeatureConfig(BaseModel): features: list[str] target: str seqlen: int = 15
  3. 03

    Phase 1: Add Arrow Support

    [ ] Add polars = "" to dependencies (see PyPI)

    [ ] Add polars = "" to dependencies (see PyPI)[ ] Implement queryarrow() in data client[ ] Verify zero-copy with memory profiler
  4. 04

    Phase 2: Polars at Entry Points

    [ ] Add pl.frompandas() wrapper at trainer entry

    [ ] Add pl.frompandas() wrapper at trainer entry[ ] Update preparesequences() to accept both types[ ] Add schema validation after conversion
  5. 05

    Phase 3: Full Lazy Evaluation

    [ ] Convert file reads to pl.scan

    [ ] Convert file reads to pl.scan[ ] Compose transformations lazily[ ] Call .collect() only before .tonumpy()

Permission review

Static risk signals and limitations

No configured static risk pattern was detected

This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

Evidence record

Why each signal appears

EvidenceSourceComputedTestedEditorial
SignalValueEvidence typeMeaning
Quality score97/100ComputedDocumentation, specificity, maintenance, and trust rules
Repository stars62SourceRepository attention, not individual Skill quality
Compatibility0 platformsSourceDeclared in the catalog source record
Usage guidetested outcome pageTestedGenerated or reviewed according to the visible evidence level

Pinned source

Provenance and original SKILL.md

Repository
terrylica/cc-skills
Skill path
plugins/devops-tools/skills/ml-data-pipeline-architecture/SKILL.md
Commit
a5f847b22ee5afa35677e446973a903d098cd1d4
License
MIT
Collected
2026-08-25
Default branch
main
View the original SKILL.md

ML Data Pipeline Architecture

Patterns for efficient ML data pipelines using Polars, Arrow, and ClickHouse.

ADR: 2026-01-22-polars-preference-hook (efficiency preferences framework)

Note: A PreToolUse hook enforces Polars preference. To use Pandas, add # polars-exception: <reason> at file top.

Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.

When to Use This Skill

Use this skill when:

  • Deciding between Polars and Pandas for a data pipeline
  • Optimizing memory usage with zero-copy Arrow patterns
  • Loading data from ClickHouse into PyTorch DataLoaders
  • Implementing lazy evaluation for large datasets
  • Migrating existing Pandas code to Polars

1. Decision Tree: Polars vs Pandas

Dataset size?
├─ < 1M rows → Pandas OK (simpler API, richer ecosystem)
├─ 1M-10M rows → Consider Polars (2-5x faster, less memory)
└─ > 10M rows → Use Polars (required for memory efficiency)

Operations?
├─ Simple transforms → Either works
├─ Group-by aggregations → Polars 5-10x faster
├─ Complex joins → Polars with lazy evaluation
└─ Streaming/chunked → Polars scan_* functions

Integration?
├─ scikit-learn heavy → Pandas (better interop)
├─ PyTorch/custom → Polars + Arrow (zero-copy to tensor)
└─ ClickHouse source → Arrow stream → Polars (optimal)

2. Zero-Copy Pipeline Architecture

The Problem with Pandas

# BAD: 3 memory copies
df = pd.read_sql(query, conn)     # Copy 1: DB → pandas
X = df[features].values           # Copy 2: pandas → numpy
tensor = torch.from_numpy(X)      # Copy 3: numpy → tensor
# Peak memory: 3x data size

The Solution with Arrow

# GOOD: 0-1 memory copies
import clickhouse_connect
import polars as pl
import torch

client = clickhouse_connect.get_client(...)
arrow_table = client.query_arrow("SELECT * FROM bars")  # Arrow in DB memory
df = pl.from_arrow(arrow_table)                          # Zero-copy view
X = df.select(features).to_numpy()                       # Single allocation
tensor = torch.from_numpy(X)                             # View (no copy)
# Peak memory: 1.2x data size

3. ClickHouse Integration Patterns

Pattern A: Arrow Stream (Recommended)

def query_arrow(client, query: str) -> pl.DataFrame:
    """ClickHouse → Arrow → Polars (zero-copy chain)."""
    arrow_table = client.query_arrow(f"{query} FORMAT ArrowStream")
    return pl.from_arrow(arrow_table)

# Usage
df = query_arrow(client, "SELECT * FROM bars WHERE ts >= '2024-01-01'")

Pattern B: Polars Native (Simpler)

# Polars has native ClickHouse support (see pola.rs for version requirements)
df = pl.read_database_uri(
    query="SELECT * FROM bars",
    uri="clickhouse://user:pass@host/db"
)

Pattern C: Parquet Export (Batch Jobs)

# For reproducible batch processing
client.query("SELECT * FROM bars INTO OUTFILE 'data.parquet' FORMAT Parquet")
df = pl.scan_parquet("data.parquet")  # Lazy, memory-mapped

4. PyTorch DataLoader Integration

Minimal Change Pattern

from torch.utils.data import TensorDataset, DataLoader

# Accept both pandas and polars
def prepare_data(df) -> tuple[torch.Tensor, torch.Tensor]:
    if isinstance(df, pd.DataFrame):
        df = pl.from_pandas(df)

    X = df.select(features).to_numpy()
    y = df.select(target).to_numpy()

    return (
        torch.from_numpy(X).float(),
        torch.from_numpy(y).float()
    )

X, y = prepare_data(df)
dataset = TensorDataset(X, y)
loader = DataLoader(dataset, batch_size=32, pin_memory=True)

Custom PolarsDataset (Large Data)

class PolarsDataset(torch.utils.data.Dataset):
    """Memory-efficient dataset from Polars DataFrame."""

    def __init__(self, df: pl.DataFrame, features: list[str], target: str):
        self.arrow = df.to_arrow()  # Arrow backing for zero-copy slicing
        self.features = features
        self.target = target

    def __len__(self) -> int:
        return self.arrow.num_rows

    def __getitem__(self, idx: int) -> tuple[torch.Tensor, torch.Tensor]:
        row = self.arrow.slice(idx, 1)
        x = torch.tensor([row[f][0].as_py() for f in self.features], dtype=torch.float32)
        y = torch.tensor(row[self.target][0].as_py(), dtype=torch.float32)
        return x, y

5. Lazy Evaluation Patterns

Pipeline Composition

# Define transformations lazily (no computation yet)
pipeline = (
    pl.scan_parquet("raw_data.parquet")
    .filter(pl.col("timestamp") >= start_date)
    .with_columns([
        (pl.col("close").pct_change()).alias("returns"),
        (pl.col("volume").log()).alias("log_volume"),
    ])
    .select(features + [target])
)

# Execute only when needed
train_df = pipeline.filter(pl.col("timestamp") < split_date).collect()
test_df = pipeline.filter(pl.col("timestamp") >= split_date).collect()

Streaming for Large Files

# Process file in chunks (never loads full file)
def process_large_file(path: str, chunk_size: int = 100_000):
    reader = pl.scan_parquet(path)

    for batch in reader.iter_batches(n_rows=chunk_size):
        # Process each chunk
        features = compute_features(batch)
        yield features.to_numpy()

6. Schema Validation

Pydantic for Config

from pydantic import BaseModel, field_validator

class FeatureConfig(BaseModel):
    features: list[str]
    target: str
    seq_len: int = 15

    @field_validator("features")
    @classmethod
    def validate_features(cls, v):
        required = {"returns_vs", "momentum_z", "atr_pct"}
        missing = required - set(v)
        if missing:
            raise ValueError(f"Missing required features: {missing}")
        return v

DataFrame Schema Validation

def validate_schema(df: pl.DataFrame, required: list[str], stage: str) -> None:
    """Fail-fast schema validation."""
    missing = [c for c in required if c not in df.columns]
    if missing:
        raise ValueError(
            f"[{stage}] Missing columns: {missing}\n"
            f"Available: {sorted(df.columns)}"
        )

7. Performance Benchmarks

OperationPandasPolarsSpeedup
Read CSV (1GB)45s4s11x
Filter rows2.1s0.4s5x
Group-by agg3.8s0.3s13x
Sort5.2s0.4s13x
Memory peak10GB2.5GB4x

Benchmark: 50M rows, 20 columns, MacBook M2


8. Migration Checklist

Phase 1: Add Arrow Support

  • Add polars = "<version>" to dependencies (see PyPI)
  • Implement query_arrow() in data client
  • Verify zero-copy with memory profiler

Phase 2: Polars at Entry Points

  • Add pl.from_pandas() wrapper at trainer entry
  • Update prepare_sequences() to accept both types
  • Add schema validation after conversion

Phase 3: Full Lazy Evaluation

  • Convert file reads to pl.scan_*
  • Compose transformations lazily
  • Call .collect() only before .to_numpy()

9. Anti-Patterns to Avoid

DON'T: Mix APIs Unnecessarily

# BAD: Convert back and forth
df_polars = pl.from_pandas(df_pandas)
df_pandas_again = df_polars.to_pandas()  # Why?

DON'T: Collect Too Early

# BAD: Defeats lazy evaluation
df = pl.scan_parquet("data.parquet").collect()  # Full load
filtered = df.filter(...)  # After the fact

# GOOD: Filter before collect
df = pl.scan_parquet("data.parquet").filter(...).collect()

DON'T: Ignore Memory Pressure

# BAD: Loads entire file
df = pl.read_parquet("huge_file.parquet")

# GOOD: Stream in chunks
for batch in pl.scan_parquet("huge_file.parquet").iter_batches():
    process(batch)

References


Troubleshooting

IssueCauseSolution
Memory spike during loadCollecting too earlyUse lazy evaluation, call collect() only when needed
Arrow conversion failsUnsupported data typeCheck for object columns, convert to native types
ClickHouse connection errorWrong port or credentialsVerify host:8123 (HTTP) or host:9000 (native)
Zero-copy not workingIntermediate pandas conversionRemove to_pandas() calls, stay in Arrow/Polars
Polars hook blocking codePandas used without exceptionAdd # polars-exception: reason comment at file top
Slow group-by operationsUsing pandas for large datasetsMigrate to Polars for 5-10x speedup
Schema validation failureColumn names case-sensitiveVerify exact column names from source
PyTorch DataLoader OOMLoading full dataset into memoryUse PolarsDataset with Arrow backing for lazy access
Parquet scan performanceNot using predicate pushdownAdd filters before collect() for lazy evaluation
Type mismatch in tensorFloat64 vs Float32 mismatchExplicitly cast with .cast(pl.Float32) before numpy

Post-Execution Reflection

After this skill completes, reflect before closing the task:

  1. Locate yourself. — Find this SKILL.md's canonical path before editing.
  2. What failed? — Fix the instruction that caused it.
  3. What worked better than expected? — Promote to recommended practice.
  4. What drifted? — Fix any script, reference, or dependency that no longer matches reality.
  5. Log it. — Evolution-log entry with trigger, fix, and evidence.

Do NOT defer. The next invocation inherits whatever you leave behind.

Frequently asked questions

What to verify before installation and use

What does the ml-data-pipeline-architecture source document cover?

Patterns for efficient ML data pipelines using Polars, Arrow, and ClickHouse.

How do I install ml-data-pipeline-architecture?

The source record exposes this install command: npx skills add https://github.com/terrylica/cc-skills --skill "plugins/devops-tools/skills/ml-data-pipeline-architecture". Inspect the command and pinned source before running it.

Alternatives

Compare before choosing