Techtonique dot net, the Machine Learning web API, is back online (but more like a passion project for now)

[This article was first published on T. Moudiki's Webpage - R, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

https://www.techtonique.net contains tools for Exploratory Data Analysis (EDA), editors for R and Python code, tools for data visualization, no-code web interfaces for various data science tasks, a language-agnostic API for machine learning tasks (classification, regression, survival analysis, reserving, forecasting etc.).

I recently noticed a spike in sign-ups (not sure how they noticed it was up again, without official announcement…), and I thought it was a good opportunity to share that techtonique.net is back. More like a passion project, or part of a portfolio (for now). The API is available and evolving. You can use it for free (with rate limiting), but please note that it is slightly slower than it used to be (indeed, I used to have Azure Credits thanks to Microsoft for Startups => a faster server). It still serves the responses, in a reasonable time though, and I will try to optimize it as much as possible.

It is worth mentioning that I removed the stochastic simulation API for now, as it required (more technical) to run R in Python, through a Docker container.

Here are some examples of how to use the API for machine learning tasks in R and Python (there are also no-code interfaces for these tasks in the website, and the API is language-agnostic, so you can use it with any programming language that can make HTTP requests):

The starting point is to signup/login (if you’re facing issues, contact [email protected]) and get a token from: https://www.techtonique.net/token. Then, you can use the token in API requests for machine learning tasks. Each response is a prediction, as envisaged by the chosen model, and based on the input data.

1 – R example:

#!/usr/bin/env Rscript

# Install httr if needed: install.packages("httr")
# All you need is a token from www.techtonique.net/token, then run the script
library(httr)

BASE_URL   <- "https://www.techtonique.net"
GITHUB_RAW <- "https://raw.githubusercontent.com/Techtonique/datasets/main"

DATASETS <- list(
  univariate     = paste0(GITHUB_RAW, "/time_series/univariate/a10.csv"),
  multivariate   = paste0(GITHUB_RAW, "/time_series/multivariate/ice_cream_vs_heater.csv"),
  classification = paste0(GITHUB_RAW, "/tabular/classification/breast_cancer_dataset2.csv"),
  regression     = paste0(GITHUB_RAW, "/tabular/regression/boston_dataset2.csv"),
  raa            = paste0(GITHUB_RAW, "/tabular/triangle/raa.csv"),
  abc            = paste0(GITHUB_RAW, "/tabular/triangle/abc.csv"),
  km             = paste0(GITHUB_RAW, "/tabular/survival/kidney.csv"),
  ridge_survival = paste0(GITHUB_RAW, "/tabular/survival/gbsg2_2.csv")
)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

get_token <- function() {
  args <- commandArgs(trailingOnly = TRUE)
  idx  <- which(args == "--token")
  if (length(idx) > 0 && idx < length(args)) return(args[idx + 1])
  cat("Please enter your JWT token: ")
  readLines(con = "stdin", n = 1)
}

# Download a GitHub dataset to a temp file and return its path.
# Using a real file is the only reliable way to do multipart uploads in httr.
fetch_dataset <- function(key) {
  url      <- DATASETS[[key]]
  filename <- basename(url)
  cat(sprintf("  Fetching %s from GitHub... ", filename))
  tmp <- tempfile(fileext = ".csv")
  resp <- GET(url, write_disk(tmp, overwrite = TRUE))
  stop_for_status(resp)
  cat("OK\n")
  tmp   # return the temp file path
}

make_request <- function(endpoint, token, dataset_key = NULL, params = list()) {
  url     <- paste0(BASE_URL, endpoint)
  headers <- add_headers(Authorization = paste("Bearer", token))

  if (!is.null(dataset_key)) {
    tmp_path <- fetch_dataset(dataset_key)
    on.exit(unlink(tmp_path), add = TRUE)   # clean up temp file when done
    body <- list(file = upload_file(tmp_path, type = "text/csv"))
    resp <- POST(url, headers, query = params, body = body, encode = "multipart")
  } else {
    resp <- GET(url, headers, query = params)
  }

  if (http_error(resp)) {
    cat(sprintf("  ERROR %s: %s\n", status_code(resp),
                content(resp, as = "text", encoding = "UTF-8")))
    return(NULL)
  }
  content(resp, as = "parsed", type = "application/json")
}

print_response <- function(resp) {
  if (is.null(resp)) return(invisible(NULL))
  for (key in names(resp)) {
    val <- resp[[key]]
    if (is.list(val) || length(val) > 6) {
      cat(sprintf("  %s: [%s ...]\n", key, paste(head(unlist(val), 6), collapse = ", ")))
    } else {
      cat(sprintf("  %s: %s\n", key, paste(val, collapse = ", ")))
    }
  }
}

# ---------------------------------------------------------------------------
# Test sections
# ---------------------------------------------------------------------------

test_forecasting <- function(token) {
  cat("\n=== Testing Forecasting Endpoints ===\n")

  cat("\nTesting univariate forecasting...\n")
  resp <- make_request("/forecasting", token,
    dataset_key = "univariate",
    params = list(base_model = "RidgeCV", n_hidden_features = 5,
                  lags = 25, type_pi = "kde", replications = 4, h = 3))
  print_response(resp)

  cat("\nTesting multivariate forecasting...\n")
  resp <- make_request("/forecasting", token,
    dataset_key = "multivariate",
    params = list(base_model = "RidgeCV", n_hidden_features = 5, lags = 25, h = 3))
  print_response(resp)
}

test_ml <- function(token) {
  cat("\n=== Testing Machine Learning Endpoints ===\n")

  cat("\nTesting classification...\n")
  resp <- make_request("/mlclassification", token,
    dataset_key = "classification",
    params = list(base_model = "RandomForestClassifier",
                  n_hidden_features = 5, predict_proba = TRUE))
  print_response(resp)

  cat("\nTesting regression...\n")
  resp <- make_request("/mlregression", token,
    dataset_key = "regression",
    params = list(base_model = "RidgeCV", n_hidden_features = 5, return_pi = TRUE))
  print_response(resp)
}

test_reserving <- function(token) {
  cat("\n=== Testing Reserving Endpoints ===\n")

  cat("\nTesting RidgeCV...\n")
  resp <- make_request("/mlreserving", token,
    dataset_key = "raa",
    params = list(method = "RidgeCV"))
  print_response(resp)

  cat("\nTesting LassoCV...\n")
  resp <- make_request("/mlreserving", token,
    dataset_key = "abc",
    params = list(method = "LassoCV"))
  print_response(resp)
}

test_survival <- function(token) {
  cat("\n=== Testing Survival Analysis Endpoints ===\n")

  cat("\nTesting Kaplan-Meier...\n")
  resp <- make_request("/survivalcurve", token,
    dataset_key = "km",
    params = list(method = "km"))
  print_response(resp)

  cat("\nTesting Ridge survival...\n")
  resp <- make_request("/survivalcurve", token,
    dataset_key = "ridge_survival",
    params = list(method = "RidgeCV", patient_id = 0))
  print_response(resp)
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

main <- function() {
  token <- get_token()
  test_forecasting(token)
  test_ml(token)
  test_reserving(token)
  test_survival(token)
}

main()

2 - Python example:

import argparse
import requests
import os
import io
from typing import Dict, Any

# All you need is a token from www.techtonique.net/token, then run the script

# Base URL for API endpoints
base_url = "https://www.techtonique.net"

# Base URL for raw GitHub content
GITHUB_RAW = "https://raw.githubusercontent.com/Techtonique/datasets/main"

# Dataset paths within the GitHub repo
DATASETS = {
    "univariate":       f"{GITHUB_RAW}/time_series/univariate/a10.csv",
    "multivariate":     f"{GITHUB_RAW}/time_series/multivariate/ice_cream_vs_heater.csv",
    "classification":   f"{GITHUB_RAW}/tabular/classification/breast_cancer_dataset2.csv",
    "regression":       f"{GITHUB_RAW}/tabular/regression/boston_dataset2.csv",
    "raa":              f"{GITHUB_RAW}/tabular/triangle/raa.csv",
    "abc":              f"{GITHUB_RAW}/tabular/triangle/abc.csv",
    "km":               f"{GITHUB_RAW}/tabular/survival/kidney.csv",
    "ridge_survival":   f"{GITHUB_RAW}/tabular/survival/gbsg2_2.csv",
}


def get_token() -> str:
    """Get token from command line argument or prompt"""
    parser = argparse.ArgumentParser(description='Test API endpoints')
    parser.add_argument('--token', help='JWT token for authentication')
    args = parser.parse_args()

    if args.token:
        return args.token

    return input("Please enter your JWT token: ")


def fetch_dataset(dataset_key: str) -> tuple[io.BytesIO, str]:
    """
    Download a dataset from GitHub and return it as an in-memory bytes buffer
    together with a filename, ready to be passed to requests as a file upload.
    """
    url = DATASETS[dataset_key]
    filename = url.split("/")[-1]
    print(f"  Fetching {filename} from GitHub...", end=" ")
    response = requests.get(url)
    response.raise_for_status()
    print("OK")
    return io.BytesIO(response.content), filename


def make_request(
    url: str,
    token: str,
    method: str = "POST",
    file_tuple: tuple = None,   # (BytesIO, filename)
    params: Dict = None,
) -> Dict[str, Any]:
    """Make an API request and return the response."""
    headers = {"Authorization": f"Bearer {token}"}

    try:
        if method == "POST":
            files = None
            if file_tuple:
                buf, filename = file_tuple
                files = {"file": (filename, buf, "text/csv")}
            response = requests.post(url, headers=headers, files=files, params=params)
        else:
            response = requests.get(url, headers=headers, params=params)

        response.raise_for_status()
        return response.json()

    except requests.exceptions.RequestException as e:
        print(f"Error making request to {url}:")
        print(f"Status code: {e.response.status_code if hasattr(e, 'response') else 'N/A'}")
        print(f"Response: {e.response.text if hasattr(e, 'response') else str(e)}")
        return None


def test_forecasting(token: str):
    print("\n=== Testing Forecasting Endpoints ===")

    print("\nTesting univariate forecasting...")
    response = make_request(
        f"{base_url}/forecasting", token,
        file_tuple=fetch_dataset("univariate"),
        params={"base_model": "RidgeCV", "n_hidden_features": 5,
                "lags": 25, "type_pi": "kde", "replications": 4, "h": 3},
    )
    if response:
        print("Response:", response)

    print("\nTesting multivariate forecasting...")
    response = make_request(
        f"{base_url}/forecasting", token,
        file_tuple=fetch_dataset("multivariate"),
        params={"base_model": "RidgeCV", "n_hidden_features": 5, "lags": 25, "h": 3},
    )
    if response:
        print("Response:", response)


def test_ml(token: str):
    print("\n=== Testing Machine Learning Endpoints ===")

    print("\nTesting classification...")
    response = make_request(
        f"{base_url}/mlclassification", token,
        file_tuple=fetch_dataset("classification"),
        params={"base_model": "RandomForestClassifier",
                "n_hidden_features": 5, "predict_proba": True},
    )
    if response:
        print("Response:", response)

    print("\nTesting regression...")
    response = make_request(
        f"{base_url}/mlregression", token,
        file_tuple=fetch_dataset("regression"),
        params={"base_model": "RidgeCV", "n_hidden_features": 5, "return_pi": True},
    )
    if response:
        print("Response:", response)


def test_reserving(token: str):
    print("\n=== Testing Reserving Endpoints ===")

    print("\nTesting RidgeCV...")
    response = make_request(
        f"{base_url}/mlreserving", token,
        file_tuple=fetch_dataset("raa"),
        params={"method": "RidgeCV"},
    )
    if response:
        print("Response:", response)

    print("\nTesting LassoCV...")
    response = make_request(
        f"{base_url}/mlreserving", token,
        file_tuple=fetch_dataset("abc"),
        params={"method": "LassoCV"},
    )
    if response:
        print("Response:", response)


def test_survival(token: str):
    print("\n=== Testing Survival Analysis Endpoints ===")

    print("\nTesting Kaplan-Meier...")
    response = make_request(
        f"{base_url}/survivalcurve", token,
        file_tuple=fetch_dataset("km"),
        params={"method": "km"},
    )
    if response:
        print("Response:", response)

    print("\nTesting Ridge survival...")
    response = make_request(
        f"{base_url}/survivalcurve", token,
        file_tuple=fetch_dataset("ridge_survival"),
        params={"method": "RidgeCV", "patient_id": 0},
    )
    if response:
        print("Response:", response)


def main():
    token = get_token()
    test_forecasting(token)
    test_ml(token)
    test_reserving(token)
    test_survival(token)


if __name__ == "__main__":
    main()

image-title-here

To leave a comment for the author, please follow the link and comment on their blog: T. Moudiki's Webpage - R.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)