{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Featurizer Tutorial: Basic Aggregations\n", "\n", "This notebook demonstrates the core workflow of Featurizer using a simple e-commerce scenario:\n", "- **Customers** (parent entity) with attributes like country and age\n", "- **Orders** (child entity) with amounts and statuses\n", "\n", "We'll generate features for customers by aggregating their order history." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Setup\n", "\n", "First, let's set up the environment and create sample data." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.323842Z", "iopub.status.busy": "2026-06-21T19:29:05.323749Z", "iopub.status.idle": "2026-06-21T19:29:05.329902Z", "shell.execute_reply": "2026-06-21T19:29:05.329505Z" } }, "outputs": [], "source": [ "import sys\n", "from pathlib import Path\n", "\n", "# This tutorial is database-free: it loads the config and inspects the\n", "# synthesized features and the generated SQL — none of which touch a\n", "# database. To actually *execute* the features against PostgreSQL, run the\n", "# example script instead (`just example `, or create_data.py +\n", "# run_example.py with DATABASE_URL / PG* set). See the example README.\n", "sys.path.insert(0, str(Path.cwd().parent.parent))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Understanding the Configuration\n", "\n", "Featurizer uses YAML configuration to define entities, relationships, and feature generation parameters." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.331315Z", "iopub.status.busy": "2026-06-21T19:29:05.331230Z", "iopub.status.idle": "2026-06-21T19:29:05.333395Z", "shell.execute_reply": "2026-06-21T19:29:05.332953Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "# E-commerce feature generation: Customers with Orders\n", "\n", "target: customers\n", "max_depth: 2\n", "\n", "intervals:\n", " - P7D # Last 7 days\n", " - P30D # Last 30 days\n", "\n", "# A focused primitive set keeps the feature matrix small and readable. The full\n", "# default set would synthesize thousands of columns — past PostgreSQL's 1664\n", "# per-row limit and far past anything legible in a tutorial.\n", "aggregations:\n", " - count\n", " - sum\n", " - mean\n", " - min\n", " - max\n", " - stddev\n", " - nunique\n", "transformations:\n", " - identity\n", " - abs\n", "\n", "entities:\n", " - alias: customers\n", " id: customer_id\n", " table: customers\n", " temporal_ix: signup_date\n", " variables:\n", " # role: categorical one-hot encodes a direct categorical against a FIXED\n", " # vocabulary (declared here, or a column's PostgreSQL ENUM) — split-blind\n", " # and fit-free. Each value becomes a \"customers.country=\" 0/1\n", " # column. Without a role, a raw text/categorical column passes through\n", " # unencoded (and warns), which a downstream encoder usually can't take.\n", " country:\n", " type: categorical\n", " role: categorical\n", " vocabulary: [AU, CA, DE, FR, UK, US]\n", " age:\n", " type: numeric\n", "\n", " - alias: orders\n", " id: order_id\n", " table: orders\n", " temporal_ix: order_date\n", " variables:\n", " amount:\n", " type: numeric\n", " status:\n", " type: categorical\n", "\n", "relationships:\n", " - parent:\n", " entity: customers\n", " key: customer_id\n", " child:\n", " entity: orders\n", " key: customer_id\n", "\n" ] } ], "source": [ "# Let's examine the configuration file\n", "with open(\"config.yaml\") as f:\n", " print(f.read())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Key Configuration Elements:\n", "\n", "- **target**: The entity we want to generate features for (`customers`)\n", "- **max_depth**: How deep to traverse relationships (2 levels)\n", "- **intervals**: Time windows for aggregations (`P7D` = 7 days, `P30D` = 30 days)\n", "- **entities**: Define tables with their columns and types\n", "- **relationships**: Define parent-child connections via foreign keys" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Creating the Featurizer\n", "\n", "Load the configuration and create a Featurizer instance." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.350712Z", "iopub.status.busy": "2026-06-21T19:29:05.350619Z", "iopub.status.idle": "2026-06-21T19:29:05.612987Z", "shell.execute_reply": "2026-06-21T19:29:05.612536Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32m2026-06-21 13:29:05.609\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mfeaturizer.planner\u001b[0m:\u001b[36mplan\u001b[0m:\u001b[36m230\u001b[0m - \u001b[34m\u001b[1mStarting feature build for target customers\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32m2026-06-21 13:29:05.609\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mfeaturizer.planner\u001b[0m:\u001b[36m_build_features\u001b[0m:\u001b[36m256\u001b[0m - \u001b[34m\u001b[1mbuild_features(customers) depth=0\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32m2026-06-21 13:29:05.610\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mfeaturizer.planner\u001b[0m:\u001b[36m_build_features\u001b[0m:\u001b[36m256\u001b[0m - \u001b[34m\u001b[1mbuild_features(orders) depth=1\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32m2026-06-21 13:29:05.610\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mfeaturizer.planner\u001b[0m:\u001b[36m_build_features\u001b[0m:\u001b[36m276\u001b[0m - \u001b[1mMaximum recursion depth reached at depth 2; materializing orders without traversing further.\u001b[0m\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32m2026-06-21 13:29:05.610\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mfeaturizer.planner\u001b[0m:\u001b[36m_build_aggregations\u001b[0m:\u001b[36m1014\u001b[0m - \u001b[34m\u001b[1mProcessing backward relationship Entity(customers).customer_id -> Entity(orders).customer_id\u001b[0m\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Target entity: customers\n", "Max depth: 2\n", "Intervals: ['P7D', 'P30D']\n", "Number of entities: 2\n", "Number of relationships: 1\n" ] } ], "source": [ "from featurizer import Featurizer\n", "\n", "# Create featurizer from config\n", "featurizer = Featurizer(\"config.yaml\")\n", "\n", "print(f\"Target entity: {featurizer.target.alias}\")\n", "print(f\"Max depth: {featurizer.max_depth}\")\n", "print(f\"Intervals: {featurizer.intervals}\")\n", "print(f\"Number of entities: {len(list(featurizer.entities))}\")\n", "print(f\"Number of relationships: {len(featurizer.relationships)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Exploring the Entity Graph\n", "\n", "Let's examine the entities and their relationships." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.613945Z", "iopub.status.busy": "2026-06-21T19:29:05.613862Z", "iopub.status.idle": "2026-06-21T19:29:05.615675Z", "shell.execute_reply": "2026-06-21T19:29:05.615314Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Entities:\n", "\n", " customers:\n", " Table: customers\n", " ID: customer_id\n", " Temporal index: signup_date\n", " Features: 4\n", "\n", " orders:\n", " Table: orders\n", " ID: order_id\n", " Temporal index: order_date\n", " Features: 4\n" ] } ], "source": [ "# List all entities\n", "print(\"Entities:\")\n", "for entity in featurizer.entities:\n", " print(f\"\\n {entity.alias}:\")\n", " print(f\" Table: {entity.table}\")\n", " print(f\" ID: {entity.id.name if entity.id else 'None'}\")\n", " print(\n", " f\" Temporal index: {entity.temporal_ix.name if entity.temporal_ix else 'None'}\"\n", " )\n", " print(f\" Features: {len(entity.features)}\")" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.616505Z", "iopub.status.busy": "2026-06-21T19:29:05.616432Z", "iopub.status.idle": "2026-06-21T19:29:05.618040Z", "shell.execute_reply": "2026-06-21T19:29:05.617666Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Relationships:\n", " customers.customer_id -> orders.customer_id\n" ] } ], "source": [ "# Show relationships\n", "print(\"Relationships:\")\n", "for rel in featurizer.relationships:\n", " print(f\" {rel.parent.alias}.{rel.parent_key} -> {rel.child.alias}.{rel.child_key}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Generated Features\n", "\n", "Featurizer automatically synthesizes features by:\n", "1. Taking base features from each entity (columns declared as variables)\n", "2. Applying aggregations when traversing backward relationships\n", "3. Applying transformations to all features" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.619012Z", "iopub.status.busy": "2026-06-21T19:29:05.618956Z", "iopub.status.idle": "2026-06-21T19:29:05.620850Z", "shell.execute_reply": "2026-06-21T19:29:05.620483Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Total features generated: 107\n", "\n", "Sample features (first 20):\n", " 1. \"ABS(customers.COUNT(orders.order_date))\"\n", " 2. \"ABS(customers.COUNT(orders.order_date|interval=P30D))\"\n", " 3. \"ABS(customers.COUNT(orders.order_date|interval=P7D))\"\n", " 4. \"ABS(customers.COUNT(orders.order_id))\"\n", " 5. \"ABS(customers.COUNT(orders.order_id|interval=P30D))\"\n", " 6. \"ABS(customers.COUNT(orders.order_id|interval=P7D))\"\n", " 7. \"ABS(customers.COUNT(orders.status))\"\n", " 8. \"ABS(customers.COUNT(orders.status|interval=P30D))\"\n", " 9. \"ABS(customers.COUNT(orders.status|interval=P7D))\"\n", " 10. \"ABS(customers.MAX(orders.ABS(orders.amount)))\"\n", " 11. \"ABS(customers.MAX(orders.ABS(orders.amount)|interval=P30D))\"\n", " 12. \"ABS(customers.MAX(orders.ABS(orders.amount)|interval=P7D))\"\n", " 13. \"ABS(customers.MAX(orders.amount))\"\n", " 14. \"ABS(customers.MAX(orders.amount|interval=P30D))\"\n", " 15. \"ABS(customers.MAX(orders.amount|interval=P7D))\"\n", " 16. \"ABS(customers.MEAN(orders.ABS(orders.amount)))\"\n", " 17. \"ABS(customers.MEAN(orders.ABS(orders.amount)|interval=P30D))\"\n", " 18. \"ABS(customers.MEAN(orders.ABS(orders.amount)|interval=P7D))\"\n", " 19. \"ABS(customers.MEAN(orders.amount))\"\n", " 20. \"ABS(customers.MEAN(orders.amount|interval=P30D))\"\n" ] } ], "source": [ "# Get features for the target entity\n", "target_features = featurizer.features[featurizer.target.alias]\n", "\n", "print(f\"Total features generated: {len(target_features)}\")\n", "print(\"\\nSample features (first 20):\")\n", "for i, feature in enumerate(sorted(target_features, key=lambda f: f.name)[:20], 1):\n", " print(f\" {i:2}. {feature.name}\")" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.621658Z", "iopub.status.busy": "2026-06-21T19:29:05.621611Z", "iopub.status.idle": "2026-06-21T19:29:05.623160Z", "shell.execute_reply": "2026-06-21T19:29:05.622809Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Features by type:\n", " categorical: 1\n", " index: 2\n", " numeric: 104\n" ] } ], "source": [ "# Categorize features by type\n", "feature_types = {}\n", "for f in target_features:\n", " feature_types.setdefault(f.type, []).append(f)\n", "\n", "print(\"Features by type:\")\n", "for ftype, features in sorted(feature_types.items()):\n", " print(f\" {ftype}: {len(features)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. Understanding Feature Names\n", "\n", "Feature names follow a pattern that describes how they were created:\n", "\n", "- `AGGREGATION(entity.column)` - Basic aggregation\n", "- `AGGREGATION(entity.column|interval=P7D)` - Time-windowed aggregation\n", "- `TRANSFORM(entity.column)` - Transformation applied" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.624066Z", "iopub.status.busy": "2026-06-21T19:29:05.624012Z", "iopub.status.idle": "2026-06-21T19:29:05.625622Z", "shell.execute_reply": "2026-06-21T19:29:05.625356Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sample aggregation features:\n", " \"ABS(customers.COUNT(orders.order_date))\"\n", " \"ABS(customers.COUNT(orders.order_date|interval=P30D))\"\n", " \"ABS(customers.COUNT(orders.order_date|interval=P7D))\"\n", " \"ABS(customers.COUNT(orders.order_id))\"\n", " \"ABS(customers.COUNT(orders.order_id|interval=P30D))\"\n", " \"ABS(customers.COUNT(orders.order_id|interval=P7D))\"\n", " \"ABS(customers.COUNT(orders.status))\"\n", " \"ABS(customers.COUNT(orders.status|interval=P30D))\"\n", " \"ABS(customers.COUNT(orders.status|interval=P7D))\"\n", " \"ABS(customers.MEAN(orders.ABS(orders.amount)))\"\n" ] } ], "source": [ "# Show aggregation features\n", "agg_features = [\n", " f\n", " for f in target_features\n", " if \"SUM(\" in f.name or \"MEAN(\" in f.name or \"COUNT(\" in f.name\n", "]\n", "\n", "print(\"Sample aggregation features:\")\n", "for f in sorted(agg_features, key=lambda x: x.name)[:10]:\n", " print(f\" {f.name}\")" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.626409Z", "iopub.status.busy": "2026-06-21T19:29:05.626345Z", "iopub.status.idle": "2026-06-21T19:29:05.627775Z", "shell.execute_reply": "2026-06-21T19:29:05.627490Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Time-windowed features: 64\n", "\n", "Sample windowed features:\n", " \"ABS(customers.COUNT(orders.order_date|interval=P30D))\"\n", " \"ABS(customers.COUNT(orders.order_date|interval=P7D))\"\n", " \"ABS(customers.COUNT(orders.order_id|interval=P30D))\"\n", " \"ABS(customers.COUNT(orders.order_id|interval=P7D))\"\n", " \"ABS(customers.COUNT(orders.status|interval=P30D))\"\n", " \"ABS(customers.COUNT(orders.status|interval=P7D))\"\n", " \"ABS(customers.MAX(orders.ABS(orders.amount)|interval=P30D))\"\n", " \"ABS(customers.MAX(orders.ABS(orders.amount)|interval=P7D))\"\n", " \"ABS(customers.MAX(orders.amount|interval=P30D))\"\n", " \"ABS(customers.MAX(orders.amount|interval=P7D))\"\n" ] } ], "source": [ "# Show time-windowed features\n", "windowed = [f for f in target_features if \"interval=\" in f.name]\n", "\n", "print(f\"Time-windowed features: {len(windowed)}\")\n", "print(\"\\nSample windowed features:\")\n", "for f in sorted(windowed, key=lambda x: x.name)[:10]:\n", " print(f\" {f.name}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7. Examining the Generated SQL\n", "\n", "Featurizer generates a PostgreSQL query with CTEs (Common Table Expressions) for each stage." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.628650Z", "iopub.status.busy": "2026-06-21T19:29:05.628605Z", "iopub.status.idle": "2026-06-21T19:29:05.630307Z", "shell.execute_reply": "2026-06-21T19:29:05.630030Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "\u001b[32m2026-06-21 13:29:05.628\u001b[0m | \u001b[34m\u001b[1mDEBUG \u001b[0m | \u001b[36mfeaturizer.sql\u001b[0m:\u001b[36mrender\u001b[0m:\u001b[36m40\u001b[0m - \u001b[34m\u001b[1mRendered SQL for target 'customers': 5 CTEs, 18886 chars\u001b[0m\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Generated SQL Query:\n", "================================================================================\n", "\n", " select aod.as_of_date, t.*\n", " from as_of_dates as aod\n", " cross join lateral (\n", "\n", " with\n", "\n", " \n", " -- sythetize aggregations and direct features for orders\n", " orders_synth as (\n", " select\n", " orders.order_id, orders.order_date, orders.customer_id, amount, status\n", " from orders\n", " \n", " \n", " )\n", " ,\n", " -- transform orders\n", " orders_transform as (\n", " select\n", " order_id, order_date, customer_id, abs(amount) as \"ABS(orders.amount)\" , amount as amount, status as status\n", " from orders_synth _ego\n", " )\n", " ,\n", " -- Aggregate for customers\n", " orders_aggs_for_customers as (\n", " select\n", " orders_transform.customer_id,\n", " count( order_date ) as \"COUNT(orders.order_date)\" ,count( order_date ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"COUNT(orders.order_date|interval=P30D)\" ,count( order_date ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"COUNT(orders.order_date|interval=P7D)\" ,count( order_id ) as \"COUNT(orders.order_id)\" ,count( order_id ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"COUNT(orders.order_id|interval=P30D)\" ,count( order_id ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"COUNT(orders.order_id|interval=P7D)\" ,count( status ) as \"COUNT(orders.status)\" ,count( status ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"COUNT(orders.status|interval=P30D)\" ,count( status ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"COUNT(orders.status|interval=P7D)\" ,max( \"ABS(orders.amount)\" ) as \"MAX(orders.ABS(orders.amount))\" ,max( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MAX(orders.ABS(orders.amount)|interval=P30D)\" ,max( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MAX(orders.ABS(orders.amount)|interval=P7D)\" ,max( amount ) as \"MAX(orders.amount)\" ,max( amount ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MAX(orders.amount|interval=P30D)\" ,max( amount ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MAX(orders.amount|interval=P7D)\" ,avg( \"ABS(orders.amount)\" ) as \"MEAN(orders.ABS(orders.amount))\" ,avg( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MEAN(orders.ABS(orders.amount)|interval=P30D)\" ,avg( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MEAN(orders.ABS(orders.amount)|interval=P7D)\" ,avg( amount ) as \"MEAN(orders.amount)\" ,avg( amount ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MEAN(orders.amount|interval=P30D)\" ,avg( amount ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MEAN(orders.amount|interval=P7D)\" ,min( \"ABS(orders.amount)\" ) as \"MIN(orders.ABS(orders.amount))\" ,min( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MIN(orders.ABS(orders.amount)|interval=P30D)\" ,min( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MIN(orders.ABS(orders.amount)|interval=P7D)\" ,min( amount ) as \"MIN(orders.amount)\" ,min( amount ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MIN(orders.amount|interval=P30D)\" ,min( amount ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"MIN(orders.amount|interval=P7D)\" ,count(distinct order_date ) as \"NUNIQUE(orders.order_date)\" ,count(distinct order_date ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"NUNIQUE(orders.order_date|interval=P30D)\" ,count(distinct order_date ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"NUNIQUE(orders.order_date|interval=P7D)\" ,count(distinct order_id ) as \"NUNIQUE(orders.order_id)\" ,count(distinct order_id ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"NUNIQUE(orders.order_id|interval=P30D)\" ,count(distinct order_id ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"NUNIQUE(orders.order_id|interval=P7D)\" ,count(distinct status ) as \"NUNIQUE(orders.status)\" ,count(distinct status ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"NUNIQUE(orders.status|interval=P30D)\" ,count(distinct status ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"NUNIQUE(orders.status|interval=P7D)\" ,stddev( \"ABS(orders.amount)\" ) as \"STDDEV(orders.ABS(orders.amount))\" ,stddev( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"STDDEV(orders.ABS(orders.amount)|interval=P30D)\" ,stddev( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"STDDEV(orders.ABS(orders.amount)|interval=P7D)\" ,stddev( amount ) as \"STDDEV(orders.amount)\" ,stddev( amount ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"STDDEV(orders.amount|interval=P30D)\" ,stddev( amount ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"STDDEV(orders.amount|interval=P7D)\" ,sum( \"ABS(orders.amount)\" ) as \"SUM(orders.ABS(orders.amount))\" ,sum( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"SUM(orders.ABS(orders.amount)|interval=P30D)\" ,sum( \"ABS(orders.amount)\" ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"SUM(orders.ABS(orders.amount)|interval=P7D)\" ,sum( amount ) as \"SUM(orders.amount)\" ,sum( amount ) filter (where daterange((aod.as_of_date - interval 'P30D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"SUM(orders.amount|interval=P30D)\" ,sum( amount ) filter (where daterange((aod.as_of_date - interval 'P7D')::date, aod.as_of_date::date, '[]') @> order_date::date) as \"SUM(orders.amount|interval=P7D)\" \n", " from orders_transform\n", " where order_date <= aod.as_of_date\n", " group by customer_id\n", " )\n", " ,\n", " -- sythetize aggregations and direct features for customers\n", " customers_synth as (\n", " select\n", " customers.customer_id, customers.signup_date, \"COUNT(orders.order_date)\", \"COUNT(orders.order_date|interval=P30D)\", \"COUNT(orders.order_date|interval=P7D)\", \"COUNT(orders.order_id)\", \"COUNT(orders.order_id|interval=P30D)\", \"COUNT(orders.order_id|interval=P7D)\", \"COUNT(orders.status)\", \"COUNT(orders.status|interval=P30D)\", \"COUNT(orders.status|interval=P7D)\", \"MAX(orders.ABS(orders.amount))\", \"MAX(orders.ABS(orders.amount)|interval=P30D)\", \"MAX(orders.ABS(orders.amount)|interval=P7D)\", \"MAX(orders.amount)\", \"MAX(orders.amount|interval=P30D)\", \"MAX(orders.amount|interval=P7D)\", \"MEAN(orders.ABS(orders.amount))\", \"MEAN(orders.ABS(orders.amount)|interval=P30D)\", \"MEAN(orders.ABS(orders.amount)|interval=P7D)\", \"MEAN(orders.amount)\", \"MEAN(orders.amount|interval=P30D)\", \"MEAN(orders.amount|interval=P7D)\", \"MIN(orders.ABS(orders.amount))\", \"MIN(orders.ABS(orders.amount)|interval=P30D)\", \"MIN(orders.ABS(orders.amount)|interval=P7D)\", \"MIN(orders.amount)\", \"MIN(orders.amount|interval=P30D)\", \"MIN(orders.amount|interval=P7D)\", \"NUNIQUE(orders.order_date)\", \"NUNIQUE(orders.order_date|interval=P30D)\", \"NUNIQUE(orders.order_date|interval=P7D)\", \"NUNIQUE(orders.order_id)\", \"NUNIQUE(orders.order_id|interval=P30D)\", \"NUNIQUE(orders.order_id|interval=P7D)\", \"NUNIQUE(orders.status)\", \"NUNIQUE(orders.status|interval=P30D)\", \"NUNIQUE(orders.status|interval=P7D)\", \"STDDEV(orders.ABS(orders.amount))\", \"STDDEV(orders.ABS(orders.amount)|interval=P30D)\", \"STDDEV(orders.ABS(orders.amount)|interval=P7D)\", \"STDDEV(orders.amount)\", \"STDDEV(orders.amount|interval=P30D)\", \"STDDEV(orders.amount|interval=P7D)\", \"SUM(orders.ABS(orders.amount))\", \"SUM(orders.ABS(orders.amount)|interval=P30D)\", \"SUM(orders.ABS(orders.amount)|interval=P7D)\", \"SUM(orders.amount)\", \"SUM(orders.amount|interval=P30D)\", \"SUM(orders.amount|interval=P7D)\", age, country\n", " from customers\n", " left join \n", " orders_aggs_for_customers on orders_aggs_for_customers.customer_id = customers.customer_id \n", " )\n", " ,\n", " -- transform customers\n", " customers_transform as (\n", " select\n", " customer_id, signup_date, abs(\"COUNT(orders.order_date)\") as \"ABS(customers.COUNT(orders.order_date))\" , abs(\"COUNT(orders.order_date|interval=P30D)\") as \"ABS(customers.COUNT(orders.order_date|interval=P30D))\" , abs(\"COUNT(orders.order_date|interval=P7D)\") as \"ABS(customers.COUNT(orders.order_date|interval=P7D))\" , abs(\"COUNT(orders.order_id)\") as \"ABS(customers.COUNT(orders.order_id))\" , abs(\"COUNT(orders.order_id|interval=P30D)\") as \"ABS(customers.COUNT(orders.order_id|interval=P30D))\" , abs(\"COUNT(orders.order_id|interval=P7D)\") as \"ABS(customers.COUNT(orders.order_id|interval=P7D))\" , abs(\"COUNT(orders.status)\") as \"ABS(customers.COUNT(orders.status))\" , abs(\"COUNT(orders.status|interval=P30D)\") as \"ABS(customers.COUNT(orders.status|interval=P30D))\" , abs(\"COUNT(orders.status|interval=P7D)\") as \"ABS(customers.COUNT(orders.status|interval=P7D))\" , abs(\"MAX(orders.ABS(orders.amount))\") as \"ABS(customers.MAX(orders.ABS(orders.amount)))\" , abs(\"MAX(orders.ABS(orders.amount)|interval=P30D)\") as \"ABS(customers.MAX(orders.ABS(orders.amount)|interval=P30D))\" , abs(\"MAX(orders.ABS(orders.amount)|interval=P7D)\") as \"ABS(customers.MAX(orders.ABS(orders.amount)|interval=P7D))\" , abs(\"MAX(orders.amount)\") as \"ABS(customers.MAX(orders.amount))\" , abs(\"MAX(orders.amount|interval=P30D)\") as \"ABS(customers.MAX(orders.amount|interval=P30D))\" , abs(\"MAX(orders.amount|interval=P7D)\") as \"ABS(customers.MAX(orders.amount|interval=P7D))\" , abs(\"MEAN(orders.ABS(orders.amount))\") as \"ABS(customers.MEAN(orders.ABS(orders.amount)))\" , abs(\"MEAN(orders.ABS(orders.amount)|interval=P30D)\") as \"ABS(customers.MEAN(orders.ABS(orders.amount)|interval=P30D))\" , abs(\"MEAN(orders.ABS(orders.amount)|interval=P7D)\") as \"ABS(customers.MEAN(orders.ABS(orders.amount)|interval=P7D))\" , abs(\"MEAN(orders.amount)\") as \"ABS(customers.MEAN(orders.amount))\" , abs(\"MEAN(orders.amount|interval=P30D)\") as \"ABS(customers.MEAN(orders.amount|interval=P30D))\" , abs(\"MEAN(orders.amount|interval=P7D)\") as \"ABS(customers.MEAN(orders.amount|interval=P7D))\" , abs(\"MIN(orders.ABS(orders.amount))\") as \"ABS(customers.MIN(orders.ABS(orders.amount)))\" , abs(\"MIN(orders.ABS(orders.amount)|interval=P30D)\") as \"ABS(customers.MIN(orders.ABS(orders.amount)|interval=P30D))\" , abs(\"MIN(orders.ABS(orders.amount)|interval=P7D)\") as \"ABS(customers.MIN(orders.ABS(orders.amount)|interval=P7D))\" , abs(\"MIN(orders.amount)\") as \"ABS(customers.MIN(orders.amount))\" , abs(\"MIN(orders.amount|interval=P30D)\") as \"ABS(customers.MIN(orders.amount|interval=P30D))\" , abs(\"MIN(orders.amount|interval=P7D)\") as \"ABS(customers.MIN(orders.amount|interval=P7D))\" , abs(\"NUNIQUE(orders.order_date)\") as \"ABS(customers.NUNIQUE(orders.order_date))\" , abs(\"NUNIQUE(orders.order_date|interval=P30D)\") as \"ABS(customers.NUNIQUE(orders.order_date|interval=P30D))\" , abs(\"NUNIQUE(orders.order_date|interval=P7D)\") as \"ABS(customers.NUNIQUE(orders.order_date|interval=P7D))\" , abs(\"NUNIQUE(orders.order_id)\") as \"ABS(customers.NUNIQUE(orders.order_id))\" , abs(\"NUNIQUE(orders.order_id|interval=P30D)\") as \"ABS(customers.NUNIQUE(orders.order_id|interval=P30D))\" , abs(\"NUNIQUE(orders.order_id|interval=P7D)\") as \"ABS(customers.NUNIQUE(orders.order_id|interval=P7D))\" , abs(\"NUNIQUE(orders.status)\") as \"ABS(customers.NUNIQUE(orders.status))\" , abs(\"NUNIQUE(orders.status|interval=P30D)\") as \"ABS(customers.NUNIQUE(orders.status|interval=P30D))\" , abs(\"NUNIQUE(orders.status|interval=P7D)\") as \"ABS(customers.NUNIQUE(orders.status|interval=P7D))\" , abs(\"STDDEV(orders.ABS(orders.amount))\") as \"ABS(customers.STDDEV(orders.ABS(orders.amount)))\" , abs(\"STDDEV(orders.ABS(orders.amount)|interval=P30D)\") as \"ABS(customers.STDDEV(orders.ABS(orders.amount)|interval=P30D))\" , abs(\"STDDEV(orders.ABS(orders.amount)|interval=P7D)\") as \"ABS(customers.STDDEV(orders.ABS(orders.amount)|interval=P7D))\" , abs(\"STDDEV(orders.amount)\") as \"ABS(customers.STDDEV(orders.amount))\" , abs(\"STDDEV(orders.amount|interval=P30D)\") as \"ABS(customers.STDDEV(orders.amount|interval=P30D))\" , abs(\"STDDEV(orders.amount|interval=P7D)\") as \"ABS(customers.STDDEV(orders.amount|interval=P7D))\" , abs(\"SUM(orders.ABS(orders.amount))\") as \"ABS(customers.SUM(orders.ABS(orders.amount)))\" , abs(\"SUM(orders.ABS(orders.amount)|interval=P30D)\") as \"ABS(customers.SUM(orders.ABS(orders.amount)|interval=P30D))\" , abs(\"SUM(orders.ABS(orders.amount)|interval=P7D)\") as \"ABS(customers.SUM(orders.ABS(orders.amount)|interval=P7D))\" , abs(\"SUM(orders.amount)\") as \"ABS(customers.SUM(orders.amount))\" , abs(\"SUM(orders.amount|interval=P30D)\") as \"ABS(customers.SUM(orders.amount|interval=P30D))\" , abs(\"SUM(orders.amount|interval=P7D)\") as \"ABS(customers.SUM(orders.amount|interval=P7D))\" , abs(age) as \"ABS(customers.age)\" , \"COUNT(orders.order_date)\" as \"COUNT(orders.order_date)\", \"COUNT(orders.order_date|interval=P30D)\" as \"COUNT(orders.order_date|interval=P30D)\", \"COUNT(orders.order_date|interval=P7D)\" as \"COUNT(orders.order_date|interval=P7D)\", \"COUNT(orders.order_id)\" as \"COUNT(orders.order_id)\", \"COUNT(orders.order_id|interval=P30D)\" as \"COUNT(orders.order_id|interval=P30D)\", \"COUNT(orders.order_id|interval=P7D)\" as \"COUNT(orders.order_id|interval=P7D)\", \"COUNT(orders.status)\" as \"COUNT(orders.status)\", \"COUNT(orders.status|interval=P30D)\" as \"COUNT(orders.status|interval=P30D)\", \"COUNT(orders.status|interval=P7D)\" as \"COUNT(orders.status|interval=P7D)\", \"MAX(orders.ABS(orders.amount))\" as \"MAX(orders.ABS(orders.amount))\", \"MAX(orders.ABS(orders.amount)|interval=P30D)\" as \"MAX(orders.ABS(orders.amount)|interval=P30D)\", \"MAX(orders.ABS(orders.amount)|interval=P7D)\" as \"MAX(orders.ABS(orders.amount)|interval=P7D)\", \"MAX(orders.amount)\" as \"MAX(orders.amount)\", \"MAX(orders.amount|interval=P30D)\" as \"MAX(orders.amount|interval=P30D)\", \"MAX(orders.amount|interval=P7D)\" as \"MAX(orders.amount|interval=P7D)\", \"MEAN(orders.ABS(orders.amount))\" as \"MEAN(orders.ABS(orders.amount))\", \"MEAN(orders.ABS(orders.amount)|interval=P30D)\" as \"MEAN(orders.ABS(orders.amount)|interval=P30D)\", \"MEAN(orders.ABS(orders.amount)|interval=P7D)\" as \"MEAN(orders.ABS(orders.amount)|interval=P7D)\", \"MEAN(orders.amount)\" as \"MEAN(orders.amount)\", \"MEAN(orders.amount|interval=P30D)\" as \"MEAN(orders.amount|interval=P30D)\", \"MEAN(orders.amount|interval=P7D)\" as \"MEAN(orders.amount|interval=P7D)\", \"MIN(orders.ABS(orders.amount))\" as \"MIN(orders.ABS(orders.amount))\", \"MIN(orders.ABS(orders.amount)|interval=P30D)\" as \"MIN(orders.ABS(orders.amount)|interval=P30D)\", \"MIN(orders.ABS(orders.amount)|interval=P7D)\" as \"MIN(orders.ABS(orders.amount)|interval=P7D)\", \"MIN(orders.amount)\" as \"MIN(orders.amount)\", \"MIN(orders.amount|interval=P30D)\" as \"MIN(orders.amount|interval=P30D)\", \"MIN(orders.amount|interval=P7D)\" as \"MIN(orders.amount|interval=P7D)\", \"NUNIQUE(orders.order_date)\" as \"NUNIQUE(orders.order_date)\", \"NUNIQUE(orders.order_date|interval=P30D)\" as \"NUNIQUE(orders.order_date|interval=P30D)\", \"NUNIQUE(orders.order_date|interval=P7D)\" as \"NUNIQUE(orders.order_date|interval=P7D)\", \"NUNIQUE(orders.order_id)\" as \"NUNIQUE(orders.order_id)\", \"NUNIQUE(orders.order_id|interval=P30D)\" as \"NUNIQUE(orders.order_id|interval=P30D)\", \"NUNIQUE(orders.order_id|interval=P7D)\" as \"NUNIQUE(orders.order_id|interval=P7D)\", \"NUNIQUE(orders.status)\" as \"NUNIQUE(orders.status)\", \"NUNIQUE(orders.status|interval=P30D)\" as \"NUNIQUE(orders.status|interval=P30D)\", \"NUNIQUE(orders.status|interval=P7D)\" as \"NUNIQUE(orders.status|interval=P7D)\", \"STDDEV(orders.ABS(orders.amount))\" as \"STDDEV(orders.ABS(orders.amount))\", \"STDDEV(orders.ABS(orders.amount)|interval=P30D)\" as \"STDDEV(orders.ABS(orders.amount)|interval=P30D)\", \"STDDEV(orders.ABS(orders.amount)|interval=P7D)\" as \"STDDEV(orders.ABS(orders.amount)|interval=P7D)\", \"STDDEV(orders.amount)\" as \"STDDEV(orders.amount)\", \"STDDEV(orders.amount|interval=P30D)\" as \"STDDEV(orders.amount|interval=P30D)\", \"STDDEV(orders.amount|interval=P7D)\" as \"STDDEV(orders.amount|interval=P7D)\", \"SUM(orders.ABS(orders.amount))\" as \"SUM(orders.ABS(orders.amount))\", \"SUM(orders.ABS(orders.amount)|interval=P30D)\" as \"SUM(orders.ABS(orders.amount)|interval=P30D)\", \"SUM(orders.ABS(orders.amount)|interval=P7D)\" as \"SUM(orders.ABS(orders.amount)|interval=P7D)\", \"SUM(orders.amount)\" as \"SUM(orders.amount)\", \"SUM(orders.amount|interval=P30D)\" as \"SUM(orders.amount|interval=P30D)\", \"SUM(orders.amount|interval=P7D)\" as \"SUM(orders.amount|interval=P7D)\", case when country::text = 'AU' then 1 else 0 end as \"customers.country=AU\" , case when country::text = 'CA' then 1 else 0 end as \"customers.country=CA\" , case when country::text = 'DE' then 1 else 0 end as \"customers.country=DE\" , case when country::text = 'FR' then 1 else 0 end as \"customers.country=FR\" , case when country::text = 'UK' then 1 else 0 end as \"customers.country=UK\" , case when country::text = 'US' then 1 else 0 end as \"customers.country=US\" , age as age\n", " from customers_synth _ego\n", " )\n", " \n", "\n", " select * from customers_transform\n", " ) as t\n", "\n", " order by aod.as_of_date\n", " \n", "================================================================================\n" ] } ], "source": [ "# Get the generated SQL query\n", "sql = featurizer.query\n", "\n", "print(\"Generated SQL Query:\")\n", "print(\"=\" * 80)\n", "print(sql)\n", "print(\"=\" * 80)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### SQL Structure:\n", "\n", "The query has this structure:\n", "```sql\n", "SELECT aod.as_of_date, t.*\n", "FROM as_of_dates AS aod\n", "CROSS JOIN LATERAL (\n", " WITH\n", " -- CTEs for each entity's synthesis and transformation\n", " orders_synth AS (...),\n", " orders_transform AS (...),\n", " orders_aggs_for_customers AS (...),\n", " customers_synth AS (...),\n", " customers_transform AS (...)\n", " SELECT * FROM customers_transform\n", ") AS t\n", "ORDER BY aod.as_of_date\n", "```" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.631117Z", "iopub.status.busy": "2026-06-21T19:29:05.631069Z", "iopub.status.idle": "2026-06-21T19:29:05.632765Z", "shell.execute_reply": "2026-06-21T19:29:05.632397Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of CTEs: 5\n", "\n", "CTE names:\n", " - orders_synth\n", " - orders_transform\n", " - orders_aggs_for_customers\n", " - customers_synth\n", " - customers_transform\n" ] } ], "source": [ "# Show the CTEs that were generated\n", "print(f\"Number of CTEs: {len(featurizer.ctes)}\")\n", "print(\"\\nCTE names:\")\n", "for cte in featurizer.ctes:\n", " # Extract CTE name from the query\n", " lines = cte.strip().split(\"\\n\")\n", " for line in lines:\n", " if \" as (\" in line:\n", " name = line.split(\" as (\")[0].strip().lstrip(\"-\").strip()\n", " print(f\" - {name}\")\n", " break" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 8. Available Primitives\n", "\n", "Featurizer comes with many built-in primitives. Let's explore them." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.633643Z", "iopub.status.busy": "2026-06-21T19:29:05.633575Z", "iopub.status.idle": "2026-06-21T19:29:05.635103Z", "shell.execute_reply": "2026-06-21T19:29:05.634843Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Available aggregations (69):\n", " acf_1, age_in_system, all, any, bbox_area, burstiness, cosinor_amplitude_weekly, count, cross_type_latency, cv, distance_travelled, entropy, event_rate, first_passage_time, gap_cv, gap_max, gap_mean, gap_min, gap_stddev, geometric_mean, gini, harmonic_mean, hhi, inter_event_hazard_proxy, iqr, kl_drift, kurtosis, longest_streak, markov_conditional_entropy, max, max_transition_prob, mean, mean_deviation, median, median_absolute_deviation, min, min_max_scale, mode, ngram_2_freq, ngram_3_freq, nunique, p10, p25, p75, p90, p95, p99, radius_of_gyration, range, recency, recurrence_interval, rework_count, right_censoring_indicator, sequence_entropy, skewness, spatial_std, state_volatility, stddev, sum, tenure, theil, time_in_current_state, time_span, transition_matrix_summary, trimmed_mean_10, variance, variance_ratio, wasserstein_drift, z_score\n" ] } ], "source": [ "from featurizer.primitives.utils import list_aggregations, list_transformations\n", "\n", "# List available aggregations\n", "aggs = list(list_aggregations())\n", "print(f\"Available aggregations ({len(aggs)}):\")\n", "print(f\" {', '.join(aggs)}\")" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.635858Z", "iopub.status.busy": "2026-06-21T19:29:05.635810Z", "iopub.status.idle": "2026-06-21T19:29:05.637773Z", "shell.execute_reply": "2026-06-21T19:29:05.637486Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "Available transformations (83):\n", " math: abs, exp, ln, log, sqrt, cbrt, sign, ceil, floor, trunc\n", " date: day, dow, dom, doy, year, month, hour, quarter, week, week_of_year, century\n", " rolling: rolling_iqr_14, rolling_iqr_7, rolling_mean_14, rolling_mean_3, rolling_mean_7, rolling_median_5, rolling_median_7, rolling_std_14, rolling_std_3, rolling_std_7\n", " lag: lag_1, lag_3, lag_7\n", " cumulative: cum_sum, cum_mean, cum_max, cum_min, cum_count\n" ] } ], "source": [ "# List available transformations\n", "transforms = list(list_transformations())\n", "print(f\"\\nAvailable transformations ({len(transforms)}):\")\n", "\n", "# Group by category\n", "categories = {\n", " \"math\": [\n", " \"abs\",\n", " \"exp\",\n", " \"ln\",\n", " \"log\",\n", " \"sqrt\",\n", " \"cbrt\",\n", " \"sign\",\n", " \"ceil\",\n", " \"floor\",\n", " \"trunc\",\n", " ],\n", " \"date\": [\n", " \"day\",\n", " \"dow\",\n", " \"dom\",\n", " \"doy\",\n", " \"year\",\n", " \"month\",\n", " \"hour\",\n", " \"quarter\",\n", " \"week\",\n", " \"week_of_year\",\n", " \"century\",\n", " ],\n", " \"rolling\": [t for t in transforms if \"rolling\" in t],\n", " \"lag\": [t for t in transforms if \"lag_\" in t],\n", " \"cumulative\": [\"cum_sum\", \"cum_mean\", \"cum_max\", \"cum_min\", \"cum_count\"],\n", "}\n", "\n", "for cat, items in categories.items():\n", " available = [i for i in items if i in transforms]\n", " if available:\n", " print(f\" {cat}: {', '.join(available)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 9. Summary\n", "\n", "In this tutorial, we learned:\n", "\n", "1. **Configuration**: How to define entities, relationships, and feature generation parameters in YAML\n", "2. **Entity Graph**: How Featurizer models the relationships between tables\n", "3. **Feature Synthesis**: How features are automatically generated through aggregations and transformations\n", "4. **SQL Generation**: How the planner produces CTEs and the renderer creates the final query\n", "5. **Primitives**: The available aggregation and transformation primitives\n", "\n", "### Next Steps:\n", "- Try Example 2 (Temporal Joins) for as-of join semantics\n", "- Try Example 3 (Deep Nesting) for multi-level relationships\n", "- Try Example 4 (Custom Primitives) to extend Featurizer" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2026-06-21T19:29:05.638654Z", "iopub.status.busy": "2026-06-21T19:29:05.638603Z", "iopub.status.idle": "2026-06-21T19:29:05.640234Z", "shell.execute_reply": "2026-06-21T19:29:05.639965Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Feature Generation Summary\n", "========================================\n", "Target: customers\n", "Depth: 2\n", "Intervals: P7D, P30D\n", "Total features: 107\n", "SQL query length: 18,886 characters\n", "CTEs generated: 5\n" ] } ], "source": [ "# Final summary\n", "print(\"Feature Generation Summary\")\n", "print(\"=\" * 40)\n", "print(f\"Target: {featurizer.target.alias}\")\n", "print(f\"Depth: {featurizer.max_depth}\")\n", "print(f\"Intervals: {', '.join(featurizer.intervals)}\")\n", "print(f\"Total features: {len(target_features)}\")\n", "print(f\"SQL query length: {len(sql):,} characters\")\n", "print(f\"CTEs generated: {len(featurizer.ctes)}\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.13" } }, "nbformat": 4, "nbformat_minor": 4 }