Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Transform raw data into analytical assets using ETL/ELT patterns, SQL (dbt), Python (pandas/polars/PySpark), and orchestration (Airflow). Use when building data pipelines, implementing incremental models, migrating from pandas to polars, or orchestrating multi-step transformations with testing and quality checks.
.claude/skills/ancoleman-transforming-data/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 115% | 0% |
| case-20 | ✓→✗ | ▼ Worse | 103% | 0% |
| case-13 | ✓→✓ | = Same ✓ | 328% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 168% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 140% | 0% |
Transform raw data into analytical assets using modern transformation patterns, frameworks, and orchestration tools.
Select and implement data transformation patterns across the modern data stack. Transform raw data into clean, tested, and documented analytical datasets using SQL (dbt), Python DataFrames (pandas, polars, PySpark), and pipeline orchestration (Airflow, Dagster, Prefect).
Invoke this skill when:
sql{{ config( materialized='incremental', unique_key='order_id' ) }} select order_id, customer_id, order_created_at, sum(revenue) as total_revenue from {{ ref('int_order_items_joined') }} group by 1, 2, 3 {% if is_incremental() %} where order_created_at > (select max(order_created_at) from {{ this }}) {% endif %}
pythonimport polars as pl result = ( pl.scan_csv('large_dataset.csv') .filter(pl.col('year') == 2024) .with_columns([(pl.col('quantity') * pl.col('price')).alias('revenue')]) .group_by('region') .agg(pl.col('revenue').sum()) .collect() # Execute lazy query )
pythonfrom airflow import DAG from airflow.operators.python import PythonOperator from datetime import datetime, timedelta with DAG( dag_id='daily_sales_pipeline', schedule_interval='0 2 * * *', default_args={'retries': 2, 'retry_delay': timedelta(minutes=5)}, start_date=datetime(2024, 1, 1), catchup=False ) as dag: extract = PythonOperator(task_id='extract', python_callable=extract_data) transform = PythonOperator(task_id='transform', python_callable=transform_data) extract >> transform
Use ELT (Extract, Load, Transform) when:
Tools: dbt, Dataform, Snowflake tasks, BigQuery scheduled queries
Use ETL (Extract, Transform, Load) when:
Tools: AWS Glue, Azure Data Factory, custom Python scripts
Use Hybrid when combining sensitive data cleansing (ETL) with analytics transformations (ELT).
Default recommendation: ELT with dbt unless specific compliance or performance constraints require ETL.
For detailed patterns, see references/etl-vs-elt-patterns.md.
Choose pandas when:
Choose polars when:
Choose PySpark when:
Migration path: pandas → polars (easier, similar API) or pandas → PySpark (requires cluster)
For comparisons and migration guides, see references/dataframe-comparison.md.
Choose Airflow when:
Choose Dagster when:
dbt_assets integration)Choose Prefect when:
Safe default: Airflow (battle-tested) unless specific needs for Dagster/Prefect.
For detailed patterns, see references/orchestration-patterns.md.
models/staging/)models/intermediate/)models/marts/)View: Query re-run each time model referenced. Use for fast queries, staging layer.
Table: Full refresh on each run. Use for frequently queried models, expensive computations.
Incremental: Only processes new/changed records. Use for large fact tables, event logs.
Ephemeral: CTE only, not persisted. Use for intermediate calculations.
yamlmodels: - name: fct_orders columns: - name: order_id tests: - unique - not_null - name: customer_id tests: - relationships: to: ref('dim_customers') field: customer_id - name: total_revenue tests: - dbt_utils.accepted_range: min_value: 0
For comprehensive dbt patterns, see:
references/dbt-best-practices.mdreferences/incremental-strategies.mdpythonimport pandas as pd df = pd.read_csv('sales.csv') result = ( df .query('year == 2024') .assign(revenue=lambda x: x['quantity'] * x['price']) .groupby('region') .agg({'revenue': ['sum', 'mean']}) )
pythonimport polars as pl result = ( pl.scan_csv('sales.csv') # Lazy evaluation .filter(pl.col('year') == 2024) .with_columns([(pl.col('quantity') * pl.col('price')).alias('revenue')]) .group_by('region') .agg([ pl.col('revenue').sum().alias('revenue_sum'), pl.col('revenue').mean().alias('revenue_mean') ]) .collect() # Execute lazy query )
Key differences:
scan_csv() (lazy) vs pandas read_csv() (eager)with_columns() vs pandas assign()pl.col() expressions vs pandas string referencescollect() to execute lazy queriespythonfrom pyspark.sql import SparkSession, functions as F spark = SparkSession.builder.appName("Transform").getOrCreate() df = spark.read.csv('sales.csv', header=True, inferSchema=True) result = ( df .filter(F.col('year') == 2024) .withColumn('revenue', F.col('quantity') * F.col('price')) .groupBy('region') .agg(F.sum('revenue').alias('total_revenue')) )
For migration guides, see references/dataframe-comparison.md.
pythonfrom airflow import DAG from airflow.operators.python import PythonOperator from datetime import datetime, timedelta default_args = { 'owner': 'data-engineering', 'retries': 2, 'retry_delay': timedelta(minutes=5) } with DAG( dag_id='data_pipeline', default_args=default_args, schedule_interval='0 2 * * *', # Daily at 2 AM start_date=datetime(2024, 1, 1), catchup=False ) as dag: task1 = PythonOperator(task_id='extract', python_callable=extract_fn) task2 = PythonOperator(task_id='transform', python_callable=transform_fn) task1 >> task2 # Define dependency
Linear: A >> B >> C (sequential) Fan-out: A >> [B, C, D] (parallel after A) Fan-in: [A, B, C] >> D (D waits for all)
For Airflow, Dagster, and Prefect patterns, see references/orchestration-patterns.md.
Generic tests (reusable): unique, not_null, accepted_values, relationships
Singular tests (custom SQL):
sql-- tests/assert_positive_revenue.sql select * from {{ ref('fct_orders') }} where total_revenue < 0
pythonimport great_expectations as gx context = gx.get_context() suite = context.add_expectation_suite("orders_suite") suite.add_expectation( gx.expectations.ExpectColumnValuesToNotBeNull(column="order_id") ) suite.add_expectation( gx.expectations.ExpectColumnValuesToBeBetween( column="total_revenue", min_value=0 ) )
For comprehensive testing patterns, see references/data-quality-testing.md.
Window functions for analytics:
sqlselect order_date, daily_revenue, avg(daily_revenue) over ( partition by region order by order_date rows between 6 preceding and current row ) as revenue_7d_ma, sum(daily_revenue) over ( partition by region order by order_date ) as cumulative_revenue from daily_sales
For advanced window functions, see references/window-functions-guide.md.
Ensure transformations produce same result when run multiple times:
merge statements in incremental modelsunique_key in dbt incremental modelssql{% if is_incremental() %} where created_at > (select max(created_at) from {{ this }}) {% endif %}
pythontry: result = perform_transformation() validate_result(result) except ValidationError as e: log_error(e) raise
SQL Transformations: dbt Core (industry standard, multi-warehouse, rich ecosystem)
bashpip install dbt-core dbt-snowflake
Python DataFrames: polars (10-100x faster than pandas, multi-threaded, lazy evaluation)
bashpip install polars
Orchestration: Apache Airflow (battle-tested at scale, 5,000+ integrations)
bashpip install apache-airflow
Working examples in:
examples/python/pandas-basics.py - pandas transformationsexamples/python/polars-migration.py - pandas to polars migrationexamples/python/pyspark-transformations.py - PySpark operationsexamples/python/airflow-data-pipeline.py - Complete Airflow DAGexamples/sql/dbt-staging-model.sql - dbt staging layerexamples/sql/dbt-intermediate-model.sql - dbt intermediate layerexamples/sql/dbt-incremental-model.sql - Incremental patternsexamples/sql/window-functions.sql - Advanced SQLscripts/generate_dbt_models.py - Generate dbt model boilerplatescripts/benchmark_dataframes.py - Compare pandas vs polars performanceFor data ingestion patterns, see ingesting-data. For data visualization, see visualizing-data. For database design, see databases-* skills. For real-time streaming, see streaming-data. For data platform architecture, see ai-data-engineering. For monitoring pipelines, see observability.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-13 | pass→pass | 4,997 | 4,546 | -9% | 1 | 1 | 0% | 898 | 3,844 | +328% | 0 | 0 | — |
case-01 | pass→pass | 38,536 | 15,210 | -61% | 1 | 1 | 0% | 2,198 | 5,892 | +168% | 0 | 0 | — |
case-02 | pass→pass | 12,821 | 10,739 | -16% | 1 | 1 | 0% | 2,013 | 4,837 | +140% | 0 | 0 | — |
case-03 | pass→pass | 12,040 | 10,445 | -13% | 1 | 1 | 0% | 2,259 | 4,753 | +110% | 0 | 0 | — |
case-04 | pass→pass | 6,403 | 6,252 | -2% | 1 | 1 | 0% | 1,174 | 4,177 | +256% | 0 | 0 | — |
case-05 | fail→pass | 17,064 | 12,509 | -27% | 1 | 1 | 0% | 2,462 | 5,302 | +115% | 0 | 0 | — |
case-06 | pass→pass | 12,854 | 22,127 | +72% | 1 | 1 | 0% | 2,230 | 5,376 | +141% | 0 | 0 | — |
case-07 | pass→pass | 14,564 | 8,132 | -44% | 1 | 1 | 0% | 2,281 | 4,587 | +101% | 0 | 0 | — |
case-08 | pass→pass | 3,337 | 4,288 | +28% | 1 | 1 | 0% | 582 | 3,751 | +545% | 0 | 0 | — |
case-09 | pass→pass | 9,287 | 7,358 | -21% | 1 | 1 | 0% | 1,706 | 4,391 | +157% | 0 | 0 | — |
case-10 | pass→pass | 4,825 | 3,561 | -26% | 1 | 1 | 0% | 915 | 3,759 | +311% | 0 | 0 | — |
case-11 | pass→pass | 11,480 | 6,480 | -44% | 1 | 1 | 0% | 916 | 4,225 | +361% | 0 | 0 | — |
case-12 | pass→pass | 5,199 | 6,859 | +32% | 1 | 1 | 0% | 846 | 4,160 | +392% | 0 | 0 | — |
case-14 | pass→pass | 3,837 | 5,884 | +53% | 1 | 1 | 0% | 658 | 4,034 | +513% | 0 | 0 | — |
case-15 | pass→pass | 9,079 | 7,008 | -23% | 1 | 1 | 0% | 1,782 | 4,262 | +139% | 0 | 0 | — |
case-16 | pass→pass | 9,829 | 7,112 | -28% | 1 | 1 | 0% | 1,606 | 4,227 | +163% | 0 | 0 | — |
case-17 | pass→pass | 13,290 | 9,091 | -32% | 1 | 1 | 0% | 2,372 | 4,793 | +102% | 0 | 0 | — |
case-18 | pass→pass | 7,185 | 5,612 | -22% | 1 | 1 | 0% | 1,246 | 4,138 | +232% | 0 | 0 | — |
case-19 | pass→pass | 4,721 | 4,072 | -14% | 1 | 1 | 0% | 647 | 3,870 | +498% | 0 | 0 | — |
case-20 | pass→fail | 22,908 | 21,355 | -7% | 1 | 1 | 0% | 3,500 | 7,097 | +103% | 0 | 0 | — |
case-21 | pass→pass | 17,929 | 14,991 | -16% | 1 | 1 | 0% | 3,161 | 6,034 | +91% | 0 | 0 | — |
case-22 | pass→pass | 16,969 | 13,655 | -20% | 1 | 1 | 0% | 2,848 | 5,785 | +103% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.