From 559b450b8b51b5a77cf37fed6f1660a8314af5f7 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Fri, 14 Aug 2026 21:05:35 +0200 Subject: [PATCH 1/4] Expand examples --- examples/README.rst | 31 ++++- examples/pipeline_mlp_classifier.py | 108 +++++++++++++++ examples/pipeline_mlp_regressor.py | 103 ++++++++++++++ examples/pytorch_demand_regressor.py | 118 ++++++++++++++++ examples/pytorch_fraud_detector.py | 6 +- examples/pytorch_maintenance_classifier.py | 148 +++++++++++++++++++++ 6 files changed, 511 insertions(+), 3 deletions(-) create mode 100644 examples/pipeline_mlp_classifier.py create mode 100644 examples/pipeline_mlp_regressor.py create mode 100644 examples/pytorch_demand_regressor.py create mode 100644 examples/pytorch_maintenance_classifier.py diff --git a/examples/README.rst b/examples/README.rst index 3b30a9c..c65cc26 100644 --- a/examples/README.rst +++ b/examples/README.rst @@ -1,2 +1,31 @@ A few examples with significant test cases -that show how to use the orbital library. \ No newline at end of file +that show how to use the orbital library. + +scikit-learn (``pipeline_*.py``) +--------------------------------- + +- ``pipeline_lineareg.py`` -- Linear Regression +- ``pipeline_logisticreg.py`` -- multiclass Logistic Regression +- ``pipeline_lasso.py`` -- Lasso Regression +- ``pipeline_elasticnet.py`` -- Elastic Net Regression +- ``pipeline_decision_tree_classifier.py`` -- Decision Tree Classifier +- ``pipeline_decision_tree_regressor.py`` -- Decision Tree Regressor +- ``pipeline_randforest_classifier.py`` -- Random Forest Classifier +- ``pipeline_boosted_tree_classifier.py`` -- Gradient Boosted Tree multiclass Classifier +- ``pipeline_boosted_tree_binary_classifier.py`` -- Gradient Boosted Tree binary Classifier +- ``pipeline_boosted_tree_regressor.py`` -- Gradient Boosted Tree Regressor +- ``pipeline_mlp_classifier.py`` -- MLP binary Classifier (``MLPClassifier``) +- ``pipeline_mlp_regressor.py`` -- MLP Regressor (``MLPRegressor``, ``tanh`` activation) + +PyTorch (``pytorch_*.py``) +--------------------------- + +- ``pytorch_fraud_detector.py`` -- binary classification (fraud detection) +- ``pytorch_maintenance_classifier.py`` -- multiclass classification (predictive maintenance) +- ``pytorch_demand_regressor.py`` -- regression (demand forecasting) + +Other +----- + +- ``minimal.py`` -- smallest possible pipeline +- ``simple_tree_regressor.py`` -- Decision Tree Regressor without ibis \ No newline at end of file diff --git a/examples/pipeline_mlp_classifier.py b/examples/pipeline_mlp_classifier.py new file mode 100644 index 0000000..23fa81f --- /dev/null +++ b/examples/pipeline_mlp_classifier.py @@ -0,0 +1,108 @@ +import os +import logging + +import ibis +import numpy as np +import pyarrow as pa +from sklearn.datasets import load_breast_cancer +from sklearn.neural_network import MLPClassifier +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + +import orbital +import orbital.types + +PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) +ASSERT = int(os.environ.get("ASSERT", "0")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +BACKEND = os.environ.get("BACKEND", "duckdb").lower() + +if BACKEND not in {"duckdb", "sqlite"}: + raise ValueError(f"Unsupported backend {BACKEND!r}") + +logging.basicConfig(level=logging.INFO) +logging.getLogger("orbital").setLevel( + logging.INFO +) # Set DEBUG to see translation process. + +# Breast cancer diagnosis: binary classification (malignant vs benign) is +# the most common real-world MLPClassifier use case (churn, fraud, credit risk). +cancer = load_breast_cancer(as_frame=True) +X = cancer.data +# SQL and orbital don't like spaces in column names, replace them with underscores +X.columns = [cname.replace(" ", "_") for cname in X.columns] +y = cancer.target + +pipeline = Pipeline( + [ + ("scaler", StandardScaler()), + ( + "mlp", + MLPClassifier(hidden_layer_sizes=(64, 32), max_iter=500, random_state=42), + ), + ] +) +pipeline.fit(X, y) + +# Prepare the inputs outside the benchmarked function. +features = orbital.types.guess_datatypes(X) +# Rows 0/1 are malignant and 19/20 are benign, mixing both classes in the demo output. +# reset_index avoids pyarrow adding the non-contiguous original index as an extra column. +example_data = pa.Table.from_pandas(X.iloc[[0, 1, 19, 20]].reset_index(drop=True)) +con = { + "sqlite": lambda: ibis.sqlite.connect(":memory:"), + "duckdb": lambda: ibis.duckdb.connect(), +}[BACKEND]() +if PRINT_SQL: + con.create_table("DATA_TABLE", obj=example_data) + + +def main(): + orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) + print(orbital_pipeline) + + if PRINT_SQL: + sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) + print(f"\nGenerated Query for {BACKEND.upper()}:") + print(sql) + print("\nPrediction with SQL") + print(con.raw_sql(sql).fetchall()) + + print("\nPrediction with Ibis") + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + ibis_result = con.execute(ibis_expression) + print(ibis_result) + + if PREDICT_WITH_LIBRARY: + print("\nPrediction with SKLearn") + test_df = example_data.to_pandas() + sklearn_labels = pipeline.predict(test_df) + sklearn_probabilities = pipeline.predict_proba(test_df) + print(f"Labels: {sklearn_labels}") + print(f"Probabilities: {sklearn_probabilities}") + + if ASSERT and PREDICT_WITH_LIBRARY: + assert np.array_equal(sklearn_labels, ibis_result["output_label"]), ( + "Labels do not match!" + ) + + # Binary classification should produce exactly 2 probability columns + prob_cols = [ + col for col in ibis_result.columns if col.startswith("output_probability.") + ] + assert len(prob_cols) == 2, ( + f"Expected exactly 2 probability columns, got {len(prob_cols)}" + ) + for i, col in enumerate(prob_cols): + np.testing.assert_allclose( + sklearn_probabilities[:, i], + ibis_result[col].to_numpy(), + atol=1e-4, + err_msg=f"Probabilities for {col} don't match sklearn", + ) + print("\nLabels and probabilities match!") + + +if __name__ == "__main__": + main() diff --git a/examples/pipeline_mlp_regressor.py b/examples/pipeline_mlp_regressor.py new file mode 100644 index 0000000..74faaba --- /dev/null +++ b/examples/pipeline_mlp_regressor.py @@ -0,0 +1,103 @@ +import os +import logging + +import ibis +import numpy as np +import pandas as pd +import pyarrow as pa +from sklearn.neural_network import MLPRegressor +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + +import orbital +import orbital.types + +PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) +ASSERT = int(os.environ.get("ASSERT", "0")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +BACKEND = os.environ.get("BACKEND", "duckdb").lower() + +if BACKEND not in {"duckdb", "sqlite"}: + raise ValueError(f"Unsupported backend {BACKEND!r}") + +logging.basicConfig(level=logging.INFO) +logging.getLogger("orbital").setLevel( + logging.INFO +) # Set DEBUG to see translation process. + +# House price prediction: sqft, bedrooms and age drive the price, with +# some noise to keep it realistic. activation="tanh" exercises the Tanh +# translator on the sklearn MLP path (PyTorch already covers ReLU/Sigmoid). +rng = np.random.default_rng(7) +num_samples = 800 +sqft = rng.uniform(500, 4000, num_samples) +bedrooms = rng.integers(1, 6, num_samples).astype(np.float64) +age_years = rng.uniform(0, 80, num_samples) +noise = rng.normal(0, 1.5, num_samples) +# Price in $10,000s (48.2 == $482,000): MLPRegressor's default Adam learning +# rate saturates tanh units when trained directly on raw dollar-scale +# targets, keeping the target in the tens/hundreds avoids that without +# changing any solver hyperparameters. +price = sqft * 0.021 + bedrooms * 0.9 - age_years * 0.04 + noise +price = np.clip(price, 5, None) + +X = pd.DataFrame({"sqft": sqft, "bedrooms": bedrooms, "age_years": age_years}) + +pipeline = Pipeline( + [ + ("scaler", StandardScaler()), + ( + "mlp", + MLPRegressor( + hidden_layer_sizes=(32, 16), + activation="tanh", + max_iter=2000, + random_state=7, + ), + ), + ] +) +pipeline.fit(X, price) + +# Prepare the inputs outside the benchmarked function. +features = orbital.types.guess_datatypes(X) +example_data = pa.Table.from_pandas(X.head(4)) +con = { + "sqlite": lambda: ibis.sqlite.connect(":memory:"), + "duckdb": lambda: ibis.duckdb.connect(), +}[BACKEND]() +if PRINT_SQL: + con.create_table("DATA_TABLE", obj=example_data) + + +def main(): + orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) + print(orbital_pipeline) + + if PRINT_SQL: + sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) + print(f"\nGenerated Query for {BACKEND.upper()}:") + print(sql) + print("\nPrediction with SQL") + print(con.raw_sql(sql).fetchall()) + + print("\nPrediction with Ibis") + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + ibis_predictions = con.execute(ibis_expression) + print(ibis_predictions) + + if PREDICT_WITH_LIBRARY: + print("\nPrediction with SKLearn") + predictions = pipeline.predict(example_data.to_pandas()) + print(predictions) + + if ASSERT and PREDICT_WITH_LIBRARY: + assert np.allclose(ibis_predictions["variable"], predictions, atol=1e-3), ( + "Predictions do not match!" + ) + print("\nPredictions match!") + + +if __name__ == "__main__": + main() diff --git a/examples/pytorch_demand_regressor.py b/examples/pytorch_demand_regressor.py new file mode 100644 index 0000000..d5bc749 --- /dev/null +++ b/examples/pytorch_demand_regressor.py @@ -0,0 +1,118 @@ +"""Translate a PyTorch demand-forecasting network into SQL. + +A retail demand network (price, promo flag, day of week, prior-week sales +-> 64 -> 64 hidden neurons with ReLU, no output activation) predicts units +sold for the coming week. Trained and converted to a SQL query that +computes the same predictions directly inside DuckDB. + +Unlike the fraud and maintenance classifiers, this is a plain regression +network: no Sigmoid/Softmax squashes the output, and there is currently no +other NN regression example in this repository. + +This example requires PyTorch: pip install orbital[pytorch] +""" + +import os + +import duckdb +import numpy as np +import pandas as pd +import torch + +import orbital +import orbital.types + +PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) +ASSERT = int(os.environ.get("ASSERT", "0")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) + +FEATURES = { + "price": orbital.types.DoubleColumnType(), + "promo": orbital.types.DoubleColumnType(), + "day_of_week": orbital.types.DoubleColumnType(), + "prior_week_sales": orbital.types.DoubleColumnType(), +} + +np.random.seed(42) +torch.manual_seed(42) + +# Synthetic weekly sales: higher price reduces demand, promos and a strong +# prior week both increase it, weekends (day_of_week 5-6) taper it slightly. +num_samples = 1500 +price = np.random.uniform(5, 50, num_samples) +promo = np.random.binomial(1, 0.3, num_samples).astype(np.float64) +day_of_week = np.random.randint(0, 7, num_samples).astype(np.float64) +prior_week_sales = np.random.uniform(50, 500, num_samples) +noise = np.random.normal(0, 10, num_samples) +units_sold = ( + 150 - 1.8 * price + 60 * promo - 4 * day_of_week + 0.4 * prior_week_sales + noise +) +units_sold = np.clip(units_sold, 0, None) + +X_train = np.column_stack([price, promo, day_of_week, prior_week_sales]).astype( + np.float32 +) +y_train = units_sold.astype(np.float32) + +model = torch.nn.Sequential( + torch.nn.Linear(len(FEATURES), 64), + torch.nn.ReLU(), + torch.nn.Linear(64, 64), + torch.nn.ReLU(), + torch.nn.Linear(64, 1), +) + +criterion = torch.nn.MSELoss() +optimizer = torch.optim.Adam(model.parameters(), lr=0.01) +X_tensor = torch.from_numpy(X_train) +y_tensor = torch.from_numpy(y_train.reshape(-1, 1)) +for epoch in range(300): + optimizer.zero_grad() + loss = criterion(model(X_tensor), y_tensor) + loss.backward() + optimizer.step() +print(f"Trained model, final loss: {loss.item():.4f}") + +# Prepare the inputs outside the benchmarked function. +test_data = pd.DataFrame( + { + "price": [10.0, 45.0, 25.0, 15.0], + "promo": [1.0, 0.0, 0.0, 1.0], + "day_of_week": [5.0, 1.0, 3.0, 6.0], + "prior_week_sales": [400.0, 80.0, 250.0, 300.0], + } +) +duckdb.register("weekly_sales", test_data) + + +def main(): + pipeline = orbital.parse_pytorch_model(model, FEATURES) + + sql = orbital.export_sql("weekly_sales", pipeline, dialect="duckdb") + if PRINT_SQL: + print("\nGenerated Query for DuckDB:") + print(sql) + + sql_predictions = duckdb.sql(sql).df().iloc[:, 0].to_numpy() + print("\nPrediction with SQL") + print(sql_predictions) + + if PREDICT_WITH_LIBRARY: + print("\nPrediction with PyTorch") + with torch.no_grad(): + torch_predictions = ( + model(torch.from_numpy(test_data.to_numpy(dtype=np.float32))) + .numpy() + .flatten() + ) + print(torch_predictions) + + if ASSERT: + assert np.allclose(sql_predictions, torch_predictions, atol=1e-4), ( + "SQL and PyTorch predictions do not match" + ) + print("\nSQL and PyTorch predictions match.") + + +if __name__ == "__main__": + main() diff --git a/examples/pytorch_fraud_detector.py b/examples/pytorch_fraud_detector.py index 35cc340..2bbc3c3 100644 --- a/examples/pytorch_fraud_detector.py +++ b/examples/pytorch_fraud_detector.py @@ -1,6 +1,6 @@ """Translate a PyTorch neural network into SQL. -A tiny fraud detection network (4 inputs -> 8 hidden neurons with ReLU +A fraud detection network (4 inputs -> 16 -> 8 hidden neurons with ReLU -> 1 sigmoid output) is trained in PyTorch and converted to a SQL query that computes the same predictions directly inside DuckDB. @@ -47,7 +47,9 @@ y_train = (np.random.rand(num_samples) < fraud_prob).astype(np.float32) model = torch.nn.Sequential( - torch.nn.Linear(len(FEATURES), 8), + torch.nn.Linear(len(FEATURES), 16), + torch.nn.ReLU(), + torch.nn.Linear(16, 8), torch.nn.ReLU(), torch.nn.Linear(8, 1), torch.nn.Sigmoid(), diff --git a/examples/pytorch_maintenance_classifier.py b/examples/pytorch_maintenance_classifier.py new file mode 100644 index 0000000..2485e1b --- /dev/null +++ b/examples/pytorch_maintenance_classifier.py @@ -0,0 +1,148 @@ +"""Translate a PyTorch predictive-maintenance network into SQL. + +A machine-health network (5 sensor readings -> 64 -> 32 hidden neurons with +ReLU + BatchNorm1d + Dropout -> 3-way Softmax) predicts one of three failure +modes {normal, bearing_wear, overheating}. Trained and converted to a SQL +query that computes the same predictions directly inside DuckDB. + +BatchNorm1d and Dropout are training-time regularization layers: in eval +mode BatchNorm1d becomes a fixed per-channel affine transform (folded into +the preceding layer at export) and Dropout becomes a no-op, so they add no +extra translation work but prove real regularized architectures survive +conversion, not just toy Linear+activation stacks. + +This example requires PyTorch: pip install orbital[pytorch] +""" + +import os + +import duckdb +import numpy as np +import pandas as pd +import torch + +import orbital +import orbital.types + +PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) +ASSERT = int(os.environ.get("ASSERT", "0")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) + +FEATURES = { + "temperature": orbital.types.DoubleColumnType(), + "vibration": orbital.types.DoubleColumnType(), + "pressure": orbital.types.DoubleColumnType(), + "rpm": orbital.types.DoubleColumnType(), + "age": orbital.types.DoubleColumnType(), +} +FAILURE_MODES = ["normal", "bearing_wear", "overheating"] + +np.random.seed(42) +torch.manual_seed(42) + +# Synthetic sensor readings: overheating tracks temperature, bearing wear +# tracks vibration and age. Each sample is labelled by the highest of three +# noisy scores, so the classes overlap like real sensor data instead of +# being perfectly separable. +num_samples = 1500 +temperature = np.random.normal(70, 15, num_samples) +vibration = np.random.exponential(1.0, num_samples) +pressure = np.random.normal(100, 20, num_samples) +rpm = np.random.normal(1800, 300, num_samples) +age = np.random.uniform(0, 10, num_samples) + +score_noise = np.random.normal(0, 0.4, (num_samples, 3)) +scores = ( + np.column_stack( + [ + np.zeros(num_samples), # normal: baseline score + (temperature - 85) / 10, # overheating: rises with temperature + (vibration - 1.2) * 2 + (age - 5) / 5, # bearing_wear: vibration + age + ] + ) + + score_noise +) +y_train = scores.argmax(axis=1) + +X_train = np.column_stack([temperature, vibration, pressure, rpm, age]).astype( + np.float32 +) + +model = torch.nn.Sequential( + torch.nn.Linear(len(FEATURES), 64), + torch.nn.BatchNorm1d(64), + torch.nn.ReLU(), + torch.nn.Dropout(0.2), + torch.nn.Linear(64, 32), + torch.nn.BatchNorm1d(32), + torch.nn.ReLU(), + torch.nn.Dropout(0.2), + torch.nn.Linear(32, len(FAILURE_MODES)), + torch.nn.Softmax(dim=1), +) + +# The model's last layer is already a Softmax, so training needs the +# probability-based counterpart of cross entropy (NLLLoss on log +# probabilities) rather than CrossEntropyLoss, which expects raw logits +# and would apply a second softmax internally. +criterion = torch.nn.NLLLoss() +optimizer = torch.optim.Adam(model.parameters(), lr=0.01) +X_tensor = torch.from_numpy(X_train) +y_tensor = torch.from_numpy(y_train.astype(np.int64)) +model.train() +for epoch in range(300): + optimizer.zero_grad() + output = model(X_tensor) + loss = criterion(torch.log(output + 1e-12), y_tensor) + loss.backward() + optimizer.step() +print(f"Trained model, final loss: {loss.item():.4f}") + +# Prepare the inputs outside the benchmarked function. +test_data = pd.DataFrame( + { + "temperature": [65.0, 95.0, 68.0, 72.0], + "vibration": [0.8, 0.9, 3.2, 1.0], + "pressure": [98.0, 105.0, 101.0, 99.0], + "rpm": [1790.0, 1850.0, 1770.0, 1800.0], + "age": [2.0, 3.0, 8.5, 4.0], + } +) +duckdb.register("sensor_readings", test_data) + + +def main(): + pipeline = orbital.parse_pytorch_model(model, FEATURES) + + sql = orbital.export_sql("sensor_readings", pipeline, dialect="duckdb") + if PRINT_SQL: + print("\nGenerated Query for DuckDB:") + print(sql) + + # The model has no ZipMap step (that is an sklearn-classifier concept), + # so the three Softmax outputs surface as plain columns "softmax.out_0..2". + sql_predictions = duckdb.sql(sql).df().to_numpy() + print("\nPrediction with SQL") + print(sql_predictions) + + if PREDICT_WITH_LIBRARY: + print("\nPrediction with PyTorch") + # The SQL was generated from the eval-mode graph (fixed BatchNorm1d + # stats, Dropout disabled). Without eval() here, Dropout would + # randomly zero activations and the two predictions would diverge. + model.eval() + with torch.no_grad(): + torch_predictions = model( + torch.from_numpy(test_data.to_numpy(dtype=np.float32)) + ).numpy() + print(torch_predictions) + + if ASSERT: + assert np.allclose(sql_predictions, torch_predictions, atol=1e-5), ( + "SQL and PyTorch predictions do not match" + ) + print("\nSQL and PyTorch predictions match.") + + +if __name__ == "__main__": + main() From 1d8506fd6f18e09a2d9f99baabc8944bffddb7c7 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Fri, 14 Aug 2026 21:23:19 +0200 Subject: [PATCH 2/4] fix ASSERT --- examples/pipeline_boosted_tree_binary_classifier.py | 4 ++-- examples/pipeline_boosted_tree_classifier.py | 4 ++-- examples/pipeline_boosted_tree_regressor.py | 4 ++-- examples/pipeline_decision_tree_classifier.py | 4 ++-- examples/pipeline_decision_tree_regressor.py | 4 ++-- examples/pipeline_elasticnet.py | 4 ++-- examples/pipeline_lasso.py | 4 ++-- examples/pipeline_lineareg.py | 4 ++-- examples/pipeline_logisticreg.py | 4 ++-- examples/pipeline_mlp_classifier.py | 4 ++-- examples/pipeline_mlp_regressor.py | 4 ++-- examples/pipeline_randforest_classifier.py | 4 ++-- examples/pytorch_demand_regressor.py | 2 +- examples/pytorch_fraud_detector.py | 2 +- examples/pytorch_maintenance_classifier.py | 2 +- 15 files changed, 27 insertions(+), 27 deletions(-) diff --git a/examples/pipeline_boosted_tree_binary_classifier.py b/examples/pipeline_boosted_tree_binary_classifier.py index ef49efa..495616a 100644 --- a/examples/pipeline_boosted_tree_binary_classifier.py +++ b/examples/pipeline_boosted_tree_binary_classifier.py @@ -17,7 +17,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -124,7 +124,7 @@ def main(): ibis_result = con.execute(ibis_expression) print(ibis_result) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.array_equal(sklearn_predictions, ibis_result["output_label"]), "Predictions do not match!" # Binary classification should produce exactly 2 probability columns diff --git a/examples/pipeline_boosted_tree_classifier.py b/examples/pipeline_boosted_tree_classifier.py index 63b9058..5ce8d55 100644 --- a/examples/pipeline_boosted_tree_classifier.py +++ b/examples/pipeline_boosted_tree_classifier.py @@ -17,7 +17,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -139,7 +139,7 @@ def main(): ibis_target = con.execute(ibis_expression) print(ibis_target) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.array_equal(target, ibis_target["output_label"]), "Predictions do not match!" print("\nPredictions match!") diff --git a/examples/pipeline_boosted_tree_regressor.py b/examples/pipeline_boosted_tree_regressor.py index 6f5cd3b..0b0db77 100644 --- a/examples/pipeline_boosted_tree_regressor.py +++ b/examples/pipeline_boosted_tree_regressor.py @@ -17,7 +17,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -122,7 +122,7 @@ def main(): ibis_target = con.execute(ibis_expression)["variable"].to_numpy() print(ibis_target) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.allclose(target, ibis_target), "Predictions do not match!" print("\nPredictions match!") diff --git a/examples/pipeline_decision_tree_classifier.py b/examples/pipeline_decision_tree_classifier.py index 3cf468f..3c17ee7 100644 --- a/examples/pipeline_decision_tree_classifier.py +++ b/examples/pipeline_decision_tree_classifier.py @@ -17,7 +17,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -132,7 +132,7 @@ def main(): target = pipeline.predict(test_df) print(target) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.array_equal(target, ibis_target["output_label"]), "Predictions do not match!" print("\nPredictions match!") diff --git a/examples/pipeline_decision_tree_regressor.py b/examples/pipeline_decision_tree_regressor.py index 248c9c4..c99e4e2 100644 --- a/examples/pipeline_decision_tree_regressor.py +++ b/examples/pipeline_decision_tree_regressor.py @@ -17,7 +17,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -113,7 +113,7 @@ def main(): ibis_target = con.execute(ibis_expression)["variable"].to_numpy() print(ibis_target) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.allclose(target, ibis_target), "Predictions do not match!" print("\nPredictions match!") diff --git a/examples/pipeline_elasticnet.py b/examples/pipeline_elasticnet.py index 6530b5a..f3d6502 100644 --- a/examples/pipeline_elasticnet.py +++ b/examples/pipeline_elasticnet.py @@ -14,7 +14,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -90,7 +90,7 @@ def main(): target = pipeline.predict(example_data.to_pandas()) print(target) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.allclose(target, ibis_target), "Predictions do not match!" print("\nPredictions match!") diff --git a/examples/pipeline_lasso.py b/examples/pipeline_lasso.py index a88a50f..fad3eac 100644 --- a/examples/pipeline_lasso.py +++ b/examples/pipeline_lasso.py @@ -15,7 +15,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -87,7 +87,7 @@ def main(): target = pipeline.predict(example_data.to_pandas()) print(target) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.allclose(target, ibis_target), "Predictions do not match!" print("\nPredictions match!") diff --git a/examples/pipeline_lineareg.py b/examples/pipeline_lineareg.py index 23ffadf..0007e19 100644 --- a/examples/pipeline_lineareg.py +++ b/examples/pipeline_lineareg.py @@ -15,7 +15,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -88,7 +88,7 @@ def main(): predictions = pipeline.predict(example_data.to_pandas()) print(predictions) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.allclose(ibis_predictions["variable"], predictions), "Predictions do not match!" print("\nPredictions match!") diff --git a/examples/pipeline_logisticreg.py b/examples/pipeline_logisticreg.py index 8ca44b6..aea7908 100644 --- a/examples/pipeline_logisticreg.py +++ b/examples/pipeline_logisticreg.py @@ -16,7 +16,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -95,7 +95,7 @@ def main(): target = pipeline.predict(test_df) print(target) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.array_equal(target, ibis_target["output_label"]), "Predictions do not match!" print("\nPredictions match!") diff --git a/examples/pipeline_mlp_classifier.py b/examples/pipeline_mlp_classifier.py index 23fa81f..eb2ba86 100644 --- a/examples/pipeline_mlp_classifier.py +++ b/examples/pipeline_mlp_classifier.py @@ -14,7 +14,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -82,7 +82,7 @@ def main(): print(f"Labels: {sklearn_labels}") print(f"Probabilities: {sklearn_probabilities}") - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.array_equal(sklearn_labels, ibis_result["output_label"]), ( "Labels do not match!" ) diff --git a/examples/pipeline_mlp_regressor.py b/examples/pipeline_mlp_regressor.py index 74faaba..07fdfb6 100644 --- a/examples/pipeline_mlp_regressor.py +++ b/examples/pipeline_mlp_regressor.py @@ -14,7 +14,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -92,7 +92,7 @@ def main(): predictions = pipeline.predict(example_data.to_pandas()) print(predictions) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.allclose(ibis_predictions["variable"], predictions, atol=1e-3), ( "Predictions do not match!" ) diff --git a/examples/pipeline_randforest_classifier.py b/examples/pipeline_randforest_classifier.py index ba194b2..097fbf2 100644 --- a/examples/pipeline_randforest_classifier.py +++ b/examples/pipeline_randforest_classifier.py @@ -16,7 +16,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT BACKEND = os.environ.get("BACKEND", "duckdb").lower() if BACKEND not in {"duckdb", "sqlite"}: @@ -93,7 +93,7 @@ def main(): target = pipeline.predict(test_df) print(target) - if ASSERT and PREDICT_WITH_LIBRARY: + if ASSERT: assert np.array_equal(target, ibis_target["output_label"]), "Predictions do not match!" print("\nPredictions match!") diff --git a/examples/pytorch_demand_regressor.py b/examples/pytorch_demand_regressor.py index d5bc749..299ef24 100644 --- a/examples/pytorch_demand_regressor.py +++ b/examples/pytorch_demand_regressor.py @@ -24,7 +24,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT FEATURES = { "price": orbital.types.DoubleColumnType(), diff --git a/examples/pytorch_fraud_detector.py b/examples/pytorch_fraud_detector.py index 2bbc3c3..b0c53b3 100644 --- a/examples/pytorch_fraud_detector.py +++ b/examples/pytorch_fraud_detector.py @@ -19,7 +19,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT FEATURES = { "amount": orbital.types.DoubleColumnType(), diff --git a/examples/pytorch_maintenance_classifier.py b/examples/pytorch_maintenance_classifier.py index 2485e1b..64b3c12 100644 --- a/examples/pytorch_maintenance_classifier.py +++ b/examples/pytorch_maintenance_classifier.py @@ -26,7 +26,7 @@ PRINT_SQL = int(os.environ.get("PRINT_SQL", "0")) ASSERT = int(os.environ.get("ASSERT", "0")) -PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) +PREDICT_WITH_LIBRARY = int(os.environ.get("PREDICT_WITH_LIBRARY", "1")) or ASSERT FEATURES = { "temperature": orbital.types.DoubleColumnType(), From 56445fdec7f73e1941f4c55f06d20a9b70ff31c6 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Fri, 14 Aug 2026 22:06:18 +0200 Subject: [PATCH 3/4] consolidate examples structure --- ...pipeline_boosted_tree_binary_classifier.py | 13 ++++--- examples/pipeline_boosted_tree_classifier.py | 19 +++++++--- examples/pipeline_boosted_tree_regressor.py | 12 +++++-- examples/pipeline_decision_tree_classifier.py | 16 ++++++--- examples/pipeline_decision_tree_regressor.py | 17 +++++---- examples/pipeline_elasticnet.py | 12 +++++-- examples/pipeline_lasso.py | 12 +++++-- examples/pipeline_lineareg.py | 12 +++++-- examples/pipeline_logisticreg.py | 12 +++++-- examples/pipeline_mlp_classifier.py | 12 +++++-- examples/pipeline_mlp_regressor.py | 12 +++++-- examples/pipeline_randforest_classifier.py | 16 ++++++--- examples/pytorch_demand_regressor.py | 35 +++++++++++++------ examples/pytorch_fraud_detector.py | 35 +++++++++++++------ examples/pytorch_maintenance_classifier.py | 35 +++++++++++++------ 15 files changed, 192 insertions(+), 78 deletions(-) diff --git a/examples/pipeline_boosted_tree_binary_classifier.py b/examples/pipeline_boosted_tree_binary_classifier.py index 495616a..9c23077 100644 --- a/examples/pipeline_boosted_tree_binary_classifier.py +++ b/examples/pipeline_boosted_tree_binary_classifier.py @@ -99,11 +99,18 @@ def categorize_price_binary(price: float) -> str: con.create_table("DATA_TABLE", obj=data_sample) -def main(): - # Convert the model to an execution pipeline +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(model, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(data_sample).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + +def main(): if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) print(f"\nGenerated Query for {BACKEND.upper()}:") @@ -119,8 +126,6 @@ def main(): print(f"Probabilities: {sklearn_probabilities}") print("\nPrediction with Ibis") - ibis_table = ibis.memtable(data_sample).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_result = con.execute(ibis_expression) print(ibis_result) diff --git a/examples/pipeline_boosted_tree_classifier.py b/examples/pipeline_boosted_tree_classifier.py index 5ce8d55..3c361d5 100644 --- a/examples/pipeline_boosted_tree_classifier.py +++ b/examples/pipeline_boosted_tree_classifier.py @@ -110,11 +110,24 @@ def categorize_price(price: float) -> str: con.create_table("DATA_TABLE", obj=data_sample) -def main(): - # Convert the model to an execution pipeline +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(model, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(data_sample).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +# Sqlite can't execute this query (see the FIXME in main below); translate() +# alone already takes tens of seconds for this pipeline regardless of +# backend, so skip building it here too instead of paying that cost only +# to then skip execution in main(). +orbital_pipeline = ibis_expression = None +if BACKEND != "sqlite": + orbital_pipeline, ibis_expression = translate_to_orbital() + +def main(): if BACKEND == "sqlite": # FIXME: Sqlite currently can't handle the boosted tree classifier SQL print("Skipping sqlite as it can't handle the query") @@ -134,8 +147,6 @@ def main(): print(target) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(data_sample).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_target = con.execute(ibis_expression) print(ibis_target) diff --git a/examples/pipeline_boosted_tree_regressor.py b/examples/pipeline_boosted_tree_regressor.py index 0b0db77..a579ca2 100644 --- a/examples/pipeline_boosted_tree_regressor.py +++ b/examples/pipeline_boosted_tree_regressor.py @@ -100,10 +100,18 @@ con.create_table("DATA_TABLE", obj=data_sample) -def main(): +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(model, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(data_sample).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + +def main(): if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) print(f"\nGenerated Query for {BACKEND.upper()}:") @@ -117,8 +125,6 @@ def main(): print(target) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(data_sample).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_target = con.execute(ibis_expression)["variable"].to_numpy() print(ibis_target) diff --git a/examples/pipeline_decision_tree_classifier.py b/examples/pipeline_decision_tree_classifier.py index 3c17ee7..7f6f544 100644 --- a/examples/pipeline_decision_tree_classifier.py +++ b/examples/pipeline_decision_tree_classifier.py @@ -107,11 +107,19 @@ def categorize_area(a: float) -> str: con.create_table("DATA_TABLE", obj=example_data) -def main(): - print("orbital Features:", features) - +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + + +def main(): + print("orbital Features:", features) if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) @@ -121,8 +129,6 @@ def main(): print(con.raw_sql(sql).fetchall()) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_target = con.execute(ibis_expression) print(ibis_target) diff --git a/examples/pipeline_decision_tree_regressor.py b/examples/pipeline_decision_tree_regressor.py index c99e4e2..702003b 100644 --- a/examples/pipeline_decision_tree_regressor.py +++ b/examples/pipeline_decision_tree_regressor.py @@ -87,12 +87,19 @@ con.create_table("DATA_TABLE", obj=example_data) -def main(): - print("orbital Features:", features) - - # Convert the pipeline to SQL with Orbital +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + + +def main(): + print("orbital Features:", features) if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) @@ -108,8 +115,6 @@ def main(): print(target) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_target = con.execute(ibis_expression)["variable"].to_numpy() print(ibis_target) diff --git a/examples/pipeline_elasticnet.py b/examples/pipeline_elasticnet.py index f3d6502..02b947c 100644 --- a/examples/pipeline_elasticnet.py +++ b/examples/pipeline_elasticnet.py @@ -68,10 +68,18 @@ con.create_table("DATA_TABLE", obj=example_data) -def main(): +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + +def main(): if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) print(f"\nGenerated Query for {BACKEND.upper()}:") @@ -80,8 +88,6 @@ def main(): print(con.raw_sql(sql).fetchall()) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_target = con.execute(ibis_expression)["variable"].to_numpy() print(ibis_target) diff --git a/examples/pipeline_lasso.py b/examples/pipeline_lasso.py index fad3eac..3da868d 100644 --- a/examples/pipeline_lasso.py +++ b/examples/pipeline_lasso.py @@ -65,10 +65,18 @@ con.create_table("DATA_TABLE", obj=example_data) -def main(): +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + +def main(): if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) print(f"\nGenerated Query for {BACKEND.upper()}:") @@ -77,8 +85,6 @@ def main(): print(con.raw_sql(sql).fetchall()) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_target = con.execute(ibis_expression)["variable"].to_numpy() print(ibis_target) diff --git a/examples/pipeline_lineareg.py b/examples/pipeline_lineareg.py index 0007e19..4aac0b2 100644 --- a/examples/pipeline_lineareg.py +++ b/examples/pipeline_lineareg.py @@ -66,10 +66,18 @@ con.create_table("DATA_TABLE", obj=example_data) -def main(): +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + +def main(): if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) print(f"\nGenerated Query for {BACKEND.upper()}:") @@ -78,8 +86,6 @@ def main(): print(con.raw_sql(sql).fetchall()) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_predictions = con.execute(ibis_expression) print(ibis_predictions) diff --git a/examples/pipeline_logisticreg.py b/examples/pipeline_logisticreg.py index aea7908..cd14701 100644 --- a/examples/pipeline_logisticreg.py +++ b/examples/pipeline_logisticreg.py @@ -72,10 +72,18 @@ con.create_table("DATA_TABLE", obj=example_data) -def main(): +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + +def main(): if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) print(f"\nGenerated Query for {BACKEND.upper()}:") @@ -84,8 +92,6 @@ def main(): print(con.raw_sql(sql).fetchall()) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_target = con.execute(ibis_expression) print(ibis_target) diff --git a/examples/pipeline_mlp_classifier.py b/examples/pipeline_mlp_classifier.py index eb2ba86..87b5ed3 100644 --- a/examples/pipeline_mlp_classifier.py +++ b/examples/pipeline_mlp_classifier.py @@ -57,10 +57,18 @@ con.create_table("DATA_TABLE", obj=example_data) -def main(): +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + +def main(): if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) print(f"\nGenerated Query for {BACKEND.upper()}:") @@ -69,8 +77,6 @@ def main(): print(con.raw_sql(sql).fetchall()) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_result = con.execute(ibis_expression) print(ibis_result) diff --git a/examples/pipeline_mlp_regressor.py b/examples/pipeline_mlp_regressor.py index 07fdfb6..2730bc0 100644 --- a/examples/pipeline_mlp_regressor.py +++ b/examples/pipeline_mlp_regressor.py @@ -70,10 +70,18 @@ con.create_table("DATA_TABLE", obj=example_data) -def main(): +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + +def main(): if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) print(f"\nGenerated Query for {BACKEND.upper()}:") @@ -82,8 +90,6 @@ def main(): print(con.raw_sql(sql).fetchall()) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_predictions = con.execute(ibis_expression) print(ibis_predictions) diff --git a/examples/pipeline_randforest_classifier.py b/examples/pipeline_randforest_classifier.py index 097fbf2..3b55806 100644 --- a/examples/pipeline_randforest_classifier.py +++ b/examples/pipeline_randforest_classifier.py @@ -68,11 +68,19 @@ con.create_table("DATA_TABLE", obj=example_data) -def main(): - print("orbital Features:", features) - +def translate_to_orbital(): orbital_pipeline = orbital.parse_pipeline(pipeline, features=features) print(orbital_pipeline) + ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() + + +def main(): + print("orbital Features:", features) if PRINT_SQL: sql = orbital.export_sql("DATA_TABLE", orbital_pipeline, dialect=BACKEND) @@ -82,8 +90,6 @@ def main(): print(con.raw_sql(sql).fetchall()) print("\nPrediction with Ibis") - ibis_table = ibis.memtable(example_data).alias("DATA_TABLE") - ibis_expression = orbital.translate(ibis_table, orbital_pipeline) ibis_target = con.execute(ibis_expression) print(ibis_target) diff --git a/examples/pytorch_demand_regressor.py b/examples/pytorch_demand_regressor.py index 299ef24..b552cc2 100644 --- a/examples/pytorch_demand_regressor.py +++ b/examples/pytorch_demand_regressor.py @@ -14,7 +14,7 @@ import os -import duckdb +import ibis import numpy as np import pandas as pd import torch @@ -82,20 +82,33 @@ "prior_week_sales": [400.0, 80.0, 250.0, 300.0], } ) -duckdb.register("weekly_sales", test_data) +con = ibis.duckdb.connect() +if PRINT_SQL: + con.create_table("weekly_sales", obj=test_data) -def main(): - pipeline = orbital.parse_pytorch_model(model, FEATURES) +def translate_to_orbital(): + orbital_pipeline = orbital.parse_pytorch_model(model, FEATURES) + print(orbital_pipeline) + ibis_table = ibis.memtable(test_data).alias("weekly_sales") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() - sql = orbital.export_sql("weekly_sales", pipeline, dialect="duckdb") + +def main(): if PRINT_SQL: + sql = orbital.export_sql("weekly_sales", orbital_pipeline, dialect="duckdb") print("\nGenerated Query for DuckDB:") print(sql) + print("\nPrediction with SQL") + print(con.raw_sql(sql).fetchall()) - sql_predictions = duckdb.sql(sql).df().iloc[:, 0].to_numpy() - print("\nPrediction with SQL") - print(sql_predictions) + print("\nPrediction with Ibis") + ibis_predictions = con.execute(ibis_expression).iloc[:, 0].to_numpy() + print(ibis_predictions) if PREDICT_WITH_LIBRARY: print("\nPrediction with PyTorch") @@ -108,10 +121,10 @@ def main(): print(torch_predictions) if ASSERT: - assert np.allclose(sql_predictions, torch_predictions, atol=1e-4), ( - "SQL and PyTorch predictions do not match" + assert np.allclose(ibis_predictions, torch_predictions, atol=1e-4), ( + "Ibis and PyTorch predictions do not match" ) - print("\nSQL and PyTorch predictions match.") + print("\nIbis and PyTorch predictions match.") if __name__ == "__main__": diff --git a/examples/pytorch_fraud_detector.py b/examples/pytorch_fraud_detector.py index b0c53b3..01de3df 100644 --- a/examples/pytorch_fraud_detector.py +++ b/examples/pytorch_fraud_detector.py @@ -9,7 +9,7 @@ import os -import duckdb +import ibis import numpy as np import pandas as pd import torch @@ -75,20 +75,33 @@ "v2": [0.5, 2.0, -1.0, 3.0], } ) -duckdb.register("transactions", test_data) +con = ibis.duckdb.connect() +if PRINT_SQL: + con.create_table("transactions", obj=test_data) -def main(): - pipeline = orbital.parse_pytorch_model(model, FEATURES) +def translate_to_orbital(): + orbital_pipeline = orbital.parse_pytorch_model(model, FEATURES) + print(orbital_pipeline) + ibis_table = ibis.memtable(test_data).alias("transactions") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() - sql = orbital.export_sql("transactions", pipeline, dialect="duckdb") + +def main(): if PRINT_SQL: + sql = orbital.export_sql("transactions", orbital_pipeline, dialect="duckdb") print("\nGenerated Query for DuckDB:") print(sql) + print("\nPrediction with SQL") + print(con.raw_sql(sql).fetchall()) - sql_predictions = duckdb.sql(sql).df().iloc[:, 0].to_numpy() - print("\nPrediction with SQL") - print(sql_predictions) + print("\nPrediction with Ibis") + ibis_predictions = con.execute(ibis_expression).iloc[:, 0].to_numpy() + print(ibis_predictions) if PREDICT_WITH_LIBRARY: print("\nPrediction with PyTorch") @@ -101,10 +114,10 @@ def main(): print(torch_predictions) if ASSERT: - assert np.allclose(sql_predictions, torch_predictions, atol=1e-5), ( - "SQL and PyTorch predictions do not match" + assert np.allclose(ibis_predictions, torch_predictions, atol=1e-5), ( + "Ibis and PyTorch predictions do not match" ) - print("\nSQL and PyTorch predictions match.") + print("\nIbis and PyTorch predictions match.") if __name__ == "__main__": diff --git a/examples/pytorch_maintenance_classifier.py b/examples/pytorch_maintenance_classifier.py index 64b3c12..494b9d1 100644 --- a/examples/pytorch_maintenance_classifier.py +++ b/examples/pytorch_maintenance_classifier.py @@ -16,7 +16,7 @@ import os -import duckdb +import ibis import numpy as np import pandas as pd import torch @@ -108,22 +108,35 @@ "age": [2.0, 3.0, 8.5, 4.0], } ) -duckdb.register("sensor_readings", test_data) +con = ibis.duckdb.connect() +if PRINT_SQL: + con.create_table("sensor_readings", obj=test_data) -def main(): - pipeline = orbital.parse_pytorch_model(model, FEATURES) +def translate_to_orbital(): + orbital_pipeline = orbital.parse_pytorch_model(model, FEATURES) + print(orbital_pipeline) + ibis_table = ibis.memtable(test_data).alias("sensor_readings") + ibis_expression = orbital.translate(ibis_table, orbital_pipeline) + return orbital_pipeline, ibis_expression + + +orbital_pipeline, ibis_expression = translate_to_orbital() - sql = orbital.export_sql("sensor_readings", pipeline, dialect="duckdb") + +def main(): if PRINT_SQL: + sql = orbital.export_sql("sensor_readings", orbital_pipeline, dialect="duckdb") print("\nGenerated Query for DuckDB:") print(sql) + print("\nPrediction with SQL") + print(con.raw_sql(sql).fetchall()) # The model has no ZipMap step (that is an sklearn-classifier concept), # so the three Softmax outputs surface as plain columns "softmax.out_0..2". - sql_predictions = duckdb.sql(sql).df().to_numpy() - print("\nPrediction with SQL") - print(sql_predictions) + print("\nPrediction with Ibis") + ibis_predictions = con.execute(ibis_expression).to_numpy() + print(ibis_predictions) if PREDICT_WITH_LIBRARY: print("\nPrediction with PyTorch") @@ -138,10 +151,10 @@ def main(): print(torch_predictions) if ASSERT: - assert np.allclose(sql_predictions, torch_predictions, atol=1e-5), ( - "SQL and PyTorch predictions do not match" + assert np.allclose(ibis_predictions, torch_predictions, atol=1e-5), ( + "Ibis and PyTorch predictions do not match" ) - print("\nSQL and PyTorch predictions match.") + print("\nIbis and PyTorch predictions match.") if __name__ == "__main__": From f73ac307625b8d69a9df4dd55c3f34134b0aeca2 Mon Sep 17 00:00:00 2001 From: Alessandro Molina Date: Fri, 14 Aug 2026 22:37:49 +0200 Subject: [PATCH 4/4] show unit in seconds --- .github/workflows/benchmarks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 66a8314..06c7353 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -35,6 +35,7 @@ jobs: uv run pytest benchmarks/ \ --benchmark-only \ --benchmark-json pytest-benchmark-sample.json \ + --benchmark-time-unit=s \ --no-cov - name: Upload benchmark artifact