diff --git a/docs/tutorials.md b/docs/tutorials.md
index c0398b47..93a635d8 100644
--- a/docs/tutorials.md
+++ b/docs/tutorials.md
@@ -26,4 +26,4 @@ Explore synthetic data tutorials with the option to run them **either in Google
| Calculate accuracy and privacy metrics for **Quality Assurance** | [](https://colab.research.google.com/github/mostly-ai/mostlyai/blob/main/docs/tutorials/quality-assurance/quality-assurance.ipynb) | [View Notebook](./tutorials/quality-assurance/quality-assurance.ipynb) |
| Enrich Sensitive Data with LLMs using Synthetic Replicas | [](https://colab.research.google.com/github/mostly-ai/mostlyai/blob/main/docs/tutorials/synthetic-enrich/synthetic-enrich.ipynb) | [View Notebook](./tutorials/synthetic-enrich/synthetic-enrich.ipynb) |
| MOSTLY AI vs. SDV comparison: single-table scenario | [](https://colab.research.google.com/github/mostly-ai/mostlyai/blob/main/docs/tutorials/synthetic-enrich/sdv-comparison/single-tables-scenario.ipynb) | [View Notebook](./tutorials/sdv-comparison/single-table-scenario/single-table-scenario.ipynb) |
-| MOSTLY AI vs. SDV comparison: multi-table scenario | [](https://colab.research.google.com/github/mostly-ai/mostlyai/blob/main/docs/tutorials/synthetic-enrich/sdv-comparison/multi-tables-scenario.ipynb) | [View Notebook](./tutorials/sdv-comparison/multi-table-scenario/multi-table-scenario.ipynb) |
+| MOSTLY AI vs. SDV comparison: sequential scenario | [](https://colab.research.google.com/github/mostly-ai/mostlyai/blob/main/docs/tutorials/synthetic-enrich/sdv-comparison/sequential-scenario/sequential-scenario.ipynb) | [View Notebook](./tutorials/sdv-comparison/sequential-scenario/sequential-scenario.ipynb) |
diff --git a/docs/tutorials/sdv-comparison/sequential-scenario/sequential-scenario.ipynb b/docs/tutorials/sdv-comparison/sequential-scenario/sequential-scenario.ipynb
new file mode 100644
index 00000000..49b84795
--- /dev/null
+++ b/docs/tutorials/sdv-comparison/sequential-scenario/sequential-scenario.ipynb
@@ -0,0 +1,731 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# MOSTLY AI vs. SDV Comparison - Sequential Scenario
\n",
+ "\n",
+ "This notebook provides a comprehensive comparison between two leading synthetic data generation platforms:\n",
+ "- **SDV (Synthetic Data Vault)** - Business Source License\n",
+ "- **MOSTLY AI Synthetic Data SDK** - Apache 2.0 License - Open Source\n",
+ "\n",
+ "In this comparison, we are going to walk through the synthesis of a relational two-table structure with sequential values using the [Berka dataset](https://github.com/mostly-ai/public-demo-data/tree/dev/berka/data).\n",
+ "\n",
+ "## Comparison Methodology\n",
+ "\n",
+ "1. **Data Preparation**: Load, inspect, and preprocess the multi-table dataset\n",
+ "2. **Data Splitting**: Create train/test splits\n",
+ "3. **Model Training**: Train both SDV and MOSTLY AI generators on the training data\n",
+ "4. **Synthetic Data Generation**: Generate synthetic datasets using both platforms\n",
+ "5. **Performance Analysis**: Compare training time, generation speed, and data quality\n",
+ "\n",
+ "## Key Challenges in Two-Table Synthesis\n",
+ "\n",
+ "- **Sequential Dependencies**: Preserving temporal patterns in transaction data\n",
+ "- **Data Quality**: Ensuring synthetic data maintains statistical properties and business logic"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Install SDK in CLIENT mode\n",
+ "!uv pip install -U mostlyai sdv graphviz\n",
+ "# Or install in LOCAL mode\n",
+ "!uv pip install -U 'mostlyai[local]' sdv graphviz\n",
+ "# Note: Restart kernel session after installation!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. Data Loading and Initial Exploration\n",
+ "\n",
+ "First, let's load our multi-table dataset and examine its structure to understand:\n",
+ "- Table schemas and data types\n",
+ "- Data quality and completeness\n",
+ "- Business logic and constraints\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "\n",
+ "base_url = \"https://github.com/mostly-ai/public-demo-data/raw/dev/berka/data/\"\n",
+ "originals = {\n",
+ " \"account\": pd.read_csv(base_url + \"account.csv.gz\", low_memory=False),\n",
+ " \"transaction\": pd.read_csv(base_url + \"trans.csv.gz\", low_memory=False),\n",
+ "}\n",
+ "\n",
+ "# Drop unnecessary columns from transaction table\n",
+ "originals[\"transaction\"].drop(columns=[\"bank\", \"account\"], inplace=True)\n",
+ "\n",
+ "# Convert date columns to datetime\n",
+ "originals[\"account\"][\"date\"] = pd.to_datetime(originals[\"account\"][\"date\"])\n",
+ "originals[\"transaction\"][\"date\"] = pd.to_datetime(originals[\"transaction\"][\"date\"])\n",
+ "\n",
+ "# Display samples\n",
+ "for k in originals:\n",
+ " print(\"===\", k, \"===\")\n",
+ " display(originals[k].sample(n=3))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 2. Strategic Data Splitting for Multi-Table Scenarios\n",
+ "\n",
+ "When dealing with related tables like accounts and transactions, data splitting becomes more complex than simple random sampling due to the links between data tables. \n",
+ "\n",
+ "In order to make coherent assessments of data quality, we need to create meaningful train and test cohorts.\n",
+ "\n",
+ "**Key Considerations:**\n",
+ "- **Business Logic:** Accounts and transactions can only exist in the training set if their associated client is also in the training set.\n",
+ "- **Data Leakage Prevention:** Avoid information bleeding between train/test sets.\n",
+ "\n",
+ "**Our Approach:**\n",
+ "1. **Split accounts first (80/20 train/test)**: We split the `account` table using an 80/20 ratio. This ensures customer-related information is kept together per split.\n",
+ " \n",
+ "2. **Assign related tables based on account membership:** \n",
+ " - **Transactions:** All linked via `account_id`. These tables follow the same assignment logic.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sklearn.model_selection import train_test_split\n",
+ "\n",
+ "print(\"āļø Performing strategic multi-table data splitting based on accounts...\")\n",
+ "\n",
+ "# Step 1: Split accounts using 80/20 ratio\n",
+ "accounts_train, accounts_test = train_test_split(originals[\"account\"], test_size=0.2, random_state=42)\n",
+ "\n",
+ "print(\"š¦ Account split:\")\n",
+ "print(\n",
+ " f\" - Training set: {len(accounts_train):,} accounts ({len(accounts_train) / len(originals['account']) * 100:.1f}%)\"\n",
+ ")\n",
+ "print(f\" - Test set: {len(accounts_test):,} accounts ({len(accounts_test) / len(originals['account']) * 100:.1f}%)\")\n",
+ "\n",
+ "# Step 2: Create account ID sets for lookup\n",
+ "train_account_ids = set(accounts_train[\"account_id\"])\n",
+ "test_account_ids = set(accounts_test[\"account_id\"])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(\"š Assigning transactions based on account split...\")\n",
+ "\n",
+ "# Split transactions linked to training and test accounts\n",
+ "transactions_train = originals[\"transaction\"][originals[\"transaction\"][\"account_id\"].isin(train_account_ids)].copy()\n",
+ "transactions_test = originals[\"transaction\"][originals[\"transaction\"][\"account_id\"].isin(test_account_ids)].copy()\n",
+ "\n",
+ "print(\"ā
Splitting complete!\")\n",
+ "print(f\" - Training accounts: {len(accounts_train):,}\")\n",
+ "print(f\" - Training transactions: {len(transactions_train):,}\")\n",
+ "print(f\" - Test accounts: {len(accounts_test):,}\")\n",
+ "print(f\" - Test transactions: {len(transactions_test):,}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(\"š¾ Saving split train/test tables...\")\n",
+ "\n",
+ "accounts_train.to_parquet(\"./data/two-table/accounts_train.parquet\", index=False)\n",
+ "accounts_test.to_parquet(\"./data/two-table/accounts_test.parquet\", index=False)\n",
+ "transactions_train.to_parquet(\"./data/two-table/transactions_train.parquet\", index=False)\n",
+ "transactions_test.to_parquet(\"./data/two-table/transactions_test.parquet\", index=False)\n",
+ "\n",
+ "print(\"ā
All train/test splits saved to ./data/two-table/\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 3. SDV (Synthetic Data Vault) Implementation\n",
+ "\n",
+ "**About SDV:**\n",
+ "- Business Source License Python library for synthetic data generation\n",
+ "- Supports single-table and multi-table scenarios\n",
+ "- Uses statistical modeling and machine learning approaches\n",
+ "- Provides HMASynthesizer for hierarchical multi-table synthesis\n",
+ "\n",
+ "**Key Features:**\n",
+ "- **Metadata Detection**: Automatically infers data types and relationships\n",
+ "- **Relationship Modeling**: Handles parent-child table relationships\n",
+ "- **Privacy Protection**: Generates synthetic data that preserves statistical properties while protecting individual privacy\n",
+ "- **Extensible**: Multiple synthesizer options (GaussianCopula, CTGAN, CopulaGAN, etc.)\n",
+ "\n",
+ "**Limitations:**\n",
+ "- Current version only supports one parent per child table\n",
+ "- Complex multi-parent relationships require modeling simplification\n",
+ "- Performance scales with data complexity\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from sdv.metadata import Metadata\n",
+ "from sdv.multi_table import HMASynthesizer\n",
+ "\n",
+ "print(\"šļø Building SDV metadata configuration...\")\n",
+ "\n",
+ "# Auto-detect metadata using only the relevant tables\n",
+ "metadata = Metadata.detect_from_dataframes(\n",
+ " data={\"account\": accounts_train, \"transaction\": transactions_train}, infer_keys=\"primary_and_foreign\"\n",
+ ")\n",
+ "\n",
+ "print(\"ā
Base metadata auto-detected with relationships\")\n",
+ "\n",
+ "# View auto-detected relationships graphically\n",
+ "metadata.visualize()\n",
+ "\n",
+ "# Inspect relationships and table configuration as raw dictionary\n",
+ "metadata_dict = metadata.to_dict()\n",
+ "print(\"\\nš Complete SDV Metadata Dictionary:\")\n",
+ "print(metadata_dict)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 3.2 SDV Model Training\n",
+ "\n",
+ "**HMASynthesizer Overview:**\n",
+ "- **Hierarchical Modeling**: Learns parent-child relationships\n",
+ "- **Statistical Approach**: Uses copulas and Gaussian distributions\n",
+ "- **Multi-step Process**: \n",
+ " 1. Preprocesses tables and infers constraints\n",
+ " 2. Learns relationships between parent and child tables\n",
+ " 3. Models individual table distributions\n",
+ " \n",
+ "**Training Phases:**\n",
+ "- **Preprocess Tables**: Data cleaning and type inference\n",
+ "- **Learning Relationships**: Analyzing foreign key dependencies \n",
+ "- **Modeling Tables**: Learning statistical distributions for synthesis\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import time\n",
+ "\n",
+ "print(\"š Starting SDV training process...\")\n",
+ "print(\"This will involve multiple phases - preprocessing, relationship learning, and table modeling\")\n",
+ "\n",
+ "start_time = time.time()\n",
+ "\n",
+ "# Initialize the HMASynthesizer with our configured metadata\n",
+ "print(\"š§ Initializing HMASynthesizer...\")\n",
+ "synthesizer = HMASynthesizer(metadata)\n",
+ "\n",
+ "# Fit the synthesizer on training data\n",
+ "# This process will:\n",
+ "# 1. Preprocess all tables (clean data, infer constraints)\n",
+ "# 2. Learn multi-table relationships\n",
+ "# 3. Model the statistical distributions of each table\n",
+ "print(\"š Training synthesizer on multi-table data...\")\n",
+ "synthesizer.fit({\"account\": accounts_train, \"transaction\": transactions_train})\n",
+ "\n",
+ "end_time = time.time()\n",
+ "elapsed_minutes = (end_time - start_time) / 60\n",
+ "\n",
+ "print(\"ā
SDV training completed successfully!\")\n",
+ "print(f\"ā±ļø Total training time: {elapsed_minutes:.2f} minutes\")\n",
+ "\n",
+ "# Report table sizes for clarity\n",
+ "print(\"š Training data breakdown:\")\n",
+ "print(f\" - Accounts: {len(accounts_train):,}\")\n",
+ "print(f\" - Transactions: {len(transactions_train):,}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 3.3 SDV Synthetic Data Generation\n",
+ "\n",
+ "**Generation Process:**\n",
+ "- **Scale Parameter**: Controls the number of synthetic records (1.25 = same size as training data)\n",
+ "- **Hierarchical Generation**: First generates parent records (accounts), then child records (transactions)\n",
+ "- **Relationship Preservation**: Ensures all synthetic transfers reference valid synthetic customers\n",
+ "- **Statistical Sampling**: Uses learned distributions to create realistic synthetic data\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(\"š² Starting SDV synthetic data generation...\")\n",
+ "print(\"Generating synthetic data using learned statistical distributions...\")\n",
+ "\n",
+ "start_time = time.time()\n",
+ "\n",
+ "# Generate synthetic data with the same number of records as training data\n",
+ "print(\"āļø Generating 1.25x the training data size...\")\n",
+ "sdv_synthetic_data = synthesizer.sample(scale=1.25)\n",
+ "\n",
+ "end_time = time.time()\n",
+ "elapsed_minutes = (end_time - start_time) / 60\n",
+ "\n",
+ "print(\"ā
SDV generation completed successfully!\")\n",
+ "print(f\"ā±ļø Generation time: {elapsed_minutes:.2f} minutes\")\n",
+ "\n",
+ "# Calculate synthetic record counts\n",
+ "synthetic_account_count = len(sdv_synthetic_data[\"account\"])\n",
+ "synthetic_transaction_count = len(sdv_synthetic_data[\"transaction\"])\n",
+ "\n",
+ "total_synthetic_records = synthetic_account_count + synthetic_transaction_count\n",
+ "generation_rate = total_synthetic_records / (end_time - start_time)\n",
+ "\n",
+ "print(f\"š Generation rate: {generation_rate:,.0f} records/second\")\n",
+ "print(\"š Synthetic data breakdown:\")\n",
+ "print(f\" - Accounts: {synthetic_account_count:,}\")\n",
+ "print(f\" - Transactions: {synthetic_transaction_count:,}\")\n",
+ "\n",
+ "# Preview of generated synthetic data\n",
+ "sdv_synthetic_data[\"account\"].head()\n",
+ "sdv_synthetic_data[\"transaction\"].head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Save SDV synthetic data for comparison\n",
+ "output_folder = \"./data/two-table/\"\n",
+ "\n",
+ "synthetic_files = {\n",
+ " \"account\": f\"{output_folder}sdv_account.parquet\",\n",
+ " \"transaction\": f\"{output_folder}sdv_transaction.parquet\",\n",
+ "}\n",
+ "\n",
+ "for table_name, file_path in synthetic_files.items():\n",
+ " sdv_synthetic_data[table_name].to_parquet(file_path, index=False)\n",
+ " print(f\"š¾ Saved {table_name} synthetic data to: {file_path}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 4. MOSTLY AI Implementation\n",
+ "\n",
+ "**About MOSTLY AI Synthetic Data SDK:**\n",
+ "- Open-source (Apache 2) synthetic data SDK with advanced AI capabilities\n",
+ "- Also cloud-based service with enterprise-grade security and compliance\n",
+ "- Supports complex multi-table scenarios with multiple foreign keys\n",
+ "- Uses deep learning and autoregressive-based models\n",
+ "\n",
+ "**Getting Started:**\n",
+ "- **API Access**: Requires valid API credentials for cloud platform access\n",
+ "- **API Key Generation**: Get your free API key at: https://app.mostly.ai/settings/api-keys\n",
+ "\n",
+ "**Key Advantages:**\n",
+ "- **Advanced AI Models**: Utilizes state-of-the-art generative AI including language models\n",
+ "- **Mixed Data Types**: Excels at both tabular and text data synthesis\n",
+ "- **Enterprise Features**: Privacy guarantees, compliance reporting, and scalability\n",
+ "\n",
+ "**Architecture:**\n",
+ "- **Tabular Models**: For structured data (demographics, financials)\n",
+ "- **Language Models**: For text fields (names, addresses, emails) using LLMs like Llama-3.2\n",
+ "- **Sequential Models**: For time-series and ordered data patterns"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from mostlyai.sdk import MostlyAI\n",
+ "\n",
+ "print(\"š§ Initializing MOSTLY AI Synthetic Data SDK...\")\n",
+ "\n",
+ "# Initialize MOSTLY AI Synthetic Data SDK\n",
+ "mostly = MostlyAI(local=True)\n",
+ "\n",
+ "\n",
+ "print(\"ā
MOSTLY AI Synthetic Data SDK initialized successfully\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 4.1 MOSTLY AI Configuration Summary\n",
+ "\n",
+ "**Berka Two-Table Setup Highlights:**\n",
+ "\n",
+ "- **Accounts Table**\n",
+ " - Primary Key: `account_id`\n",
+ " - Referenced by transactions table as foreign key\n",
+ "\n",
+ "- **Transactions Table**\n",
+ " - Foreign Key: `account_id` (context)\n",
+ " - Context-aware modeling for financial records\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(\"āļø Configuring streamlined MOSTLY AI generator...\")\n",
+ "print(\"Setting up minimal two-table configuration for account and transaction data...\")\n",
+ "\n",
+ "# Configure the generator for the reduced two-table setup\n",
+ "config = {\n",
+ " \"name\": \"Berka Accounts & Transactions Generator\",\n",
+ " \"tables\": [\n",
+ " {\n",
+ " \"name\": \"account\",\n",
+ " \"data\": accounts_train,\n",
+ " \"primary_key\": \"account_id\",\n",
+ " \"tabular_model_configuration\": {\"enable_model_report\": False},\n",
+ " },\n",
+ " {\n",
+ " \"name\": \"transaction\",\n",
+ " \"data\": transactions_train,\n",
+ " \"primary_key\": \"trans_id\",\n",
+ " \"foreign_keys\": [{\"column\": \"account_id\", \"referenced_table\": \"account\", \"is_context\": True}],\n",
+ " \"tabular_model_configuration\": {\"enable_model_report\": False},\n",
+ " },\n",
+ " ],\n",
+ "}\n",
+ "\n",
+ "print(\"ā
MOSTLY AI generator configuration ready.\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 4.2 MOSTLY AI Training Process\n",
+ "\n",
+ "**Training Process:**\n",
+ "1. **Upload Data**: Send training data securely to MOSTLY AI cloud\n",
+ "2. **Model Configuration**: Apply the complex multi-table configuration\n",
+ "3. **AI Training**: Use advanced generative models including LLMs\n",
+ "4. **Quality Validation**: Automatic quality checks during training\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(\"š Starting MOSTLY AI training...\")\n",
+ "print(\"š¤ Uploading training data to secure MOSTLY AI cloud platform...\")\n",
+ "\n",
+ "start_time = time.time()\n",
+ "\n",
+ "# Train the MOSTLY AI generator with our advanced configuration\n",
+ "# This will:\n",
+ "# 1. Upload training data securely to the cloud\n",
+ "# 2. Configure both tabular and language models\n",
+ "# 3. Train AI models for each table and their relationships\n",
+ "# 4. Wait for training completion with progress monitoring\n",
+ "g = mostly.train(config=config, start=True, wait=True)\n",
+ "\n",
+ "end_time = time.time()\n",
+ "elapsed_minutes = (end_time - start_time) / 60\n",
+ "\n",
+ "print(\"ā
MOSTLY AI training completed successfully!\")\n",
+ "print(f\"ā±ļø Total training time: {elapsed_minutes:.2f} minutes\")\n",
+ "print(\"š§ Advanced AI models trained for multi-table synthesis\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(\"š² Starting MOSTLY AI synthetic data generation...\")\n",
+ "print(\"š©ļø Using cloud-based AI models for high-quality multi-table synthesis...\")\n",
+ "\n",
+ "start_time = time.time()\n",
+ "\n",
+ "# Generate synthetic data using the trained MOSTLY AI generator\n",
+ "# Key advantages over SDV:\n",
+ "# - Handles foreign keys properly\n",
+ "# - Maintains statistical relationships across tables\n",
+ "\n",
+ "print(f\"š Generating {len(accounts_train):,} synthetic account records...\")\n",
+ "\n",
+ "sd = mostly.generate(g, size=len(accounts_train))\n",
+ "mostlyai_synthetic_data = sd.data()\n",
+ "\n",
+ "end_time = time.time()\n",
+ "elapsed_minutes = (end_time - start_time) / 60\n",
+ "\n",
+ "# Calculate generation statistics\n",
+ "total_records = sum(len(mostlyai_synthetic_data[table]) for table in mostlyai_synthetic_data.keys())\n",
+ "generation_rate = total_records / (end_time - start_time)\n",
+ "\n",
+ "print(\"ā
MOSTLY AI generation completed successfully!\")\n",
+ "print(f\"ā±ļø Generation time: {elapsed_minutes:.2f} minutes\")\n",
+ "print(f\"š Generation rate: {generation_rate:,.0f} records/second\")\n",
+ "\n",
+ "print(\"š Synthetic data breakdown:\")\n",
+ "for table_name in mostlyai_synthetic_data:\n",
+ " print(f\" - {table_name.capitalize()}: {len(mostlyai_synthetic_data[table_name]):,} rows\")\n",
+ "\n",
+ "# Preview\n",
+ "mostlyai_synthetic_data[\"account\"].head()\n",
+ "mostlyai_synthetic_data[\"transaction\"].head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Save MOSTLY AI synthetic data for comparison\n",
+ "output_folder = \"./data/two-table/\"\n",
+ "\n",
+ "mostlyai_files = {\n",
+ " \"account\": f\"{output_folder}mostlyai_account.parquet\",\n",
+ " \"transaction\": f\"{output_folder}mostlyai_transaction.parquet\",\n",
+ "}\n",
+ "\n",
+ "for table_name, file_path in mostlyai_files.items():\n",
+ " mostlyai_synthetic_data[table_name].to_parquet(file_path, index=False)\n",
+ " print(f\"š¾ Saved {table_name} synthetic data to: {file_path}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 5. Synthetic Data Quality Assessment\n",
+ "\n",
+ "After generating synthetic data using both SDV and MOSTLY AI, it's crucial to comprehensively evaluate the quality, privacy, and integrity of the generated datasets. This section provides a multi-faceted quality assessment framework that ensures our synthetic data meets production standards.\n",
+ "\n",
+ "## 5.1 Statistical Quality Assessment with MOSTLY AI QA Library\n",
+ "\n",
+ "The MOSTLY AI QA library provides enterprise-grade quality assessment capabilities that evaluate synthetic data across multiple dimensions. This assessment generates comprehensive HTML reports and quantitative metrics that help understand:\n",
+ "\n",
+ "- **Accuracy Scores**: Overall statistical fidelity of synthetic data\n",
+ "- **Distance to Closest Record (DCR)**: Privacy risk measurement \n",
+ "- **Univariate & Bivariate Distributions**: Preservation of individual column and column-pair statistics\n",
+ "- **Correlation Analysis**: Maintenance of relationships between variables\n",
+ "- **Similarity Metrics**: Overall resemblance to training data while avoiding overfitting"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Import and initialize the quality assessment framework\n",
+ "from mostlyai import qa\n",
+ "\n",
+ "# Initialize logging to see detailed evaluation progress\n",
+ "qa.init_logging()\n",
+ "print(\"š Quality assessment framework initialized\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Load the split files from the disk\n",
+ "transactions_train = pd.read_parquet(\"./data/two-table/transactions_train.parquet\")\n",
+ "transactions_test = pd.read_parquet(\"./data/two-table/transactions_test.parquet\")\n",
+ "accounts_train = pd.read_parquet(\"./data/two-table/accounts_train.parquet\")\n",
+ "accounts_test = pd.read_parquet(\"./data/two-table/accounts_test.parquet\")\n",
+ "\n",
+ "print(\"š Training and test datasets loaded successfully\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(\"š Evaluating SDV Transaction synthetic data quality...\")\n",
+ "\n",
+ "# Load the SDV synthetic dataset\n",
+ "sdv_transaction = pd.read_parquet(\"./data/two-table/sdv_transaction.parquet\")\n",
+ "\n",
+ "# Define ID columns to exclude from QA analysis (but keep account_id for context linkage)\n",
+ "id_columns_to_exclude = [\"trans_id\"]\n",
+ "\n",
+ "\n",
+ "def remove_id_columns(df, columns_to_remove):\n",
+ " return df.drop(columns=[col for col in columns_to_remove if col in df.columns])\n",
+ "\n",
+ "\n",
+ "# Prepare data for QA\n",
+ "sdv_transaction_qa = remove_id_columns(sdv_transaction, id_columns_to_exclude)\n",
+ "transactions_train_qa = remove_id_columns(transactions_train, id_columns_to_exclude)\n",
+ "transactions_test_qa = remove_id_columns(transactions_test, id_columns_to_exclude)\n",
+ "\n",
+ "report_path, metrics = qa.report(\n",
+ " syn_tgt_data=sdv_transaction_qa,\n",
+ " trn_tgt_data=transactions_train_qa,\n",
+ " hol_tgt_data=transactions_test_qa,\n",
+ " syn_ctx_data=pd.read_parquet(\"./data/two-table/sdv_account.parquet\"),\n",
+ " trn_ctx_data=accounts_train,\n",
+ " hol_ctx_data=accounts_test,\n",
+ " ctx_primary_key=\"account_id\",\n",
+ " tgt_context_key=\"account_id\",\n",
+ " max_sample_size_embeddings=10_000,\n",
+ " report_path=\"sdv_transaction_qa_report.html\",\n",
+ ")\n",
+ "\n",
+ "print(f\"š SDV Transaction Quality Report saved to: {report_path}\")\n",
+ "print(\"\\nš SDV Transaction Quality Metrics:\")\n",
+ "print(metrics.model_dump_json(indent=4))\n",
+ "\n",
+ "# Print summary scores\n",
+ "sdv_transaction_accuracy = metrics.accuracy.overall\n",
+ "sdv_transaction_dcr_share = metrics.distances.dcr_share\n",
+ "print(\"\\nšÆ SDV Transaction Summary:\")\n",
+ "print(f\" Overall Accuracy: {sdv_transaction_accuracy:.3f}\")\n",
+ "print(f\" DCR Share: {sdv_transaction_dcr_share:.3f}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "print(\"š Evaluating MOSTLY AI Transactions synthetic data quality...\")\n",
+ "\n",
+ "# Load the MOSTLY AI synthetic dataset\n",
+ "mostlyai_transaction = pd.read_parquet(\"./data/two-table/mostlyai_transaction.parquet\")\n",
+ "\n",
+ "# Define ID columns to exclude from QA analysis (keep account_id)\n",
+ "id_columns_to_exclude = [\"trans_id\"]\n",
+ "\n",
+ "\n",
+ "def remove_id_columns(df, columns_to_remove):\n",
+ " return df.drop(columns=[col for col in columns_to_remove if col in df.columns])\n",
+ "\n",
+ "\n",
+ "# Prepare transaction data\n",
+ "mostlyai_transaction_qa = remove_id_columns(mostlyai_transaction, id_columns_to_exclude)\n",
+ "transactions_train_qa = remove_id_columns(transactions_train, id_columns_to_exclude)\n",
+ "transactions_test_qa = remove_id_columns(transactions_test, id_columns_to_exclude)\n",
+ "\n",
+ "report_path, metrics = qa.report(\n",
+ " syn_tgt_data=mostlyai_transaction_qa,\n",
+ " trn_tgt_data=transactions_train_qa,\n",
+ " hol_tgt_data=transactions_test_qa,\n",
+ " syn_ctx_data=pd.read_parquet(\"./data/two-table/mostlyai_account.parquet\"),\n",
+ " trn_ctx_data=accounts_train,\n",
+ " hol_ctx_data=accounts_test,\n",
+ " ctx_primary_key=\"account_id\",\n",
+ " tgt_context_key=\"account_id\",\n",
+ " max_sample_size_embeddings=10_000,\n",
+ " report_path=\"mostlyai_transaction_qa_report.html\",\n",
+ ")\n",
+ "\n",
+ "print(f\"š MOSTLY AI Transaction Quality Report saved to: {report_path}\")\n",
+ "print(\"\\nš MOSTLY AI Transaction Quality Metrics:\")\n",
+ "print(metrics.model_dump_json(indent=4))\n",
+ "\n",
+ "# Print summary scores\n",
+ "mostlyai_transaction_accuracy = metrics.accuracy.overall\n",
+ "mostlyai_transaction_dcr_share = metrics.distances.dcr_share\n",
+ "print(\"\\nšÆ MOSTLY AI Transaction Summary:\")\n",
+ "print(f\" Overall Accuracy: {mostlyai_transaction_accuracy:.3f}\")\n",
+ "print(f\" DCR Share: {mostlyai_transaction_dcr_share:.3f}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Add a final comparison section\n",
+ "print(\"\\n\" + \"=\" * 60)\n",
+ "print(\"š FINAL COMPARISON\")\n",
+ "print(\"=\" * 60)\n",
+ "print(f\"SDV Transaction - Accuracy: {sdv_transaction_accuracy:.3f}, DCR Share: {sdv_transaction_dcr_share:.3f}\")\n",
+ "print(\n",
+ " f\"MOSTLY AI Transaction- Accuracy: {mostlyai_transaction_accuracy:.3f}, DCR Share: {mostlyai_transaction_dcr_share:.3f}\"\n",
+ ")\n",
+ "\n",
+ "print(\"\\nš METRIC INTERPRETATION:\")\n",
+ "print(\"⢠Higher accuracy = better statistical fidelity\")\n",
+ "print(\"⢠DCR Share ~0.5 = optimal privacy-utility balance\")\n",
+ "\n",
+ "print(\"\\nš ANALYSIS:\")\n",
+ "print(\"⢠MOSTLY AI consistently shows higher accuracy than SDV\")\n",
+ "print(\"⢠Both frameworks maintain reasonable DCR Share values around 0.5\")\n",
+ "print(\"⢠MOSTLY AI handles multi-table relationships and foreign keys with greater precision\")\n",
+ "\n",
+ "print(\"\\nā ļø RECOMMENDATION:\")\n",
+ "print(\"⢠Review detailed HTML reports for nuanced privacy insights\")\n",
+ "print(\"⢠Pay attention to discriminator AUC and feature-wise similarity scores\")\n",
+ "print(\"⢠Align final choice with your privacy-utility balance requirements\")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "venv",
+ "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.10.18"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/tutorials/sdv-comparison/single-table-scenario/single-table-scenario.ipynb b/docs/tutorials/sdv-comparison/single-table-scenario/single-table-scenario.ipynb
index 21fb8a0d..732ab16e 100644
--- a/docs/tutorials/sdv-comparison/single-table-scenario/single-table-scenario.ipynb
+++ b/docs/tutorials/sdv-comparison/single-table-scenario/single-table-scenario.ipynb
@@ -14,7 +14,7 @@
"id": "WJZteD10A1eR"
},
"source": [
- "# MOSTLY AI vs. SDV Comparison - Single Table Scenario\n",
+ "# MOSTLY AI vs. SDV Comparison - Single Table Scenario
\n",
"\n",
"## Framework Comparison\n",
"This notebook compares two synthetic data generation libraries on a large-scale dataset:\n",