diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/.gitignore b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/.gitignore new file mode 100644 index 000000000..9df6afb18 --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/.gitignore @@ -0,0 +1,4 @@ +.ipynb_checkpoints/ +notebooks/.ipynb_checkpoints/ +__pycache__/ +.DS_Store diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/Dockerfile b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/Dockerfile new file mode 100644 index 000000000..b0f1c7155 --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["python", "run_project_check.py"] diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/README.md b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/README.md new file mode 100644 index 000000000..963e4a667 --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/README.md @@ -0,0 +1,333 @@ +# DATA605 Spring 2026: Synthetic Data Vault Privacy Classification Project + +## Project Summary + +This project explores how **Synthetic Data Vault (SDV)** can be used to generate synthetic tabular data for a privacy-preserving machine learning workflow. + +The main idea of the project is to check whether synthetic data can preserve enough useful patterns from a real dataset to train classification models. I used the **Adult Income dataset**, generated synthetic training data using SDV, evaluated the quality of the synthetic data, and compared models trained on real data versus synthetic data. + +The project is structured like a small tutorial so that someone new to SDV can understand the basic workflow and reproduce the results. + +--- + +## Why This Project Matters + +Real-world datasets often contain sensitive information, especially when they include demographic, financial, medical, or personal records. In many situations, sharing or using the original dataset directly may create privacy concerns. + +Synthetic data can help by creating artificial records that follow similar statistical patterns as the real data, without directly exposing the original records. However, synthetic data should not be assumed to be useful or safe automatically. It needs to be evaluated carefully. + +This project focuses on one important question: + +> If we train a machine learning model on synthetic data, can it still perform well on real unseen data? + +--- + +## Dataset + +This project uses the **Adult Income dataset**, a real-world tabular dataset commonly used for classification tasks. + +The dataset includes features such as: + +- age +- workclass +- education +- marital status +- occupation +- relationship +- race +- sex +- capital gain +- capital loss +- hours worked per week +- native country + +The target variable is income class: + +- `<=50K` +- `>50K` + +This makes the project a **binary classification problem**. + +I selected this dataset because it contains both numerical and categorical variables, which makes it a good example for testing synthetic tabular data generation. + +--- + +## Project Objective + +The objective of this project is to build an end-to-end workflow using SDV. + +The project includes: + +- loading and cleaning a real dataset +- exploring the dataset through basic EDA +- generating synthetic data using SDV +- evaluating synthetic data quality using SDMetrics +- training machine learning models on real and synthetic data +- comparing model performance using classification metrics +- discussing limitations and possible improvements + +The main experiment is: + +> Train models on synthetic data and evaluate them on real test data. + +This helps test whether the synthetic data preserves useful predictive patterns from the original dataset. + +--- + +## Repository Structure + +```text +DATA605_Spring2026_Synthetic_Data_Vault/ +│ +├── README.md +│ Main project documentation. +│ +├── requirements.txt +│ Python libraries required to run the project. +│ +├── Dockerfile +│ Docker setup for reproducible execution. +│ +├── docker_build.sh +│ Script to build the Docker image. +│ +├── docker_run.sh +│ Script to run the Docker container. +│ +├── run_project_check.py +│ Script used by Docker to verify that important files and outputs exist. +│ +├── synthetic_data_vault_utils.py +│ Helper functions for data cleaning, splitting, model evaluation, and saving results. +│ +├── notebooks/ +│ ├── Synthetic_Data_Vault.ipynb +│ │ Main notebook with the full project workflow. +│ │ +│ └── synthetic_data_vault.API.ipynb +│ Short API-style notebook explaining the basic SDV workflow. +│ +└── outputs/ + ├── model_comparison_results.csv + │ Initial model comparison results. + │ + └── final_model_comparison_results.csv + Final model results including the tuning experiment. +``` + +--- + +## How to Run the Project + +Create and activate a Python environment: + +```bash +conda create -n sdv_project python=3.11 -y +conda activate sdv_project +``` + +Install the required libraries: + +```bash +pip install -r requirements.txt +``` + +Start Jupyter Notebook: + +```bash +jupyter notebook +``` + +Open the main notebook: + +```text +notebooks/Synthetic_Data_Vault.ipynb +``` + +Run the notebook from top to bottom. + +--- + +## Docker Execution + +This project includes Docker files to make the project easier to reproduce. + +Build the Docker image: + +```bash +./docker_build.sh +``` + +Run the Docker image: + +```bash +./docker_run.sh +``` + +The Docker run command executes: + +```text +run_project_check.py +``` + +This script checks that the important project files and output results are available. + +A successful Docker run ends with: + +```text +Project check completed successfully. +``` + +--- + +## Methodology + +The project starts by loading the Adult Income dataset and cleaning missing or inconsistent values. After cleaning, I performed basic exploratory data analysis to understand the target distribution, feature types, and general dataset structure. + +The real dataset was then split into training and testing sets. This step is important because SDV was trained only on the training data, while the real test data was kept separate for final evaluation. This avoids data leakage and makes the comparison more fair. + +For synthetic data generation, I used SDV’s **Gaussian Copula Synthesizer**. I selected this synthesizer because it is stable, efficient, and suitable for a tutorial-style single-table project. + +After generating the synthetic data, I used **SDMetrics** to evaluate how similar the synthetic data was to the real training data. Finally, I trained classification models on real and synthetic data and evaluated all models on the same real test set. + +--- + +## Synthetic Data Quality Results + +The SDMetrics quality report produced the following results: + +| Metric | Score | +|---|---:| +| Column Shapes | 90.31% | +| Column Pair Trends | 75.09% | +| Overall Quality Score | 82.70% | + +The **Column Shapes** score shows that the synthetic data preserved individual column distributions fairly well. This means the generated data followed many of the same single-column patterns as the original training data. + +The **Column Pair Trends** score was lower, which means relationships between pairs of columns were harder to preserve. This is important because machine learning models often rely on relationships between features, not just individual column distributions. + +Overall, the synthetic data quality was reasonably strong, but not perfect. + +--- + +## Machine Learning Results + +The models were evaluated using: + +- accuracy +- precision +- recall +- F1-score + +All models were tested on the same real test set. + +| Model | Training Data | Accuracy | Precision | Recall | F1-score | +|---|---|---:|---:|---:|---:| +| Logistic Regression | Real Data | 0.846 | 0.738 | 0.589 | 0.655 | +| Logistic Regression | Synthetic Data | 0.758 | 0.556 | 0.121 | 0.199 | +| Random Forest | Real Data | 0.854 | 0.806 | 0.540 | 0.647 | +| Random Forest | Synthetic Data | 0.753 | 1.000 | 0.001 | 0.003 | +| Tuned Random Forest | Real Data | 0.849 | 0.806 | 0.515 | 0.629 | +| Tuned Random Forest | Synthetic Data | 0.752 | 0.000 | 0.000 | 0.000 | + +The models trained on real data performed better than the models trained only on synthetic data. + +The Logistic Regression model trained on real data achieved an F1-score of about `0.655` for the `>50K` class. When trained on synthetic data, the F1-score dropped to about `0.199`. + +The Random Forest model trained on synthetic data performed especially poorly for the `>50K` class. It mostly predicted the majority class, `<=50K`, and almost completely missed the minority class. + +--- + +## Key Findings + +The project showed that SDV was able to generate synthetic data with a good overall quality score. The synthetic data preserved many broad patterns from the original training data, especially individual column distributions. + +However, the synthetic data did not fully preserve the deeper feature relationships needed for strong classification performance. This was most visible in the models trained on synthetic data, which struggled to correctly identify the minority `>50K` class. + +The hyperparameter tuning experiment also did not solve this issue. This suggests that the main limitation was not only the model settings, but also the quality of the relationships preserved in the synthetic data. + +--- + +## Limitations + +This project has some limitations. + +I used the Gaussian Copula Synthesizer because it is fast and stable for a tutorial-style project. More advanced synthesizers such as CTGAN or TVAE could be tested in future work. + +The dataset is also imbalanced, with many more `<=50K` records than `>50K` records. This made the minority class harder to predict, especially when models were trained only on synthetic data. + +The project focused mainly on machine learning utility. It did not deeply evaluate privacy risks such as membership inference or record-level disclosure risk. Because of that, the synthetic data should not automatically be treated as fully private without additional privacy testing. + +--- + +## Future Improvements + +Future work could improve this project by: + +- comparing Gaussian Copula with CTGAN and TVAE +- adding privacy risk evaluation +- testing stronger imbalance-handling methods +- adding confusion matrix visualizations +- trying additional classifiers +- tuning SDV synthesizer settings +- improving minority-class representation in the synthetic data + +These improvements would help better understand both the strengths and limits of synthetic data in classification workflows. + +--- + +## Documentation Guide + +This repository is organized as a tutorial-style project. + +A reader can start with this `README.md` to understand the project goal, workflow, setup instructions, results, and limitations. + +Then they can open: + +```text +notebooks/synthetic_data_vault.API.ipynb +``` + +to understand the basic SDV API workflow. + +After that, they can open: + +```text +notebooks/Synthetic_Data_Vault.ipynb +``` + +to review the full project, including EDA, synthetic data generation, quality evaluation, machine learning, tuning, and interpretation. + +The Docker scripts can be used to verify that the project files and outputs are present. + +--- + + +## References + +This project used the following resources and documentation: + +- Synthetic Data Vault. “Welcome to the SDV!” SDV Documentation. + https://docs.sdv.dev/sdv + +- SDMetrics. “Synthetic Data Metrics.” SDMetrics Documentation. + https://docs.sdv.dev/sdmetrics + +- UCI Machine Learning Repository. “Adult Dataset.” + https://archive.ics.uci.edu/dataset/2/adult + +- Pedregosa, F., Varoquaux, G., Gramfort, A., Michel, V., Thirion, B., Grisel, O., Blondel, M., Prettenhofer, P., Weiss, R., Dubourg, V., Vanderplas, J., Passos, A., Cournapeau, D., Brucher, M., Perrot, M., & Duchesnay, É. “Scikit-learn: Machine Learning in Python.” *Journal of Machine Learning Research*, 12, 2825–2830, 2011. + https://www.jmlr.org/papers/v12/pedregosa11a.html + +- SDV Developers. “Synthetic Data Vault.” GitHub Repository. + https://github.com/sdv-dev/SDV + +--- + +## Conclusion + +This project shows that SDV can be used to generate synthetic tabular data for privacy-preserving machine learning experiments. + +The generated synthetic data had a strong overall quality score and preserved many individual column-level patterns. However, the machine learning results showed that models trained only on synthetic data did not perform as well as models trained on real data, especially for the minority `>50K` class. + +Overall, SDV is useful for safe experimentation, tutorials, and early-stage modeling. At the same time, synthetic data should always be evaluated carefully before being used as a replacement for real data in serious machine learning tasks. diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/docker_build.sh b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/docker_build.sh new file mode 100755 index 000000000..e58736b7c --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/docker_build.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +IMAGE_NAME="sdv-privacy-classification" + +echo "Building Docker image: ${IMAGE_NAME}" +docker build -t ${IMAGE_NAME} . + +echo "Docker image built successfully." diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/docker_run.sh b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/docker_run.sh new file mode 100755 index 000000000..565b85855 --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/docker_run.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +IMAGE_NAME="sdv-privacy-classification" + +echo "Running Docker image: ${IMAGE_NAME}" +docker run --rm ${IMAGE_NAME} diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/notebooks/Synthetic_Data_Vault.ipynb b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/notebooks/Synthetic_Data_Vault.ipynb new file mode 100644 index 000000000..6f2d5d5dd --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/notebooks/Synthetic_Data_Vault.ipynb @@ -0,0 +1,2182 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f8d1e627-1936-4c9e-8cde-fee3d4375bd8", + "metadata": {}, + "source": [ + "# Synthetic Data Vault Privacy Classification Project\n", + "\n", + "This project uses Synthetic Data Vault (SDV) to generate a synthetic version of the Adult Income dataset. The main goal is to test whether synthetic data can preserve useful patterns from the real dataset while reducing direct exposure to original records.\n", + "\n", + "The workflow includes data exploration, synthetic data generation, synthetic data quality evaluation, machine learning model training, and comparison between models trained on real data and synthetic data." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0b83950d-887e-49af-821d-613c1b5e7b29", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Libraries imported successfully.\n" + ] + } + ], + "source": [ + "# Basic data analysis libraries\n", + "import os\n", + "import warnings\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "# Visualization libraries\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "\n", + "# Machine learning tools\n", + "from sklearn.datasets import fetch_openml\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.compose import ColumnTransformer\n", + "from sklearn.preprocessing import OneHotEncoder, StandardScaler\n", + "from sklearn.pipeline import Pipeline\n", + "from sklearn.linear_model import LogisticRegression\n", + "from sklearn.ensemble import RandomForestClassifier\n", + "from sklearn.metrics import (\n", + " accuracy_score,\n", + " precision_score,\n", + " recall_score,\n", + " f1_score,\n", + " classification_report\n", + ")\n", + "\n", + "# SDV tools for synthetic data generation\n", + "from sdv.metadata import SingleTableMetadata\n", + "from sdv.single_table import GaussianCopulaSynthesizer\n", + "\n", + "# SDMetrics reports for synthetic data evaluation\n", + "from sdmetrics.reports.single_table import DiagnosticReport, QualityReport\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "RANDOM_STATE = 42\n", + "np.random.seed(RANDOM_STATE)\n", + "\n", + "print(\"Libraries imported successfully.\")" + ] + }, + { + "cell_type": "markdown", + "id": "d9bb4375-2913-45fd-bfa2-53d36f1b00b7", + "metadata": {}, + "source": [ + "### 1. Load the Adult Income Dataset\n", + "\n", + "The Adult Income dataset is a real-world tabular dataset used for binary classification. The target variable shows whether a person's income is above or below 50K. This dataset is useful for this project because it contains both numerical and categorical features." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1af2cf73-5904-4fe3-ab3f-ce4b09dc93ff", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dataset loaded successfully.\n", + "Dataset shape: (48842, 15)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ageworkclassfnlwgteducationeducation-nummarital-statusoccupationrelationshipracesexcapital-gaincapital-losshours-per-weeknative-countryclass
025Private22680211th7Never-marriedMachine-op-inspctOwn-childBlackMale0040United-States<=50K
138Private89814HS-grad9Married-civ-spouseFarming-fishingHusbandWhiteMale0050United-States<=50K
228Local-gov336951Assoc-acdm12Married-civ-spouseProtective-servHusbandWhiteMale0040United-States>50K
344Private160323Some-college10Married-civ-spouseMachine-op-inspctHusbandBlackMale7688040United-States>50K
418NaN103497Some-college10Never-marriedNaNOwn-childWhiteFemale0030United-States<=50K
\n", + "
" + ], + "text/plain": [ + " age workclass fnlwgt education education-num marital-status \\\n", + "0 25 Private 226802 11th 7 Never-married \n", + "1 38 Private 89814 HS-grad 9 Married-civ-spouse \n", + "2 28 Local-gov 336951 Assoc-acdm 12 Married-civ-spouse \n", + "3 44 Private 160323 Some-college 10 Married-civ-spouse \n", + "4 18 NaN 103497 Some-college 10 Never-married \n", + "\n", + " occupation relationship race sex capital-gain capital-loss \\\n", + "0 Machine-op-inspct Own-child Black Male 0 0 \n", + "1 Farming-fishing Husband White Male 0 0 \n", + "2 Protective-serv Husband White Male 0 0 \n", + "3 Machine-op-inspct Husband Black Male 7688 0 \n", + "4 NaN Own-child White Female 0 0 \n", + "\n", + " hours-per-week native-country class \n", + "0 40 United-States <=50K \n", + "1 50 United-States <=50K \n", + "2 40 United-States >50K \n", + "3 40 United-States >50K \n", + "4 30 United-States <=50K " + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Load Adult Income dataset from OpenML\n", + "adult = fetch_openml(name=\"adult\", version=2, as_frame=True)\n", + "\n", + "# Convert to a pandas DataFrame\n", + "df = adult.frame.copy()\n", + "\n", + "print(\"Dataset loaded successfully.\")\n", + "print(\"Dataset shape:\", df.shape)\n", + "\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "57c712d7-2ea5-4a62-9f82-c6f69b3bc553", + "metadata": {}, + "source": [ + "### 2. Clean and Prepare the Data\n", + "\n", + "Before generating synthetic data, I cleaned the dataset by standardizing column names, replacing unknown values with missing values, and removing rows with missing values. This keeps the project simple and makes the workflow easier to understand." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ea2edd8c-fa04-4fbc-a556-a17932720128", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Data cleaned successfully.\n", + "Shape before cleaning: (48842, 15)\n", + "Shape after cleaning: (45222, 15)\n", + "\n", + "Target class counts:\n", + "class\n", + "<=50K 34014\n", + ">50K 11208\n", + "Name: count, dtype: int64\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ageworkclassfnlwgteducationeducation_nummarital_statusoccupationrelationshipracesexcapital_gaincapital_losshours_per_weeknative_countryclass
025Private22680211th7Never-marriedMachine-op-inspctOwn-childBlackMale0040United-States<=50K
138Private89814HS-grad9Married-civ-spouseFarming-fishingHusbandWhiteMale0050United-States<=50K
228Local-gov336951Assoc-acdm12Married-civ-spouseProtective-servHusbandWhiteMale0040United-States>50K
344Private160323Some-college10Married-civ-spouseMachine-op-inspctHusbandBlackMale7688040United-States>50K
434Private19869310th6Never-marriedOther-serviceNot-in-familyWhiteMale0030United-States<=50K
\n", + "
" + ], + "text/plain": [ + " age workclass fnlwgt education education_num marital_status \\\n", + "0 25 Private 226802 11th 7 Never-married \n", + "1 38 Private 89814 HS-grad 9 Married-civ-spouse \n", + "2 28 Local-gov 336951 Assoc-acdm 12 Married-civ-spouse \n", + "3 44 Private 160323 Some-college 10 Married-civ-spouse \n", + "4 34 Private 198693 10th 6 Never-married \n", + "\n", + " occupation relationship race sex capital_gain capital_loss \\\n", + "0 Machine-op-inspct Own-child Black Male 0 0 \n", + "1 Farming-fishing Husband White Male 0 0 \n", + "2 Protective-serv Husband White Male 0 0 \n", + "3 Machine-op-inspct Husband Black Male 7688 0 \n", + "4 Other-service Not-in-family White Male 0 0 \n", + "\n", + " hours_per_week native_country class \n", + "0 40 United-States <=50K \n", + "1 50 United-States <=50K \n", + "2 40 United-States >50K \n", + "3 40 United-States >50K \n", + "4 30 United-States <=50K " + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Make a copy so the original loaded data is not changed directly\n", + "clean_df = df.copy()\n", + "\n", + "# Make column names easier to work with\n", + "clean_df.columns = (\n", + " clean_df.columns\n", + " .str.strip()\n", + " .str.lower()\n", + " .str.replace(\"-\", \"_\")\n", + " .str.replace(\" \", \"_\")\n", + ")\n", + "\n", + "# Replace question marks with missing values\n", + "clean_df = clean_df.replace(\"?\", np.nan)\n", + "\n", + "# Remove rows with missing values\n", + "clean_df = clean_df.dropna().reset_index(drop=True)\n", + "\n", + "# The target column in this dataset is named \"class\"\n", + "target_col = \"class\"\n", + "\n", + "# Clean target values by removing extra spaces\n", + "clean_df[target_col] = clean_df[target_col].astype(str).str.strip()\n", + "\n", + "print(\"Data cleaned successfully.\")\n", + "print(\"Shape before cleaning:\", df.shape)\n", + "print(\"Shape after cleaning:\", clean_df.shape)\n", + "\n", + "print(\"\\nTarget class counts:\")\n", + "print(clean_df[target_col].value_counts())\n", + "\n", + "clean_df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "c53db6ec-6642-490c-97db-10f64199b92d", + "metadata": {}, + "source": [ + "### 3. Exploratory Data Analysis\n", + "\n", + "In this section, I explore the cleaned dataset to understand the feature types, missing values, target distribution, and some basic patterns. This helps check whether the data is ready for synthetic data generation and machine learning." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "86bce53c-bf46-40aa-9a9c-68a585b8b88d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dataset shape:\n", + "(45222, 15)\n", + "\n", + "Column names:\n", + "['age', 'workclass', 'fnlwgt', 'education', 'education_num', 'marital_status', 'occupation', 'relationship', 'race', 'sex', 'capital_gain', 'capital_loss', 'hours_per_week', 'native_country', 'class']\n", + "\n", + "Missing values after cleaning:\n", + "age 0\n", + "workclass 0\n", + "fnlwgt 0\n", + "education 0\n", + "education_num 0\n", + "marital_status 0\n", + "occupation 0\n", + "relationship 0\n", + "race 0\n", + "sex 0\n", + "capital_gain 0\n", + "capital_loss 0\n", + "hours_per_week 0\n", + "native_country 0\n", + "class 0\n", + "dtype: int64\n", + "\n", + "Data types:\n", + "age int64\n", + "workclass category\n", + "fnlwgt int64\n", + "education category\n", + "education_num int64\n", + "marital_status category\n", + "occupation category\n", + "relationship category\n", + "race category\n", + "sex category\n", + "capital_gain int64\n", + "capital_loss int64\n", + "hours_per_week int64\n", + "native_country category\n", + "class object\n", + "dtype: object\n" + ] + } + ], + "source": [ + "print(\"Dataset shape:\")\n", + "print(clean_df.shape)\n", + "\n", + "print(\"\\nColumn names:\")\n", + "print(clean_df.columns.tolist())\n", + "\n", + "print(\"\\nMissing values after cleaning:\")\n", + "print(clean_df.isna().sum())\n", + "\n", + "print(\"\\nData types:\")\n", + "print(clean_df.dtypes)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "150a9065-6fef-4f98-805b-0ee00f0acd11", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Summary statistics for numerical columns:\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
agefnlwgteducation_numcapital_gaincapital_losshours_per_week
count45222.0000004.522200e+0445222.00000045222.00000045222.00000045222.000000
mean38.5479411.897347e+0510.1184601101.43034488.59541840.938017
std13.2178701.056392e+052.5528817506.430084404.95609212.007508
min17.0000001.349200e+041.0000000.0000000.0000001.000000
25%28.0000001.173882e+059.0000000.0000000.00000040.000000
50%37.0000001.783160e+0510.0000000.0000000.00000040.000000
75%47.0000002.379260e+0513.0000000.0000000.00000045.000000
max90.0000001.490400e+0616.00000099999.0000004356.00000099.000000
\n", + "
" + ], + "text/plain": [ + " age fnlwgt education_num capital_gain capital_loss \\\n", + "count 45222.000000 4.522200e+04 45222.000000 45222.000000 45222.000000 \n", + "mean 38.547941 1.897347e+05 10.118460 1101.430344 88.595418 \n", + "std 13.217870 1.056392e+05 2.552881 7506.430084 404.956092 \n", + "min 17.000000 1.349200e+04 1.000000 0.000000 0.000000 \n", + "25% 28.000000 1.173882e+05 9.000000 0.000000 0.000000 \n", + "50% 37.000000 1.783160e+05 10.000000 0.000000 0.000000 \n", + "75% 47.000000 2.379260e+05 13.000000 0.000000 0.000000 \n", + "max 90.000000 1.490400e+06 16.000000 99999.000000 4356.000000 \n", + "\n", + " hours_per_week \n", + "count 45222.000000 \n", + "mean 40.938017 \n", + "std 12.007508 \n", + "min 1.000000 \n", + "25% 40.000000 \n", + "50% 40.000000 \n", + "75% 45.000000 \n", + "max 99.000000 " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "print(\"Summary statistics for numerical columns:\")\n", + "clean_df.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4d5d2181-bafc-404b-adb3-862e51dd11d7", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(6, 4))\n", + "sns.countplot(data=clean_df, x=target_col)\n", + "plt.title(\"Income Class Distribution\")\n", + "plt.xlabel(\"Income Class\")\n", + "plt.ylabel(\"Count\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "7cb4ed7c-8e6a-49d5-9447-a43721ee1bb3", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGJCAYAAACEkIXWAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjksIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvJkbTWQAAAAlwSFlzAAAPYQAAD2EBqD+naQAAjkxJREFUeJzt3Qd4U+X3B/BvVpvuvReUUWbZGxmCoCIO3IKAIiriwoX83Pp3T5w4cYEKDkQ2svfelFUKZZTuvdvk/5y3pLbMQkfS5vt5nkvW5fbmJmlPzj3veTVms9kMIiIiIiI7obX2DhARERER1SUGwERERERkVxgAExEREZFdYQBMRERERHaFATARERER2RUGwERERERkVxgAExEREZFdYQBMRERERHaFATARERER2RUGwERk844cOQKNRoPvv/++1n+W/Az5WfIzLRo1aoTrrrsOdWH58uXq58tlXavL50n1531B1BAxACayE59//rn6A9qtWzdr74raD8ui1+vh7e2NTp064bHHHsPevXtr9DnXRdDc0PatLr7MvPfee9beFZvx119/4ZprroGvry8cHBwQHByM2267DUuXLrX2rhE1WHpr7wAR1Y1p06apDN/GjRtx6NAhNG3a1KqH/qqrrsLIkSNhNpuRmZmJHTt24IcfflCB4dtvv40nnniifN2IiAjk5+fDYDBc0s+QbUlQMXr06Cr/n7vvvht33HEHHB0dUZvOt299+vRRz1UCIWrY5L1/7733qi9CHTp0UO/5wMBAJCQkqKB4wIABWLNmDXr27GntXSVqcBgAE9mBuLg4rF27Fn/++SceeOABFQy/9NJLVt2n5s2bY8SIEZXue+uttzB06FA8+eSTaNGiBa699lp1v2QMjUZjre5Pbm4uXFxcoNPp1GItWq221p8r2Yb3339fBb+PP/44PvjgA/U+t3juuefw008/qTMkRFTzWAJBZAck4PXy8sKQIUNwyy23qNvnkpqaqjKg7u7u8PT0xKhRo1Rm9lz1t/v27VPbkvIFCdg6d+6M2bNnV2s/fXx88Ouvv6o/+q+//voFa4BPnTqFe+65B6GhoSpbGxQUhBtuuKG8dley3Xv27MGKFSvKyy369etXqc5XHnvooYfg7++vtnO+GmCLRYsWoX379ur5tmrVSn2hqOjll1+uFMRYnLnNC+3b+Wo9Z86cqcpEnJyc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EyaovweW4yb4PqtMumXI7/6cnByVGNm4caMqs5QSL1s9ZgyAGygZ4CjBr5zCX7p0qWrpUpEEeAaDQdU6WUibNPljIqcy6L+sU2FhIY/XeQwYMECVjUiWxLJIlk5OF1qu8312cfJHIzY2VgV5/Gyem5RwndnKUeo0JXMu5Hec/DGt+DtNTmVv2LDB7n+nTZ06VdVNS50+/wZcWF5eHrTayqGRZDTlbwHfZxfn4uKifo+lp6dj4cKFuOGGG2z3s2m14XdUq8aNG6dajixfvrxSGxwZrWkhLXCkNdrSpUtVG7QePXqoxV49++yzqkuGtL7ZuXOnuq3RaMyLFi1Sj/N4VU3FLhA8buf25JNPqs+mvNfWrFljHjhwoNnX11d1a+ExO3+bPb1er9pUSZuladOmmZ2dnc0///xz+TrSasnT09P8999/q8/wDTfcYPVWS9ZWWlqqfs9LF40z8Xfa2UaNGmUOCQkpb4P2559/qs/mM888U74O32dnW7BggXn+/PmqPaH8zZQuN926dTMXFRXZ7DFjANxAyXebcy3SG9hC3ngPPfSQavUlf0huuukmFSTbK2l9I31GHRwczH5+fuYBAwaUB7+Cx+vyAmAet7Pdfvvt5qCgIPVekz+2crtiP1ses3P7559/zG3atFHtk1q0aGH+6quvKj0u7ZZeeOEFc0BAgFpHPsPS09ueSa9k+d1/ruPA99nZsrKy1O8v+dJgNBrNkZGRqpVXYWFh+Tp8n53tt99+U8dKfqdJy7Px48erVme2fMw08o/18s9ERERERHWLNcBEREREZFcYABMRERGRXWEATERERER2hQEwEREREdkVBsBEREREZFcYABMRERGRXWEATERERER2hQEwEREREdkVBsBEREREZFcYABMRNTDr1q2DTqfDkCFDrL0rREQ2iVMhExE1MPfddx9cXV3x7bffYv/+/QgODrb2LhER2RRmgImIGpCcnBz89ttvGDdunMoAf//995Uenz17Npo1awaj0Yj+/fvjhx9+gEajQUZGRvk6q1evxhVXXAEnJyeEhYXh0UcfRW5urhWeDRFR7WAATETUgMyYMQMtWrRAVFQURowYge+++w5ms1k9FhcXh1tuuQU33ngjduzYgQceeADPPfdcpf8fGxuLq6++GjfffDN27typgmkJiB9++GErPSMioprHEggiogakV69euO222/DYY4+hpKQEQUFBmDlzJvr164dnn30Wc+fOxa5du8rXf/755/H6668jPT0dnp6eqnxC6oe//PLL8nUkAO7bt6/KAkvmmIiovmMGmIiogZB6340bN+LOO+9Ut/V6PW6//XZVC2x5vEuXLpX+T9euXSvdlsywlE1IDbFlGTx4MEwmk8ogExE1BHpr7wAREdUMCXQl61tx0JuUPzg6OuLTTz+tcg2xlEZI3e+ZwsPD+VIRUYPAAJiIqAGQwPfHH3/E+++/j0GDBlV6TGp+f/nlF1UXPG/evEqPbdq0qdLtjh07Yu/evWjatGmd7DcRkTWwBpiIqAGYNWuWKndISkqCh4dHpccmTpyIpUuXqgFyEgRPmDABY8aMwfbt2/Hkk0/i+PHjqguE/D8Z+Na9e3fce++9qh7YxcVFBcSLFy+uchaZiMjWsQaYiKiBlD8MHDjwrOBXSEeHzZs3Izs7G7///jv+/PNPREdH44svvijvAiFlEkLuX7FiBQ4cOKBaoXXo0AEvvvgiewkTUYPCDDARkR2TDhBTpkzBsWPHrL0rRER1hjXARER25PPPP1edIHx8fLBmzRq8++677PFLRHaHATARkR05ePAg/u///g9paWmqq4PUAE+aNMnau0VEVKdYAkFEREREdoWD4IiIiIjIrjAAJiIiIiK7wgCYiIiIiOwKA2AiIiIisisMgImIiIjIrjAAJiIiIiK7wgCYiIiIiOwKA2AiIiIigj35f8g7l4/DHNpXAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(8, 4))\n", + "sns.histplot(data=clean_df, x=\"age\", hue=target_col, bins=30, kde=True)\n", + "plt.title(\"Age Distribution by Income Class\")\n", + "plt.xlabel(\"Age\")\n", + "plt.ylabel(\"Count\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c794e75b-bd9c-4705-ad83-0d4bc90bdd83", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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/L///ttea5CvA3O1As+yZcuscaBBuho4cKB899139lPTarSyzgcffBCh/Wu6UYcOHWT37t2yatUqee2116Rdu3aSJUsWW6dPnz62Tb2roKk9b731lqUP9ejRI9rOCwAAAOKPBJXGExEpUqSQX3/9Vfr16yctWrSQK1euSI4cOSwPX3vVb9y4Ifv375dp06bJuXPnJFu2bFa3/5VXXrH316hRwyroDB06VEaMGGHvqVatWoT2v3TpUgvctXynvn766aflo48+cq+jDQ/Ns3/jjTcs11979LWUaIECBaLlnAAAACB+8dNRurF9EIg7dOCvpilpI0IbKAAAAIi/8RppPAAAAICPItgHAAAAfBTBPgAAAOCjCPYBAAAAH0WwDwAAAPgogn0AAADARxHsAwAAAD6KYB8AAADwUQT7AAAAgI8i2AcAAAB8FME+AAAA4KMI9gEAAAAfRbAfhkGDBkmWLFnEz89P5s2bF6UnvkaNGtKzZ0+JbtFx7AAAAIgffCLY79ixowW1OiVNmlTy588vQ4YMkbt370Z6m/v27ZPBgwfLF198IadOnZL69etH6TEDAAAA0S2x+Ih69erJlClT5NatW7J48WLp1q2bJEmSRPr37++13u3bt61B8CCHDh2yn02bNrVGBAAAABDf+ETPvgoICJCsWbNKnjx55NVXX5U6derIggULrNe/WbNmMmzYMMmePbsULFjQ1t+1a5fUqlVLkidPLhkyZJDOnTvL1atX3ek7jRs3tn8nSpQo1GB/x44dUrNmTQkMDJTUqVNL2bJlZfPmze7l69ats3SdFClSSLp06aRu3bpy4cIF9/L79+9L3759JX369Hbsul9Px48ft8ZGqlSpbPstW7aUM2fOeK0zYcIEefTRR60Bo5/tm2++icKzCgAAgPjMZ4L9oDSI1158tXLlSjlw4IAsX75cFi5cKNeuXbPAWwPwTZs2yezZs2XFihXSvXt3W//NN9+0uwRKU3h0Cknbtm0lZ86cto0tW7bIW2+9ZXcT1Pbt26V27dpSpEgR2bBhg/z222/WgLh37577/dOmTZOUKVPKH3/8ISNHjrTUIz1GpyGggf758+dlzZo1Nv/w4cPSqlUr9/vnzp0rPXr0kDfeeEN2794tr7zyirzwwguyatWqcJ8nvRNy+fJlrwkAAAA+wuUDOnTo4GratKn9+/79+67ly5e7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ZJBUqVLJ008/LWfOnPFaR59g3LBhQ0mRIoVtp0+fPnL37l2vdVavXi1lypSxSg758+eXqVOn8l15GDFihPj5+UnPnj059zHkn3/+keeff96u7eTJk0vx4sVl8+bNXk98fPfddyVbtmy2vE6dOnLw4EGvbZw/f17atm1rDxhKmzatvPTSS3L16lWvdXbu3ClVq1a1v1P6xOmRI0dKQnfv3j0ZMGCAPPLII3ZuH330URk6dKidcwfnP2r8+uuv0rhxY3tyqf6NmTdvntfymDzPs2fPlkKFCtk6+vu2ePFiScjn/86dO9KvXz87FylTprR12rdvLydPnkzY598F/D8zZ850JU2a1DV58mTXnj17XJ06dXKlTZvWdebMGc5RONWtW9c1ZcoU1+7du13bt293NWjQwJU7d27X1atX3et06dLFlStXLtfKlStdmzdvdj3++OOuypUru5ffvXvXVaxYMVedOnVc27Ztcy1evNiVMWNGV//+/d3rHD582JUiRQpX7969XXv37nV98sknLn9/f9eSJUv4rlwu18aNG1158+Z1lShRwtWjRw/OfQw4f/68K0+ePK6OHTu6/vjjD7tGly5d6vrrr7/c64wYMcKVJk0a17x581w7duxwNWnSxPXII4+4bty44V6nXr16rpIlS7p+//1319q1a1358+d3tWnTxr380qVLrixZsrjatm1rv2ffffedK3ny5K4vvvgiQV/7w4YNc2XIkMG1cOFC15EjR1yzZ892pUqVyjV27Fj3Opz/qKF/k99++23XnDlztCXlmjt3rtfymDrP69ats7/7I0eOtP8PvPPOO64kSZK4du3a5Uqo5//ixYv2/85Zs2a59u/f79qwYYOrQoUKrrJly3ptI6Gdf4J9uOkvRLdu3dyv792758qePbtr+PDhnKVIOnv2rP0xWrNmjfsPkf4x0P8RO/bt22fr6B8l5w9ZokSJXKdPn3avM2HCBFfq1Kldt27dstd9+/Z1FS1a1GtfrVq1ssZGQnflyhVXgQIFXMuXL3dVr17dHexz7qNXv379XE888USoy+/fv+/KmjWr68MPP3TP0+8kICDA/keq9H+Y+ruwadMm9zo///yzy8/Pz/XPP//Y688++8yVLl069++Cs++CBQu6ErKGDRu6XnzxRa95LVq0sGBFcf6jR9BgMybPc8uWLe1791SxYkXXK6+84kooQmpshdT5IyKuY8eOJdjzTxoPzO3bt2XLli12u9GRKFEie71hwwbOUiRdunTJfqZPn95+6jnW24ye51lvAebOndt9nvWn3g7MkiWLe526devK5cuXZc+ePe51PLfhrMN3JZYipSlQQc8P5z56LViwQMqVKyfPPvuspZ6VLl1aJk2a5F5+5MgROX36tNf3kiZNGksX9Lz29Za6bseh6+vfoj/++MO9TrVq1SRp0qRe176my124cEESqsqVK8vKlSvlzz//tNc7duyQ3377TerXr2+vOf8xIybPM/8fCP//h/38/OycJ9TzT7AP899//1nOp2eAqfS1/uFCxN2/f9/yxatUqSLFihWzeXou9Y+H80cnpPOsP0P6HpxlYa2jDYIbN24k2K9r5syZsnXrVhs7ERTnPnodPnxYJkyYIAUKFJClS5fKq6++Kq+//rpMmzbNff5VWH9j9Kc2FDwlTpzYGssR+f1IiN566y1p3bq1dR4kSZLEGlv690fzkhXnP2bE5HkObZ2E/HsQlI6R69evn7Rp08by8xPq+U8c2wcA+HIP8+7du613DdHvxIkT0qNHD1m+fLkNlkLMN261p+z999+31xps6vX/+eefS4cOHfg6otn3338vM2bMkG+//VaKFi0q27dvt2BfByhy/pEQ6V30li1b2oBp7YhIyOjZh8mYMaP4+/sHqwqjr7NmzcpZiqDu3bvLwoULZdWqVZIzZ073fD2XmjJ18eLFUM+z/gzpe3CWhbWO9lxo9YeESNN0zp49axWKtJdGpzVr1si4cePs39rjwrmPPlp5pEiRIl7zChcubJWlPK/dsP7G6E/9Dj1pFSqtnBGR34+ESCt2Ob37mgbYrl076dWrl/suF+c/ZsTkeQ5tnYT8exA00D927Jh1ADm9+gn1/BPsw2hqSdmyZS3n07OnTl9XqlSJsxRO2oOggf7cuXPll19+sTJ4nvQc6y12z/OsOYAaEDnnWX/u2rXL64+R88fKCaZ0Hc9tOOsk5O+qdu3adt60R9OZtKdZ0xicf3Puo4+mqwUtM6v543ny5LF/6++C/k/Q87rVtDPNkfW89rUhrA03h/4e6d8izXl21tHSe/o/c89rv2DBgpIuXTpJqK5fv245x560A0fPneL8x4yYPM/8fyDsQF/Lna5YscJKAXtKkOc/tkcII26V3tSKAVOnTrXR6p07d7bSm55VYRC2V1991UqurV692nXq1Cn3dP36da/Sm1qO85dffrHSm5UqVbIpaOnNp556ysp3ajnNTJkyhVh6s0+fPlbNZ/z48ZTeDIFnNR7OffTSiheJEye2EpAHDx50zZgxw67R6dOne5Uk1L8p8+fPd+3cudPVtGnTEEsSli5d2sp3/vbbb1ZZybMknlY20ZJ47dq1s5J4+ndL95PQS2926NDBlSNHDnfpTS1LqCV7tXKXg/MfdRW/tCyyThpGffTRR/Zvp9pLTJ1nLf2ov3OjRo2y/w8MHDgwzpZ+jKnzf/v2bSt1mjNnTvv/p+f/h295VNZJaOefYB9etF67BqJab19LcWoNWkTgF0okxElr7zv0D37Xrl2trJf+8WjevLn9IfJ09OhRV/369a2ur/4P+4033nDduXPHa51Vq1a5SpUqZd9Vvnz5vPaBkIN9zn30+umnn6yhqp0GhQoVck2cONFruZYlHDBggP1PVNepXbu268CBA17rnDt3zv6nqzXitdzsCy+8YP9z96S1y7XMp25DA1wNrhK6y5cv27Wuf7+TJUtmfxO0FrlngMP5jxr6tzekv/Pa4Irp8/z999+7HnvsMfv/gJZjXrRokSshn39t6Ib2/+FVq1Yl2PPvp/+J7bsLAAAAAKIeOfsAAACAjyLYBwAAAHwUwT4AAADgowj2AQAAAB9FsA8AAAD4KIJ9AAAAwEcR7AMAAAA+imAfAAAA8FEE+wAAAICPItgHAMRZp0+fltdee03y5csnAQEBkitXLmncuLGsXLkyRo/Dz89P5s2bF6P7BICokDhKtgIAQBQ7evSoVKlSRdKmTSsffvihFC9eXO7cuSNLly6Vbt26yf79+znnAPAAfi6Xy/WglQAAiGkNGjSQnTt3yoEDByRlypReyy5evGiNgOPHj1vPv/b0J0qUSOrVqyeffPKJZMmSxdbr2LGjrevZK9+zZ0/Zvn27rF692l7XqFFDSpQoIcmSJZMvv/xSkiZNKl26dJFBgwbZ8rx588qxY8fc78+TJ481RAAgPiCNBwAQ55w/f16WLFliPfhBA32lgf79+/eladOmtu6aNWtk+fLlcvjwYWnVqlWE9zdt2jTbzx9//CEjR46UIUOG2PbUpk2b7OeUKVPk1KlT7tcAEB+QxgMAiHP++usv0RvPhQoVCnUd7c3ftWuXHDlyxHL51ddffy1Fixa1gLx8+fLh3p/27A8cOND+XaBAAfn0009t+08++aRkypTJ3cDImjXrQ382AIhJ9OwDAOKc8GSY7tu3z4J8J9BXRYoUsaBcl0WEBvuesmXLJmfPno3QNgAgLiLYBwDEOdq7rhVwHnYQrubxB2046CDfoJIkSeL1WvetaUIAEN8R7AMA4pz06dNL3bp1Zfz48XLt2rVgy3XQbeHCheXEiRM2Ofbu3WvLtIdfaQqO5tl70sG5EaWNgXv37kXqswBAbCLYBwDESRroa4BdoUIF+fHHH+XgwYOWnjNu3DipVKmS1KlTx8pxtm3bVrZu3SobN26U9u3bS/Xq1aVcuXK2jVq1asnmzZstl1/fr3n5u3fvjvCxaEUezeHXuv8XLlyIhk8LANGDYB8AECfpg7Q0iK9Zs6a88cYbUqxYMRswq0H3hAkTLNVm/vz5ki5dOqlWrZoF//qeWbNmubehdwcGDBggffv2tQG7V65csQZBRI0ePdqq8+j4gNKlS0fxJwWA6EOdfQAAAMBH0bMPAAAA+CiCfQAAAMBHEewDAAAAPopgHwAAAPBRBPsAAACAjyLYBwAAAHwUwT4AAADgowj2AQAAAB9FsA8AAAD4KIJ9AAAAwEcR7AMAAADim/4PayBmJBsgzQYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "top_education = clean_df[\"education\"].value_counts().head(8).index\n", + "\n", + "plt.figure(figsize=(8, 5))\n", + "sns.countplot(\n", + " data=clean_df[clean_df[\"education\"].isin(top_education)],\n", + " y=\"education\",\n", + " hue=target_col\n", + ")\n", + "plt.title(\"Top Education Categories by Income Class\")\n", + "plt.xlabel(\"Count\")\n", + "plt.ylabel(\"Education\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "a87fd6e5-ec24-4a65-b412-5e64cb8b68c5", + "metadata": {}, + "source": [ + "### 4. Train-Test Split\n", + "\n", + "Before generating synthetic data, I split the cleaned dataset into training and testing sets. The SDV model is fitted only on the training data. The test data is kept separate and is used later to evaluate whether models trained on synthetic data can perform well on unseen real data." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c8a86d25-96e6-406e-8053-e8ffebac9748", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training data shape: (33916, 15)\n", + "Testing data shape: (11306, 15)\n", + "\n", + "Training target distribution:\n", + "class\n", + "<=50K 0.752152\n", + ">50K 0.247848\n", + "Name: proportion, dtype: float64\n", + "\n", + "Testing target distribution:\n", + "class\n", + "<=50K 0.752167\n", + ">50K 0.247833\n", + "Name: proportion, dtype: float64\n" + ] + } + ], + "source": [ + "# Split the cleaned real data into training and testing sets.\n", + "# Stratify keeps the target class ratio similar in both sets.\n", + "\n", + "train_df, test_df = train_test_split(\n", + " clean_df,\n", + " test_size=0.25,\n", + " random_state=RANDOM_STATE,\n", + " stratify=clean_df[target_col]\n", + ")\n", + "\n", + "train_df = train_df.reset_index(drop=True)\n", + "test_df = test_df.reset_index(drop=True)\n", + "\n", + "print(\"Training data shape:\", train_df.shape)\n", + "print(\"Testing data shape:\", test_df.shape)\n", + "\n", + "print(\"\\nTraining target distribution:\")\n", + "print(train_df[target_col].value_counts(normalize=True))\n", + "\n", + "print(\"\\nTesting target distribution:\")\n", + "print(test_df[target_col].value_counts(normalize=True))" + ] + }, + { + "cell_type": "markdown", + "id": "54744a64-5d24-4cc6-be37-4a1aa950c189", + "metadata": {}, + "source": [ + "### 5. Build SDV Metadata\n", + "\n", + "SDV needs metadata to understand the structure of the dataset. The metadata tells SDV which columns are numerical, categorical, or other data types. I use SDV's automatic metadata detection on the real training data." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "78ab42a5-a7e7-4460-822c-8e70034eb33c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Metadata detected successfully.\n" + ] + }, + { + "data": { + "text/plain": [ + "{'METADATA_SPEC_VERSION': 'SINGLE_TABLE_V1',\n", + " 'columns': {'age': {'sdtype': 'numerical'},\n", + " 'workclass': {'sdtype': 'categorical'},\n", + " 'fnlwgt': {'sdtype': 'numerical'},\n", + " 'education': {'sdtype': 'categorical'},\n", + " 'education_num': {'sdtype': 'numerical'},\n", + " 'marital_status': {'sdtype': 'categorical'},\n", + " 'occupation': {'sdtype': 'categorical'},\n", + " 'relationship': {'sdtype': 'categorical'},\n", + " 'race': {'sdtype': 'categorical'},\n", + " 'sex': {'sdtype': 'categorical'},\n", + " 'capital_gain': {'sdtype': 'numerical'},\n", + " 'capital_loss': {'sdtype': 'numerical'},\n", + " 'hours_per_week': {'sdtype': 'numerical'},\n", + " 'native_country': {'sdtype': 'categorical'},\n", + " 'class': {'sdtype': 'categorical'}}}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Create metadata object for a single-table dataset\n", + "metadata = SingleTableMetadata()\n", + "\n", + "# Detect column types automatically from the training data\n", + "metadata.detect_from_dataframe(data=train_df)\n", + "\n", + "print(\"Metadata detected successfully.\")\n", + "\n", + "# Show detected metadata\n", + "metadata.to_dict()" + ] + }, + { + "cell_type": "markdown", + "id": "53da6fef-21ee-49d6-a875-bad1807372e3", + "metadata": {}, + "source": [ + "### 6. Generate Synthetic Data using SDV\n", + "\n", + "In this section, I use the Gaussian Copula synthesizer from SDV to learn patterns from the real training data and generate a new synthetic dataset. The synthetic dataset has the same number of rows as the real training set.\n", + "\n", + "The synthesizer is fitted only on the training data, not the test data, to avoid data leakage." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "5ae69204-e959-4ed2-bd1a-cdcd55c65a5f", + "metadata": {}, + "outputs": [], + "source": [ + "metadata = SingleTableMetadata()\n", + "metadata.detect_from_dataframe(data=train_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0d3f0798-3926-4381-ab68-9d95cfaf7032", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Categorical columns converted to string format.\n", + "age int64\n", + "workclass object\n", + "fnlwgt int64\n", + "education object\n", + "education_num int64\n", + "marital_status object\n", + "occupation object\n", + "relationship object\n", + "race object\n", + "sex object\n", + "capital_gain int64\n", + "capital_loss int64\n", + "hours_per_week int64\n", + "native_country object\n", + "class object\n", + "dtype: object\n" + ] + } + ], + "source": [ + "# Fix categorical columns for SDV\n", + "# OpenML loads some columns as pandas \"category\" type.\n", + "# Converting them to normal strings prevents SDV encoding errors.\n", + "\n", + "def convert_categorical_columns_to_string(dataframe):\n", + " fixed_df = dataframe.copy()\n", + " \n", + " for column in fixed_df.columns:\n", + " if str(fixed_df[column].dtype) == \"category\":\n", + " fixed_df[column] = fixed_df[column].astype(str)\n", + " \n", + " return fixed_df\n", + "\n", + "\n", + "train_df = convert_categorical_columns_to_string(train_df)\n", + "test_df = convert_categorical_columns_to_string(test_df)\n", + "\n", + "print(\"Categorical columns converted to string format.\")\n", + "print(train_df.dtypes)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "697a8ad4-86d9-42c7-88b5-e75987d9927f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training the SDV synthesizer...\n", + "SDV synthesizer training completed.\n", + "Synthetic data generated successfully.\n", + "Synthetic data shape: (33916, 15)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ageworkclassfnlwgteducationeducation_nummarital_statusoccupationrelationshipracesexcapital_gaincapital_losshours_per_weeknative_countryclass
047Self-emp-not-inc17667211th8WidowedAdm-clericalOwn-childWhiteMale1269055United-States<=50K
139Local-gov321792HS-grad9Never-marriedProf-specialtyHusbandWhiteMale237040United-States<=50K
230Private159688HS-grad11Married-civ-spouseCraft-repairHusbandWhiteFemale37944227United-States<=50K
324Private154087Some-college12Married-civ-spouseSalesHusbandWhiteMale16652052United-States<=50K
432Private76586Bachelors13WidowedProf-specialtyOwn-childWhiteFemale31379030United-States<=50K
\n", + "
" + ], + "text/plain": [ + " age workclass fnlwgt education education_num \\\n", + "0 47 Self-emp-not-inc 176672 11th 8 \n", + "1 39 Local-gov 321792 HS-grad 9 \n", + "2 30 Private 159688 HS-grad 11 \n", + "3 24 Private 154087 Some-college 12 \n", + "4 32 Private 76586 Bachelors 13 \n", + "\n", + " marital_status occupation relationship race sex \\\n", + "0 Widowed Adm-clerical Own-child White Male \n", + "1 Never-married Prof-specialty Husband White Male \n", + "2 Married-civ-spouse Craft-repair Husband White Female \n", + "3 Married-civ-spouse Sales Husband White Male \n", + "4 Widowed Prof-specialty Own-child White Female \n", + "\n", + " capital_gain capital_loss hours_per_week native_country class \n", + "0 1269 0 55 United-States <=50K \n", + "1 237 0 40 United-States <=50K \n", + "2 3794 42 27 United-States <=50K \n", + "3 16652 0 52 United-States <=50K \n", + "4 31379 0 30 United-States <=50K " + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Initialize the SDV synthesizer\n", + "synthesizer = GaussianCopulaSynthesizer(\n", + " metadata,\n", + " enforce_min_max_values=True,\n", + " enforce_rounding=True\n", + ")\n", + "\n", + "print(\"Training the SDV synthesizer...\")\n", + "\n", + "# Fit the synthesizer on the real training data only\n", + "synthesizer.fit(train_df)\n", + "\n", + "print(\"SDV synthesizer training completed.\")\n", + "\n", + "# Generate synthetic data with the same number of rows as the real training set\n", + "synthetic_df = synthesizer.sample(num_rows=len(train_df))\n", + "\n", + "print(\"Synthetic data generated successfully.\")\n", + "print(\"Synthetic data shape:\", synthetic_df.shape)\n", + "\n", + "synthetic_df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "4620f2d4-7130-4d82-a366-efe8ee22099d", + "metadata": {}, + "source": [ + "### 7. Evaluate Synthetic Data Quality\n", + "\n", + "After generating synthetic data, I evaluated its quality using SDMetrics reports. The diagnostic report checks whether the synthetic data follows the expected structure and validity rules. The quality report compares the real and synthetic data to see how well the synthetic data captures column distributions and relationships." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "0cf3418b-bb1b-4006-8892-59d339dbaf27", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Generating report ...\n", + "\n", + "(1/2) Evaluating Data Validity: |█████████████| 15/15 [00:00<00:00, 289.96it/s]|\n", + "Data Validity Score: 100.0%\n", + "\n", + "(2/2) Evaluating Data Structure: |██████████████| 1/1 [00:00<00:00, 703.74it/s]|\n", + "Data Structure Score: 100.0%\n", + "\n", + "Overall Score (Average): 100.0%\n", + "\n", + "Diagnostic Score: 1.0\n" + ] + } + ], + "source": [ + "# Convert SDV metadata into dictionary format for SDMetrics\n", + "metadata_dict = metadata.to_dict()\n", + "\n", + "# Diagnostic report checks basic data validity and structure\n", + "diagnostic_report = DiagnosticReport()\n", + "\n", + "diagnostic_report.generate(\n", + " real_data=train_df,\n", + " synthetic_data=synthetic_df,\n", + " metadata=metadata_dict,\n", + " verbose=True\n", + ")\n", + "\n", + "diagnostic_score = diagnostic_report.get_score()\n", + "\n", + "print(\"Diagnostic Score:\", diagnostic_score)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "b75c0b9b-8100-4ff2-9699-2231b4ac5508", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Generating report ...\n", + "\n", + "(1/2) Evaluating Column Shapes: |█████████████| 15/15 [00:00<00:00, 108.67it/s]|\n", + "Column Shapes Score: 90.31%\n", + "\n", + "(2/2) Evaluating Column Pair Trends: |██████| 105/105 [00:00<00:00, 308.87it/s]|\n", + "Column Pair Trends Score: 75.09%\n", + "\n", + "Overall Score (Average): 82.7%\n", + "\n", + "Quality Score: 0.8269924224554781\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PropertyScore
0Column Shapes0.903108
1Column Pair Trends0.750877
\n", + "
" + ], + "text/plain": [ + " Property Score\n", + "0 Column Shapes 0.903108\n", + "1 Column Pair Trends 0.750877" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Quality report checks how similar the synthetic data is to the real data\n", + "quality_report = QualityReport()\n", + "\n", + "quality_report.generate(\n", + " real_data=train_df,\n", + " synthetic_data=synthetic_df,\n", + " metadata=metadata_dict,\n", + " verbose=True\n", + ")\n", + "\n", + "quality_score = quality_report.get_score()\n", + "\n", + "print(\"Quality Score:\", quality_score)\n", + "\n", + "# Show the main quality properties\n", + "quality_report.get_properties()" + ] + }, + { + "cell_type": "markdown", + "id": "cdc407be-9180-49af-a612-f98664f181e4", + "metadata": {}, + "source": [ + "##### The synthetic data matched the individual column distributions fairly well, with a Column Shapes score of about 90%. This means SDV preserved many single-column patterns such as age distribution, work hours, and category frequencies.\n", + "\n", + "##### The Column Pair Trends score was lower, around 75%, which means relationships between pairs of columns were harder to preserve. This is expected because synthetic data generation usually captures broad patterns better than complex feature interactions.\n", + "\n", + "##### Overall, the quality score was about 82.7%, suggesting that the generated data is reasonably similar to the real training data, but not a perfect copy." + ] + }, + { + "cell_type": "markdown", + "id": "c9c3f947-14cc-480c-9017-fb0d77f06d3e", + "metadata": {}, + "source": [ + "### 8. Prepare Data for Machine Learning\n", + "\n", + "To train classification models, I separated the input features from the target column. Since the dataset contains both numerical and categorical columns, I used scaling for numerical features and one-hot encoding for categorical features." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "a2d540c2-70fd-4e1b-b52b-012fa01fe0d2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Real training features shape: (33916, 14)\n", + "Synthetic training features shape: (33916, 14)\n", + "Testing features shape: (11306, 14)\n" + ] + } + ], + "source": [ + "# Separate features and target column\n", + "\n", + "def split_features_and_target(dataframe, target_column):\n", + " X = dataframe.drop(columns=[target_column])\n", + " y = dataframe[target_column]\n", + " return X, y\n", + "\n", + "\n", + "X_train_real, y_train_real = split_features_and_target(train_df, target_col)\n", + "X_train_synthetic, y_train_synthetic = split_features_and_target(synthetic_df, target_col)\n", + "X_test, y_test = split_features_and_target(test_df, target_col)\n", + "\n", + "print(\"Real training features shape:\", X_train_real.shape)\n", + "print(\"Synthetic training features shape:\", X_train_synthetic.shape)\n", + "print(\"Testing features shape:\", X_test.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "98f74f2c-e6d9-4592-a2ae-6687405e34d5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Categorical columns:\n", + "['workclass', 'education', 'marital_status', 'occupation', 'relationship', 'race', 'sex', 'native_country']\n", + "\n", + "Numerical columns:\n", + "['age', 'fnlwgt', 'education_num', 'capital_gain', 'capital_loss', 'hours_per_week']\n" + ] + } + ], + "source": [ + "# Identify categorical and numerical columns\n", + "\n", + "categorical_cols = X_train_real.select_dtypes(include=[\"object\", \"category\"]).columns.tolist()\n", + "numerical_cols = X_train_real.select_dtypes(include=[\"int64\", \"float64\"]).columns.tolist()\n", + "\n", + "print(\"Categorical columns:\")\n", + "print(categorical_cols)\n", + "\n", + "print(\"\\nNumerical columns:\")\n", + "print(numerical_cols)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ac175afd-5277-4048-a0bf-a3fc33802d3d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Preprocessor created successfully.\n" + ] + } + ], + "source": [ + "# Preprocessing:\n", + "# - numerical columns are scaled\n", + "# - categorical columns are one-hot encoded\n", + "\n", + "preprocessor = ColumnTransformer(\n", + " transformers=[\n", + " (\"num\", StandardScaler(), numerical_cols),\n", + " (\"cat\", OneHotEncoder(handle_unknown=\"ignore\"), categorical_cols)\n", + " ]\n", + ")\n", + "\n", + "print(\"Preprocessor created successfully.\")" + ] + }, + { + "cell_type": "markdown", + "id": "fdadbc65-defb-4c9d-830f-ac4ebc04dff5", + "metadata": {}, + "source": [ + "### 9. Train Classification Models\n", + "\n", + "I trained two types of classifiers: Logistic Regression and Random Forest. Each model was trained once using the real training data and once using the synthetic training data. All models were evaluated on the same real test set to make the comparison fair." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "0a4918dd-2837-4d66-8ebd-a2fde95f6907", + "metadata": {}, + "outputs": [], + "source": [ + "def evaluate_classifier(model_name, training_data_name, model, X_train, y_train, X_test, y_test):\n", + " \"\"\"\n", + " Train a classifier and evaluate it on the same real test set.\n", + " \n", + " Parameters:\n", + " model_name: Name of the machine learning model.\n", + " training_data_name: Whether the model used real or synthetic training data.\n", + " model: Scikit-learn pipeline.\n", + " X_train, y_train: Training features and labels.\n", + " X_test, y_test: Real test features and labels.\n", + " \n", + " Returns:\n", + " A dictionary containing evaluation scores.\n", + " \"\"\"\n", + " model.fit(X_train, y_train)\n", + " predictions = model.predict(X_test)\n", + "\n", + " scores = {\n", + " \"model\": model_name,\n", + " \"training_data\": training_data_name,\n", + " \"accuracy\": accuracy_score(y_test, predictions),\n", + " \"precision\": precision_score(y_test, predictions, pos_label=\">50K\"),\n", + " \"recall\": recall_score(y_test, predictions, pos_label=\">50K\"),\n", + " \"f1_score\": f1_score(y_test, predictions, pos_label=\">50K\")\n", + " }\n", + "\n", + " print(\"=\" * 70)\n", + " print(f\"{model_name} trained on {training_data_name}\")\n", + " print(\"=\" * 70)\n", + " print(classification_report(y_test, predictions))\n", + "\n", + " return scores" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "890fa2bf-43e3-4f8e-978d-92cfa2793761", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model pipelines created successfully.\n" + ] + } + ], + "source": [ + "# Logistic Regression is a simple, interpretable baseline model\n", + "logistic_model = Pipeline(steps=[\n", + " (\"preprocessor\", preprocessor),\n", + " (\"classifier\", LogisticRegression(max_iter=1000, random_state=RANDOM_STATE))\n", + "])\n", + "\n", + "# Random Forest is added as a stronger non-linear model\n", + "random_forest_model = Pipeline(steps=[\n", + " (\"preprocessor\", preprocessor),\n", + " (\"classifier\", RandomForestClassifier(\n", + " n_estimators=100,\n", + " max_depth=10,\n", + " random_state=RANDOM_STATE,\n", + " n_jobs=-1\n", + " ))\n", + "])\n", + "\n", + "print(\"Model pipelines created successfully.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "cc9eb79f-b7ab-47ee-86eb-80476402eead", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "======================================================================\n", + "Logistic Regression trained on Real Data\n", + "======================================================================\n", + " precision recall f1-score support\n", + "\n", + " <=50K 0.87 0.93 0.90 8504\n", + " >50K 0.74 0.59 0.66 2802\n", + "\n", + " accuracy 0.85 11306\n", + " macro avg 0.81 0.76 0.78 11306\n", + "weighted avg 0.84 0.85 0.84 11306\n", + "\n", + "======================================================================\n", + "Logistic Regression trained on Synthetic Data\n", + "======================================================================\n", + " precision recall f1-score support\n", + "\n", + " <=50K 0.77 0.97 0.86 8504\n", + " >50K 0.56 0.12 0.20 2802\n", + "\n", + " accuracy 0.76 11306\n", + " macro avg 0.66 0.54 0.53 11306\n", + "weighted avg 0.72 0.76 0.69 11306\n", + "\n", + "======================================================================\n", + "Random Forest trained on Real Data\n", + "======================================================================\n", + " precision recall f1-score support\n", + "\n", + " <=50K 0.86 0.96 0.91 8504\n", + " >50K 0.81 0.54 0.65 2802\n", + "\n", + " accuracy 0.85 11306\n", + " macro avg 0.83 0.75 0.78 11306\n", + "weighted avg 0.85 0.85 0.84 11306\n", + "\n", + "======================================================================\n", + "Random Forest trained on Synthetic Data\n", + "======================================================================\n", + " precision recall f1-score support\n", + "\n", + " <=50K 0.75 1.00 0.86 8504\n", + " >50K 1.00 0.00 0.00 2802\n", + "\n", + " accuracy 0.75 11306\n", + " macro avg 0.88 0.50 0.43 11306\n", + "weighted avg 0.81 0.75 0.65 11306\n", + "\n", + "Model comparison completed.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
modeltraining_dataaccuracyprecisionrecallf1_score
0Logistic RegressionReal Data0.8464530.7383720.5892220.655419
1Logistic RegressionSynthetic Data0.7581810.5557380.1209850.198710
2Random ForestReal Data0.8537940.8064000.5396150.646568
3Random ForestSynthetic Data0.7525211.0000000.0014280.002851
\n", + "
" + ], + "text/plain": [ + " model training_data accuracy precision recall \\\n", + "0 Logistic Regression Real Data 0.846453 0.738372 0.589222 \n", + "1 Logistic Regression Synthetic Data 0.758181 0.555738 0.120985 \n", + "2 Random Forest Real Data 0.853794 0.806400 0.539615 \n", + "3 Random Forest Synthetic Data 0.752521 1.000000 0.001428 \n", + "\n", + " f1_score \n", + "0 0.655419 \n", + "1 0.198710 \n", + "2 0.646568 \n", + "3 0.002851 " + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results = []\n", + "\n", + "# Logistic Regression trained on real data\n", + "results.append(\n", + " evaluate_classifier(\n", + " model_name=\"Logistic Regression\",\n", + " training_data_name=\"Real Data\",\n", + " model=logistic_model,\n", + " X_train=X_train_real,\n", + " y_train=y_train_real,\n", + " X_test=X_test,\n", + " y_test=y_test\n", + " )\n", + ")\n", + "\n", + "# Logistic Regression trained on synthetic data\n", + "results.append(\n", + " evaluate_classifier(\n", + " model_name=\"Logistic Regression\",\n", + " training_data_name=\"Synthetic Data\",\n", + " model=logistic_model,\n", + " X_train=X_train_synthetic,\n", + " y_train=y_train_synthetic,\n", + " X_test=X_test,\n", + " y_test=y_test\n", + " )\n", + ")\n", + "\n", + "# Random Forest trained on real data\n", + "results.append(\n", + " evaluate_classifier(\n", + " model_name=\"Random Forest\",\n", + " training_data_name=\"Real Data\",\n", + " model=random_forest_model,\n", + " X_train=X_train_real,\n", + " y_train=y_train_real,\n", + " X_test=X_test,\n", + " y_test=y_test\n", + " )\n", + ")\n", + "\n", + "# Random Forest trained on synthetic data\n", + "results.append(\n", + " evaluate_classifier(\n", + " model_name=\"Random Forest\",\n", + " training_data_name=\"Synthetic Data\",\n", + " model=random_forest_model,\n", + " X_train=X_train_synthetic,\n", + " y_train=y_train_synthetic,\n", + " X_test=X_test,\n", + " y_test=y_test\n", + " )\n", + ")\n", + "\n", + "results_df = pd.DataFrame(results)\n", + "\n", + "print(\"Model comparison completed.\")\n", + "results_df" + ] + }, + { + "cell_type": "markdown", + "id": "ed469379-68ed-443d-9a2d-94c3a5d4f946", + "metadata": {}, + "source": [ + "### 10. Results Interpretation\n", + "\n", + "The models trained on real data performed better than the models trained only on synthetic data. This was expected because the real training data contains the original feature relationships used for prediction.\n", + "\n", + "The Logistic Regression model trained on real data achieved about 84.6% accuracy and an F1-score of about 0.66 for the `>50K` class. When trained only on synthetic data, the accuracy dropped to about 75.8%, and the F1-score for the `>50K` class dropped to about 0.20.\n", + "\n", + "The Random Forest model trained on real data achieved about 85.4% accuracy, but when trained on synthetic data, it mostly predicted the majority class `<=50K`. This caused the recall and F1-score for the `>50K` class to become very low.\n", + "\n", + "These results suggest that SDV preserved many broad column-level patterns, which is supported by the quality score of about 82.7%. However, the synthetic data did not fully preserve the more complex relationships needed to accurately classify the minority income group. This is also consistent with the lower Column Pair Trends score compared to the Column Shapes score.\n", + "\n", + "Overall, the synthetic data was useful for demonstrating privacy-preserving data generation and basic model training, but it was not strong enough to fully replace real training data for this classification task." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "54bfbeeb-40e0-4a52-8e42-4bc28f238972", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Results saved to: ../outputs/model_comparison_results.csv\n" + ] + } + ], + "source": [ + "# Save model comparison results so they are available outside the notebook\n", + "os.makedirs(\"../outputs\", exist_ok=True)\n", + "\n", + "results_path = \"../outputs/model_comparison_results.csv\"\n", + "results_df.to_csv(results_path, index=False)\n", + "\n", + "print(f\"Results saved to: {results_path}\")" + ] + }, + { + "cell_type": "markdown", + "id": "db17fdad-d65e-494d-ab27-104a88384c7a", + "metadata": {}, + "source": [ + "### 13. Bonus Experiment: Simple Hyperparameter Tuning\n", + "\n", + "The first model comparison showed that models trained on synthetic data had difficulty identifying the minority `>50K` class. To improve this, I added a small hyperparameter tuning experiment for Random Forest.\n", + "\n", + "Instead of using a large grid search, I tested only a few practical settings. This keeps the project simple and avoids unnecessary complexity while still showing how model performance can be improved." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "4bdf8596-6ff6-4edf-ab23-907e31c8f4ad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tuning Random Forest on real training data...\n", + "Fitting 3 folds for each of 18 candidates, totalling 54 fits\n", + "Best parameters for real data:\n", + "{'classifier__class_weight': None, 'classifier__max_depth': 8, 'classifier__min_samples_leaf': 1, 'classifier__n_estimators': 100}\n", + "======================================================================\n", + "Tuned Random Forest trained on Real Data\n", + "======================================================================\n", + " precision recall f1-score support\n", + "\n", + " <=50K 0.86 0.96 0.91 8504\n", + " >50K 0.81 0.52 0.63 2802\n", + "\n", + " accuracy 0.85 11306\n", + " macro avg 0.83 0.74 0.77 11306\n", + "weighted avg 0.84 0.85 0.84 11306\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "{'model': 'Tuned Random Forest',\n", + " 'training_data': 'Real Data',\n", + " 'accuracy': 0.8491066690252963,\n", + " 'precision': 0.8058035714285714,\n", + " 'recall': 0.5153461812990721,\n", + " 'f1_score': 0.6286460600783631}" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from sklearn.model_selection import GridSearchCV\n", + "\n", + "# A small parameter grid to avoid long runtime\n", + "rf_tuned_pipeline = Pipeline(steps=[\n", + " (\"preprocessor\", preprocessor),\n", + " (\"classifier\", RandomForestClassifier(\n", + " random_state=RANDOM_STATE,\n", + " n_jobs=-1\n", + " ))\n", + "])\n", + "\n", + "param_grid = {\n", + " \"classifier__n_estimators\": [100],\n", + " \"classifier__max_depth\": [8, 12, None],\n", + " \"classifier__min_samples_leaf\": [1, 3, 5],\n", + " \"classifier__class_weight\": [None, \"balanced\"]\n", + "}\n", + "\n", + "grid_search = GridSearchCV(\n", + " estimator=rf_tuned_pipeline,\n", + " param_grid=param_grid,\n", + " scoring=\"f1\",\n", + " cv=3,\n", + " n_jobs=-1,\n", + " verbose=1\n", + ")\n", + "\n", + "print(\"Tuning Random Forest on real training data...\")\n", + "grid_search.fit(X_train_real, y_train_real)\n", + "\n", + "print(\"Best parameters for real data:\")\n", + "print(grid_search.best_params_)\n", + "\n", + "best_rf_real = grid_search.best_estimator_\n", + "\n", + "real_tuned_results = evaluate_classifier(\n", + " model_name=\"Tuned Random Forest\",\n", + " training_data_name=\"Real Data\",\n", + " model=best_rf_real,\n", + " X_train=X_train_real,\n", + " y_train=y_train_real,\n", + " X_test=X_test,\n", + " y_test=y_test\n", + ")\n", + "\n", + "real_tuned_results" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "d7492f17-4e1c-4edd-a09f-52ffcf9b1f15", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tuning Random Forest on synthetic training data...\n", + "Fitting 3 folds for each of 18 candidates, totalling 54 fits\n", + "Best parameters for synthetic data:\n", + "{'classifier__class_weight': None, 'classifier__max_depth': 8, 'classifier__min_samples_leaf': 1, 'classifier__n_estimators': 100}\n", + "======================================================================\n", + "Tuned Random Forest trained on Synthetic Data\n", + "======================================================================\n", + " precision recall f1-score support\n", + "\n", + " <=50K 0.75 1.00 0.86 8504\n", + " >50K 0.00 0.00 0.00 2802\n", + "\n", + " accuracy 0.75 11306\n", + " macro avg 0.38 0.50 0.43 11306\n", + "weighted avg 0.57 0.75 0.65 11306\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "{'model': 'Tuned Random Forest',\n", + " 'training_data': 'Synthetic Data',\n", + " 'accuracy': 0.7521669909782417,\n", + " 'precision': 0.0,\n", + " 'recall': 0.0,\n", + " 'f1_score': 0.0}" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "grid_search_synthetic = GridSearchCV(\n", + " estimator=rf_tuned_pipeline,\n", + " param_grid=param_grid,\n", + " scoring=\"f1\",\n", + " cv=3,\n", + " n_jobs=-1,\n", + " verbose=1\n", + ")\n", + "\n", + "print(\"Tuning Random Forest on synthetic training data...\")\n", + "grid_search_synthetic.fit(X_train_synthetic, y_train_synthetic)\n", + "\n", + "print(\"Best parameters for synthetic data:\")\n", + "print(grid_search_synthetic.best_params_)\n", + "\n", + "best_rf_synthetic = grid_search_synthetic.best_estimator_\n", + "\n", + "synthetic_tuned_results = evaluate_classifier(\n", + " model_name=\"Tuned Random Forest\",\n", + " training_data_name=\"Synthetic Data\",\n", + " model=best_rf_synthetic,\n", + " X_train=X_train_synthetic,\n", + " y_train=y_train_synthetic,\n", + " X_test=X_test,\n", + " y_test=y_test\n", + ")\n", + "\n", + "synthetic_tuned_results" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "4723fa00-e991-4ee9-b91f-5711b690bfd8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
modeltraining_dataaccuracyprecisionrecallf1_score
0Logistic RegressionReal Data0.8464530.7383720.5892220.655419
1Logistic RegressionSynthetic Data0.7581810.5557380.1209850.198710
2Random ForestReal Data0.8537940.8064000.5396150.646568
3Random ForestSynthetic Data0.7525211.0000000.0014280.002851
4Tuned Random ForestReal Data0.8491070.8058040.5153460.628646
5Tuned Random ForestSynthetic Data0.7521670.0000000.0000000.000000
\n", + "
" + ], + "text/plain": [ + " model training_data accuracy precision recall \\\n", + "0 Logistic Regression Real Data 0.846453 0.738372 0.589222 \n", + "1 Logistic Regression Synthetic Data 0.758181 0.555738 0.120985 \n", + "2 Random Forest Real Data 0.853794 0.806400 0.539615 \n", + "3 Random Forest Synthetic Data 0.752521 1.000000 0.001428 \n", + "4 Tuned Random Forest Real Data 0.849107 0.805804 0.515346 \n", + "5 Tuned Random Forest Synthetic Data 0.752167 0.000000 0.000000 \n", + "\n", + " f1_score \n", + "0 0.655419 \n", + "1 0.198710 \n", + "2 0.646568 \n", + "3 0.002851 \n", + "4 0.628646 \n", + "5 0.000000 " + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Add tuned model results to the comparison table\n", + "tuned_results_df = pd.DataFrame([real_tuned_results, synthetic_tuned_results])\n", + "\n", + "final_results_df = pd.concat([results_df, tuned_results_df], ignore_index=True)\n", + "\n", + "final_results_df" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "964a26a2-7d07-4269-b672-1ff847dd1eb9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Final tuned results saved to: ../outputs/final_model_comparison_results.csv\n" + ] + } + ], + "source": [ + "final_results_path = \"../outputs/final_model_comparison_results.csv\"\n", + "final_results_df.to_csv(final_results_path, index=False)\n", + "\n", + "print(f\"Final tuned results saved to: {final_results_path}\")" + ] + }, + { + "cell_type": "markdown", + "id": "179ffb3e-1ed3-492f-a9b5-77bd51947805", + "metadata": {}, + "source": [ + "### Hyperparameter Tuning Result\n", + "\n", + "I added a small Random Forest tuning experiment to check whether model performance could improve, especially for the minority `>50K` class.\n", + "\n", + "For the real-data model, tuning did not improve the F1-score compared to the original Random Forest. The original Random Forest had an F1-score of about 0.65, while the tuned version had an F1-score of about 0.63.\n", + "\n", + "For the synthetic-data model, tuning did not help. The tuned synthetic Random Forest predicted only the majority `<=50K` class and completely missed the `>50K` class. This led to precision, recall, and F1-score of 0 for the positive class.\n", + "\n", + "This suggests that the main limitation was not only the classifier settings. The synthetic data preserved many individual column patterns, but it did not preserve enough useful relationships for identifying the minority income group." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a7891fce-e9cd-4e84-9403-fcb06277ed0d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (sdv_project)", + "language": "python", + "name": "sdv_project" + }, + "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.11.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/notebooks/synthetic_data_vault.API.ipynb b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/notebooks/synthetic_data_vault.API.ipynb new file mode 100644 index 000000000..bf3459623 --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/notebooks/synthetic_data_vault.API.ipynb @@ -0,0 +1,259 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "edc93aec", + "metadata": {}, + "source": [ + "# Synthetic Data Vault API Tutorial\n", + "\n", + "This notebook is a short API-style tutorial for the main SDV functions used in this project.\n", + "\n", + "The goal is to show the basic workflow of SDV in a simple and readable way:\n", + "\n", + "1. Create metadata for a single table.\n", + "2. Fit a synthesizer on real data.\n", + "3. Sample synthetic data.\n", + "4. Evaluate synthetic data quality.\n", + "\n", + "This notebook is intentionally smaller than the main project notebook. The full experiment is available in `Synthetic_Data_Vault.ipynb`.\n" + ] + }, + { + "cell_type": "markdown", + "id": "238c94a9", + "metadata": {}, + "source": [ + "## 1. Import Required Libraries\n", + "\n", + "SDV is used for synthetic data generation, while SDMetrics is used to evaluate synthetic data quality.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06d6c0ba", + "metadata": {}, + "outputs": [], + "source": [ + "import warnings\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from sklearn.datasets import fetch_openml\n", + "from sklearn.model_selection import train_test_split\n", + "\n", + "from sdv.metadata import SingleTableMetadata\n", + "from sdv.single_table import GaussianCopulaSynthesizer\n", + "from sdmetrics.reports.single_table import DiagnosticReport, QualityReport\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "RANDOM_STATE = 42\n", + "np.random.seed(RANDOM_STATE)\n", + "\n", + "print(\"Imports completed successfully.\")" + ] + }, + { + "cell_type": "markdown", + "id": "ccdb4fbd", + "metadata": {}, + "source": [ + "## 2. Load and Clean a Small Example Dataset\n", + "\n", + "For the API demo, I use a small sample of the Adult Income dataset so the notebook runs quickly.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bdc2786a", + "metadata": {}, + "outputs": [], + "source": [ + "adult = fetch_openml(name=\"adult\", version=2, as_frame=True)\n", + "df = adult.frame.copy()\n", + "\n", + "df.columns = (\n", + " df.columns\n", + " .str.strip()\n", + " .str.lower()\n", + " .str.replace(\"-\", \"_\")\n", + " .str.replace(\" \", \"_\")\n", + ")\n", + "\n", + "df = df.replace(\"?\", np.nan)\n", + "df = df.dropna().reset_index(drop=True)\n", + "\n", + "for column in df.columns:\n", + " if str(df[column].dtype) == \"category\":\n", + " df[column] = df[column].astype(str)\n", + "\n", + "target_col = \"class\"\n", + "df[target_col] = df[target_col].astype(str).str.strip()\n", + "\n", + "# Use a small sample to keep the API tutorial fast.\n", + "df_sample = df.sample(n=3000, random_state=RANDOM_STATE).reset_index(drop=True)\n", + "\n", + "print(\"Sample shape:\", df_sample.shape)\n", + "df_sample.head()" + ] + }, + { + "cell_type": "markdown", + "id": "bd0cf26a", + "metadata": {}, + "source": [ + "## 3. Detect Metadata\n", + "\n", + "SDV needs metadata to understand the structure of the table. The metadata stores information about column names and data types.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f638abe", + "metadata": {}, + "outputs": [], + "source": [ + "metadata = SingleTableMetadata()\n", + "metadata.detect_from_dataframe(data=df_sample)\n", + "\n", + "print(\"Metadata detected successfully.\")\n", + "metadata.to_dict()" + ] + }, + { + "cell_type": "markdown", + "id": "c55ac168", + "metadata": {}, + "source": [ + "## 4. Fit the Gaussian Copula Synthesizer\n", + "\n", + "The Gaussian Copula synthesizer learns patterns from the real data and then generates new synthetic rows.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82b90055", + "metadata": {}, + "outputs": [], + "source": [ + "synthesizer = GaussianCopulaSynthesizer(\n", + " metadata,\n", + " enforce_min_max_values=True,\n", + " enforce_rounding=True\n", + ")\n", + "\n", + "synthesizer.fit(df_sample)\n", + "\n", + "print(\"Synthesizer fitted successfully.\")" + ] + }, + { + "cell_type": "markdown", + "id": "f9b5a039", + "metadata": {}, + "source": [ + "## 5. Sample Synthetic Data\n", + "\n", + "After fitting, the synthesizer can generate new rows with similar structure and statistical patterns.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab43632d", + "metadata": {}, + "outputs": [], + "source": [ + "synthetic_sample = synthesizer.sample(num_rows=1000)\n", + "\n", + "print(\"Synthetic sample shape:\", synthetic_sample.shape)\n", + "synthetic_sample.head()" + ] + }, + { + "cell_type": "markdown", + "id": "aea76e70", + "metadata": {}, + "source": [ + "## 6. Evaluate Synthetic Data Quality\n", + "\n", + "The Diagnostic Report checks whether the synthetic data follows expected structural rules. \n", + "The Quality Report checks whether the synthetic data is statistically similar to the real data.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "948998e6", + "metadata": {}, + "outputs": [], + "source": [ + "metadata_dict = metadata.to_dict()\n", + "\n", + "diagnostic_report = DiagnosticReport()\n", + "diagnostic_report.generate(\n", + " real_data=df_sample,\n", + " synthetic_data=synthetic_sample,\n", + " metadata=metadata_dict,\n", + " verbose=False\n", + ")\n", + "\n", + "print(\"Diagnostic score:\", diagnostic_report.get_score())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1f93cca", + "metadata": {}, + "outputs": [], + "source": [ + "quality_report = QualityReport()\n", + "quality_report.generate(\n", + " real_data=df_sample,\n", + " synthetic_data=synthetic_sample,\n", + " metadata=metadata_dict,\n", + " verbose=False\n", + ")\n", + "\n", + "print(\"Quality score:\", quality_report.get_score())\n", + "quality_report.get_properties()" + ] + }, + { + "cell_type": "markdown", + "id": "af2e6ac5", + "metadata": {}, + "source": [ + "## API Tutorial Summary\n", + "\n", + "This notebook demonstrated the basic SDV API workflow:\n", + "\n", + "1. Detect metadata from a real table.\n", + "2. Fit a synthetic data generator.\n", + "3. Generate synthetic rows.\n", + "4. Evaluate the synthetic data.\n", + "\n", + "The main project notebook builds on this API workflow by adding EDA, model training, real-vs-synthetic comparison, and result interpretation.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python (sdv_project)", + "language": "python", + "name": "sdv_project" + }, + "language_info": { + "name": "python", + "version": "3.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/outputs/final_model_comparison_results.csv b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/outputs/final_model_comparison_results.csv new file mode 100644 index 000000000..2d475cf2c --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/outputs/final_model_comparison_results.csv @@ -0,0 +1,7 @@ +model,training_data,accuracy,precision,recall,f1_score +Logistic Regression,Real Data,0.8464532106845922,0.7383720930232558,0.5892219842969307,0.6554188169908693 +Logistic Regression,Synthetic Data,0.7581814965505042,0.5557377049180328,0.12098501070663811,0.19871043376318875 +Random Forest,Real Data,0.8537944454272068,0.8064,0.5396145610278372,0.6465683130211675 +Random Forest,Synthetic Data,0.7525207854236688,1.0,0.0014275517487508922,0.002851033499643621 +Tuned Random Forest,Real Data,0.8491066690252963,0.8058035714285714,0.5153461812990721,0.6286460600783631 +Tuned Random Forest,Synthetic Data,0.7521669909782417,0.0,0.0,0.0 diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/outputs/model_comparison_results.csv b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/outputs/model_comparison_results.csv new file mode 100644 index 000000000..a96aabc63 --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/outputs/model_comparison_results.csv @@ -0,0 +1,5 @@ +model,training_data,accuracy,precision,recall,f1_score +Logistic Regression,Real Data,0.8464532106845922,0.7383720930232558,0.5892219842969307,0.6554188169908693 +Logistic Regression,Synthetic Data,0.7581814965505042,0.5557377049180328,0.12098501070663811,0.19871043376318875 +Random Forest,Real Data,0.8537944454272068,0.8064,0.5396145610278372,0.6465683130211675 +Random Forest,Synthetic Data,0.7525207854236688,1.0,0.0014275517487508922,0.002851033499643621 diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/requirements.txt b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/requirements.txt new file mode 100644 index 000000000..35d2232a0 --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/requirements.txt @@ -0,0 +1,10 @@ +pandas +numpy +matplotlib +seaborn +scikit-learn +sdv +sdmetrics +jupyter +notebook +ipykernel diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/run_project_check.py b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/run_project_check.py new file mode 100644 index 000000000..46cdece06 --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/run_project_check.py @@ -0,0 +1,58 @@ +""" +Simple project check for the SDV privacy classification project. + +This script verifies that the main required libraries are installed and that +the project folder contains the expected files. +""" + +from pathlib import Path + +import pandas as pd +import sklearn +import sdv +import sdmetrics + + +def main(): + project_root = Path(__file__).resolve().parent + + expected_files = [ + "README.md", + "requirements.txt", + "notebooks/Synthetic_Data_Vault.ipynb", + "notebooks/synthetic_data_vault.API.ipynb", + "outputs/model_comparison_results.csv", + "outputs/final_model_comparison_results.csv", + "synthetic_data_vault_utils.py", + ] + + print("Checking required project files...") + + missing_files = [] + for file_path in expected_files: + full_path = project_root / file_path + if full_path.exists(): + print(f"FOUND: {file_path}") + else: + print(f"MISSING: {file_path}") + missing_files.append(file_path) + + if missing_files: + raise FileNotFoundError(f"Missing required files: {missing_files}") + + print("\nChecking output results...") + results_path = project_root / "outputs" / "final_model_comparison_results.csv" + results_df = pd.read_csv(results_path) + + print(results_df.head()) + print(f"\nRows in final results file: {len(results_df)}") + + print("\nLibrary imports successful:") + print(f"pandas: {pd.__version__}") + print(f"scikit-learn: {sklearn.__version__}") + + print("\nProject check completed successfully.") + + +if __name__ == "__main__": + main() diff --git a/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/synthetic_data_vault_utils.py b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/synthetic_data_vault_utils.py new file mode 100644 index 000000000..5eabaa3f3 --- /dev/null +++ b/class_project/data605/Spring2026/projects/UmdTask120_DATA605_Spring2026_Synthetic_Data_Vault/synthetic_data_vault_utils.py @@ -0,0 +1,114 @@ +""" +Utility functions for the Synthetic Data Vault privacy classification project. + +These helpers keep repeated notebook logic organized and easier to understand. +""" + +import os +import numpy as np +import pandas as pd + +from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, classification_report + + +def clean_adult_income_dataframe(df, target_col="class"): + """ + Clean the Adult Income dataset. + + Steps: + - standardize column names + - replace '?' with missing values + - drop missing rows + - convert categorical columns to string format + + Args: + df: Raw pandas DataFrame. + target_col: Name of the target column. + + Returns: + Cleaned pandas DataFrame. + """ + clean_df = df.copy() + + clean_df.columns = ( + clean_df.columns + .str.strip() + .str.lower() + .str.replace("-", "_") + .str.replace(" ", "_") + ) + + clean_df = clean_df.replace("?", np.nan) + clean_df = clean_df.dropna().reset_index(drop=True) + + for column in clean_df.columns: + if str(clean_df[column].dtype) == "category": + clean_df[column] = clean_df[column].astype(str) + + clean_df[target_col] = clean_df[target_col].astype(str).str.strip() + + return clean_df + + +def split_features_and_target(dataframe, target_column): + """ + Separate a dataframe into features and target. + + Args: + dataframe: Input pandas DataFrame. + target_column: Name of the target column. + + Returns: + X, y + """ + X = dataframe.drop(columns=[target_column]) + y = dataframe[target_column] + return X, y + + +def evaluate_classifier(model_name, training_data_name, model, X_train, y_train, X_test, y_test, positive_label=">50K"): + """ + Train a classifier and evaluate it on a test set. + + Args: + model_name: Name of the classifier. + training_data_name: Label for the training data source. + model: Scikit-learn model or pipeline. + X_train, y_train: Training data. + X_test, y_test: Testing data. + positive_label: Positive class label. + + Returns: + Dictionary of evaluation metrics. + """ + model.fit(X_train, y_train) + predictions = model.predict(X_test) + + scores = { + "model": model_name, + "training_data": training_data_name, + "accuracy": accuracy_score(y_test, predictions), + "precision": precision_score(y_test, predictions, pos_label=positive_label, zero_division=0), + "recall": recall_score(y_test, predictions, pos_label=positive_label, zero_division=0), + "f1_score": f1_score(y_test, predictions, pos_label=positive_label, zero_division=0), + } + + print("=" * 70) + print(f"{model_name} trained on {training_data_name}") + print("=" * 70) + print(classification_report(y_test, predictions, zero_division=0)) + + return scores + + +def save_results(results_df, output_path): + """ + Save model results to a CSV file. + + Args: + results_df: Results dataframe. + output_path: Location where the CSV should be saved. + """ + os.makedirs(os.path.dirname(output_path), exist_ok=True) + results_df.to_csv(output_path, index=False) + print(f"Results saved to: {output_path}")