DEV Community

Cover image for 🪶Effortless Data Analysis - One JS VS Six Python Libraries
Code & Stats with Olivér
Code & Stats with Olivér

Posted on Edited on

🪶Effortless Data Analysis - One JS VS Six Python Libraries

In this blog post, I present my own TypeScript-based statistical library, TypeStats. It's a comprehensive statistical solution covering basic univariate and bivariate calculations, estimation, hypothesis testing, trend analysis, and regression.

TypeStats

Note: TypeStats achieves the theoretical accuracy limits of 64-bit IEEE 754 floating-point arithmetic across all NIST StRD ANOVA benchmark difficulty levels (lower, average, and higher). The results demonstrate numerical stability under severe cancellation and extreme scale shifts, with benchmark data ranging from 10⁰ to 10⁹. This validation marks a major milestone as TypeStats transitions from a promising side project into a dependable statistical library. It is designed for small- to medium-sized datasets in business intelligence, higher education, and scientific applications where standard 64-bit floating-point precision is sufficient. 1.0 release is coming soon! 🚀

Note: TypeStats is currently in beta. While the core methods have been thoroughly tested and function as intended, I cannot yet guarantee completely flawless behavior or absolute precision in all potential edge cases.

Prerequisites

Before installing TypeStats, make sure you have the following installed on your system:

  • Node.js (v18 or later recommended) - TypeStats relies on…

Note: TypeStats is currently in beta. While the core methods have been thoroughly tested and function as intended, I cannot yet guarantee completely flawless behavior or absolute precision in all potential edge cases. 🛠️

The Reason I'm Working on This Project

I'm primarily a programmer rather than a data analyst, but I've always been interested in math and statistics. Like many developers, I use Python statistical libraries. In my opinion, they are great, reliable, and highly optimized tools. However, using them can be inconvenient for two main reasons:

  1. You have to install at least four independent libraries for a moderately complex analysis.
  2. The APIs (interfaces) of these tools are often not straightforward or user-friendly.

My goal was to create a standalone statistical codebase that is easy to use and comprehensive enough to handle end-to-end tasks without relying on third-party libraries.
(This doesn't mean you don't have to install third-party libraries for database management or reading XLSX files.)

Besides, JavaScript is one of the most popular languages in the world and runs natively both in the browser and on the server. This is a key advantage over Python, which cannot run natively on the client side.

Getting Started & Installation

Setting up TypeStats in your project is straightforward. Ensure you have Node.js installed, which automatically includes npm (Node Package Manager).

  1. Install Node.js: Download and install the latest LTS version from nodejs.org.
  2. Install TypeStats: Run the following command in your terminal within your project directory:
npm i typestats
Enter fullscreen mode Exit fullscreen mode

Here's a full statistical pipeline using TypeStats that demonstrates the power and simplicity of the library.

At the end of this article, I attached a functional Python implementation for these calculations.
You can find the CSV dataset I used for this demonstration here.

import { getTableFromCSVP } from "typestats/io";
import { round } from "typestats/utils";
import { performance } from "perf_hooks";

async function pipeLine() {
    const t0 = performance.now();

    console.log("=== 1. LOADING & INITIAL SUMMARY ===");
    let table = await getTableFromCSVP("stat_dataset.csv", ",");
    table.describe();

    console.log("\n=== 1.5 DATA ORDERING ===");
    table = table.orderByAsc("date");

    console.log("\n=== 2. CATEGORICAL DATA CLEANING ===");
    table = table
        .fillNa("device", "Unknown")
        .fillNa("payment_method", "Unknown");

    table.describe();

    console.log("\n=== 3. OUTLIER ANALYSIS & SINGLE-PASS TS CLEANING ===");
    let outliersAdSpend = table.countOutliersIqr("ad_spend");
    let outliersRevenue = table.countOutliersIqr("revenue");
    console.log(`Initial outliers -> Ad Spend: ${outliersAdSpend}, Revenue: ${outliersRevenue}`);

    // Single-pass cleaning for missing values (NaN) and outliers using time-series linear interpolation
    table = table
        .replaceTSOutliersIqr("ad_spend", "interpolation")
        .replaceTSOutliersIqr("revenue", "interpolation");

    outliersAdSpend = table.countOutliersIqr("ad_spend");
    outliersRevenue = table.countOutliersIqr("revenue");
    console.log(`Outliers after IQR treatment -> Ad Spend: ${outliersAdSpend}, Revenue: ${outliersRevenue}`);

    console.log("\n=== 4. CORRELATION ANALYSIS ===");
    const correlation = table.correlation(["ad_spend", "revenue"], true);
    console.log("Pearson Correlation Matrix:");
    console.table(correlation);

    console.log("\n=== 5. ANOVA (Categorical vs. Numeric) ===");
    const anovaTable = table.toAnovaTable("payment_method", "revenue");
    anovaTable.printTable();

    const alpha = 0.05;
    const anovaResult = anovaTable.oneWayAnova(alpha);
    const etaSquared = anovaTable.etaSquared();

    console.log("\n--- One-Way ANOVA Hypothesis Test Results ---");
    console.log(`F-Statistic: ${round(anovaResult.F, 4)}`);
    console.log(`Mean Squares: MS Between = ${round(anovaResult.msBetween, 4)}, MS Within = ${round(anovaResult.msWithin, 4)}`);
    console.log(`Critical Upper Bound (alpha = ${alpha}): ${round(anovaResult.criticalBounds.upper, 4)}`);
    console.log(`Hypothesis Result (H0: equal group means): ${anovaResult.passed ? "FAILED TO REJECT H0 (No significant difference)" : "REJECT H0 (Statistically significant difference)"}`);
    console.log(`Strength of association between payment method and revenue (Eta Squared): ${round(etaSquared, 4)}`);

    console.log("\n=== 6. CRAMÉR'S V (Categorical vs. Categorical) ===");
    const contingencyTable = table.toContingencyTable("device", "payment_method");
    contingencyTable.printContingencyTable();

    const chi2Result = contingencyTable.chiSquaredIndependenceTest(alpha);
    const cramerVVal = contingencyTable.cramerV();

    console.log("\n--- Chi-Squared Test of Independence Results ---");
    console.log(`Chi-Square Statistic (chi2): ${round(chi2Result.chi2, 4)}`);
    console.log(`Critical Upper Bound (alpha = ${alpha}): ${round(chi2Result.criticalBounds.upper, 4)}`);
    console.log(`Hypothesis Result (H0: variables are independent): ${chi2Result.passed ? "FAILED TO REJECT H0 (Variables are independent)" : "REJECT H0 (Statistically significant association)"}`);
    console.log(`Cramér's V (device vs. payment method): ${round(cramerVVal, 4)}`);

    console.log("\n=== 7. TIME-SERIES TREND ANALYSIS (Revenue) ===");
    const revenueCol = table.getCol("revenue");

    const linTrend = revenueCol.linearTrend();
    const expTrend = revenueCol.exponentialTrend();
    const logTrend = revenueCol.logarithmicTrend();

    console.table({
        Linear: { intercept_a: round(linTrend.a, 4), slope_b: round(linTrend.b, 4), mse: round(linTrend.mse, 4) },
        Exponential: { intercept_a: round(expTrend.a, 4), slope_b: round(expTrend.b, 4), mse: round(expTrend.mse, 4) },
        Logarithmic: { intercept_a: round(logTrend.a, 4), slope_b: round(logTrend.b, 4), mse: round(logTrend.mse, 4) }
    });

    console.log("\n=== 8. BIVARIATE REGRESSION MODELS (Ad Spend -> Revenue) ===");
    const adSpendCol = table.getCol("ad_spend");

    const linReg = adSpendCol.linearRegression(revenueCol);
    const expReg = adSpendCol.exponentialRegression(revenueCol);
    const powReg = adSpendCol.powerRegression(revenueCol);

    console.table({
        Linear: {
            b0_intercept: round(linReg.b0, 4),
            b1_slope: round(linReg.b1, 4),
            rsd: round(linReg.rsd, 4)
        },
        Exponential: {
            b0_intercept: round(expReg.b0, 4),
            b1_slope: round(expReg.b1, 4),
            rsd: round(expReg.rsd, 4)
        },
        Power: {
            b0_intercept: round(powReg.b0, 4),
            b1_slope: round(powReg.b1, 4),
            rsd: round(powReg.rsd, 4)
        }
    });

    const t1 = performance.now();
    console.log(`\n🏁 Total pipeline execution time: ${round((t1 - t0) / 1000, 3)} seconds.`);
}

pipeLine();
Enter fullscreen mode Exit fullscreen mode

Let's take a closer look at the individual building blocks!

Reading Data from CSV

The library includes a parallelized function (utilizing worker threads) to read CSV files: getTableFromCSVP(). It returns a Table instance, which serves as the core API for data preparation, outlier filtering, and statistical analysis through its methods.

The 'P' suffix at the end of the function name indicates parallel execution. Additionally, the describe() method provides a quick statistical summary of the dataset.

Note: Parallelized methods are designed for server-side environments and will not work on the client side!
TypeStats allows you to ingest data from five distinct sources:

  • CSV files (both client-side and server-side)
  • JSON files (both client-side and server-side)
  • NDJSON files (both client-side and server-side)
  • XLSX spreadsheets
  • Databases (supported engines: MySQL, MS SQL, and PostgreSQL)
let table = await getTableFromCSVP("stat_dataset.csv", ",");
table.describe();
Enter fullscreen mode Exit fullscreen mode

Categorical Data Cleaning

Missing values in categorical variables can be filled easily with fallback labels using fillNa():

table = table
.fillNa("device", "Unknown")
.fillNa("payment_method", "Unknown");
Enter fullscreen mode Exit fullscreen mode

Time-Series Outlier & Missing Data Treatment

TypeStats includes both simple imputation methods (fillNaNumeric) and specialized time series cleaning tools. The latter are more suitable for data where order matters. Of course, you can get information about the number of outliers, utilizing, for instance, the countOutliersIqr method.

Using replaceTSOutliersIqr(), the dataset undergoes a single-pass treatment: it identifies outliers via the Interquartile Range (IQR) and replaces both anomalous and missing (NaN) values using linear interpolation along ordered time-series data.

let outliersAdSpend = table.countOutliersIqr("ad_spend");
let outliersRevenue = table.countOutliersIqr("revenue");
console.log(`Initial outliers -> Ad Spend: ${outliersAdSpend}, Revenue: ${outliersRevenue}`);

table = table
  .replaceTSOutliersIqr("ad_spend", "interpolation")
  .replaceTSOutliersIqr("revenue", "interpolation");
Enter fullscreen mode Exit fullscreen mode

Correlation Analysis

Here, we compute the Pearson correlation between ad spend and revenue. The method outputs a correlation matrix illustrating the direction and linear strength of the relationship.

  • A value of 1 or -1 indicates a perfect (deterministic) positive or negative linear relationship, respectively.
  • Values close to 1 or -1 represent strong positive or negative linear relationships.
  • Values around 0 suggest little to no linear relationship between the variables.
console.log("\n=== 4. CORRELATION ANALYSIS ===");
const correlation = table.correlation(["ad_spend", "revenue"], true);
console.log("Pearson Correlation Matrix:");
console.table(correlation);
Enter fullscreen mode Exit fullscreen mode

The second parameter of the correlation method tells the table instance to pretty print the correlation matrix.

Measuring Associations & Hypothesis Testing

For association testing, TypeStats provides specialized data structures to measure specific types of relationships along with formal hypothesis tests:

  • toAnovaTable() generates an ANOVA data structure designed to analyze relationships between a categorical variable and a numeric variable. Beyond calculating effect sizes like Eta Squared, it performs a full One-Way ANOVA test (oneWayAnova()) evaluating critical upper bounds and F-statistics.
console.log("\n=== 5. ANOVA & EFFECT SIZE (Categorical vs. Numeric) ===");
const anovaTable = table.toAnovaTable("payment_method", "revenue");
    anovaTable.printTable();

const alpha = 0.05;
const anovaResult = anovaTable.oneWayAnova(alpha);
const etaSquared = anovaTable.etaSquared();

console.log("\n--- One-Way ANOVA Hypothesis Test Results ---");
console.log(`F-Statistic: ${round(anovaResult.F, 4)}`);
console.log(`Mean Squares: MS Between = ${round(anovaResult.msBetween, 4)}, MS Within = ${round(anovaResult.msWithin, 4)}`);
console.log(`Critical Upper Bound (alpha = ${alpha}): ${round(anovaResult.criticalBounds.upper, 4)}`);
console.log(`Hypothesis Result (H0: equal group means): ${anovaResult.passed ? "FAILED TO REJECT H0 (No significant difference)" : "REJECT H0 (Statistically significant difference)"}`);
console.log(`Effect Size (Eta Squared): ${round(etaSquared, 4)}`);
Enter fullscreen mode Exit fullscreen mode

  • toContingencyTable() constructs a contingency matrix (DataMatrix) for two categorical variables. From this table, we can compute metrics like Cramér's V as well as run a formal Chi-Squared Test of Independence (chiSquaredIndependenceTest()).
console.log("\n=== 6. CHI-SQUARED TEST & CRAMÉR'S V (Categorical vs. Categorical) ===");
const contingencyTable = table.toContingencyTable("device", "payment_method");
contingencyTable.printContingencyTable();

const chi2Result = contingencyTable.chiSquaredIndependenceTest(alpha);
const cramerVVal = contingencyTable.cramerV();

console.log("\n--- Chi-Squared Test of Independence Results ---");
console.log(`Chi-Square Statistic (chi2): ${round(chi2Result.chi2, 4)}`);
console.log(`Critical Upper Bound (alpha = ${alpha}): ${round(chi2Result.criticalBounds.upper, 4)}`);
console.log(`Hypothesis Result (H0: variables are independent): ${chi2Result.passed ? "FAILED TO REJECT H0 (Variables are independent)" : "REJECT H0 (Statistically significant association)"}`);
console.log(`Association Strength (Cramér's V): ${round(cramerVVal, 4)}`);
Enter fullscreen mode Exit fullscreen mode

(In this particular case, I should have dropped the unknown values before treatment, but this is a demonstration of the capabilities of the library, not a valid analysis.)

My main goal was to provide the easiest, most seamless way to calculate these effect sizes and determine the significance of the associations. As you can see, it requires only a couple of steps to get the results.

Time-Series Trend Analysis

We can also analyze how a metric evolves over time. TypeStats includes several built-in trend models, making it easy to fit and evaluate competing curves on the same series:

  • linearTrend() fits a standard linear trend line.
  • exponentialTrend() fits an exponential growth or decay curve.
  • logarithmicTrend() fits a logarithmic curve.
  • polynomialTrend() fits a polynomial curve. (It's not called at this time.)
console.log("\n=== 7. TIME-SERIES TREND ANALYSIS (Revenue) ===");
const revenueCol = table.getCol("revenue");

const linTrend = revenueCol.linearTrend();
const expTrend = revenueCol.exponentialTrend();
const logTrend = revenueCol.logarithmicTrend();

console.table({
        Linear: { intercept_a: round(linTrend.a, 4), slope_b: round(linTrend.b, 4), mse: round(linTrend.mse, 4) },
        Exponential: { intercept_a: round(expTrend.a, 4), slope_b: round(expTrend.b, 4), mse: round(expTrend.mse, 4) },
        Logarithmic: { intercept_a: round(logTrend.a, 4), slope_b: round(logTrend.b, 4), mse: round(logTrend.mse, 4) }
    });
Enter fullscreen mode Exit fullscreen mode

Each method outputs its fitted parameters along with the Mean Squared Error (MSE), allowing for direct model comparison. Because real-world metrics rarely follow a straight line, having multiple models available streamlines exploratory analysis.

Bivariate Regression Models

Moving beyond single-variable time-series, we can analyze bivariate relationships, such as predicting revenue from advertising spend.

The methods linearRegression(), exponentialRegression(), and powerRegression() model revenue as a function of ad_spend. Each applies a distinct mathematical function to help you identify whether a linear model or a non-linear curve better captures the underlying trend.

Each model returns fitted parameters and the Residual Standard Deviation (RSD), measuring prediction deviations. Instead of assuming a linear impact, you can easily compare multiple model fits side by side.

console.log("\n=== 8. BIVARIATE REGRESSION MODELS (Ad Spend -> Revenue) ===");
const adSpendCol = table.getCol("ad_spend");

const linReg = adSpendCol.linearRegression(revenueCol);
const expReg = adSpendCol.exponentialRegression(revenueCol);
const powReg = adSpendCol.powerRegression(revenueCol);

console.table({
    Linear: {
        b0_intercept: round(linReg.b0, 4),
        b1_slope: round(linReg.b1, 4),
        rsd: round(linReg.rsd, 4)
    },
    Exponential: {
        b0_intercept: round(expReg.b0, 4),
        b1_slope: round(expReg.b1, 4),
        rsd: round(expReg.rsd, 4)
    },
    Power: {
        b0_intercept: round(powReg.b0, 4),
        b1_slope: round(powReg.b1, 4),
        rsd: round(powReg.rsd, 4)
    }
});
Enter fullscreen mode Exit fullscreen mode

Python Reference Implementation & Benchmark Comparison

To verify correctness and benchmark performance, here is the equivalent full pipeline written in Python using standard data libraries (pandas, numpy, scipy, statsmodels, pingouin, scikit-learn):

import time
import numpy as np
import pandas as pd
import pingouin as pg
import statsmodels.api as sm
from scipy import stats
from scipy.stats.contingency import association
from sklearn.metrics import mean_squared_error

def pipeline():
    t0 = time.perf_counter()

    print("=== 1. LOADING & INITIAL SUMMARY ===")
    df = pd.read_csv("stat_dataset.csv", sep=",", parse_dates=["date"])
    print(df.describe(include="all"))

    print("\n=== 1.5 DATA ORDERING ===")
    df = df.sort_values("date", kind="stable", ignore_index=True)

    print("\n=== 2. CATEGORICAL DATA CLEANING ===")
    df = df.fillna({"device": "Unknown", "payment_method": "Unknown"})
    print(df.describe(include="all"))

    print("\n=== 3. OUTLIER ANALYSIS & SINGLE-PASS TS CLEANING ===")
    num_cols = ["ad_spend", "revenue"]

    q = df[num_cols].quantile([0.25, 0.75])
    iqr = q.loc[0.75] - q.loc[0.25]
    lower = q.loc[0.25] - 1.5 * iqr
    upper = q.loc[0.75] + 1.5 * iqr
    is_outlier = df[num_cols].lt(lower) | df[num_cols].gt(upper)
    outliers_init = is_outlier.sum()
    print(f"Initial outliers -> Ad Spend: {outliers_init['ad_spend']}, Revenue: {outliers_init['revenue']}")

    df[num_cols] = (
        df[num_cols]
        .mask(is_outlier)
        .interpolate(method="linear", limit_direction="both")
    )

    q2 = df[num_cols].quantile([0.25, 0.75])
    iqr2 = q2.loc[0.75] - q2.loc[0.25]
    outliers_after = (
        df[num_cols].lt(q2.loc[0.25] - 1.5 * iqr2) | df[num_cols].gt(q2.loc[0.75] + 1.5 * iqr2)
    ).sum()
    print(f"Outliers after IQR treatment -> Ad Spend: {outliers_after['ad_spend']}, Revenue: {outliers_after['revenue']}")

    print("\n=== 4. CORRELATION ANALYSIS ===")
    correlation = df[num_cols].corr(method="pearson")
    print("Pearson Correlation Matrix:")
    print(correlation.round(4))

    print("\n=== 5. ANOVA & EFFECT SIZE (Categorical vs. Numeric) ===")
    anova_table = pg.anova(data=df, dv="revenue", between="payment_method", detailed=True)
    print(anova_table.to_string(index=False))

    alpha = 0.05
    f_val = anova_table.loc[0, "F"]
    df_between = int(anova_table.loc[0, "DF"])
    df_within = int(anova_table.loc[1, "DF"])
    ms_between = anova_table.loc[0, "MS"]
    ms_within = anova_table.loc[1, "MS"]
    eta_squared = anova_table.loc[0, "np2"]

    f_critical = stats.f.ppf(1 - alpha, df_between, df_within)
    h0_passed = f_val <= f_critical

    print("\n--- One-Way ANOVA Hypothesis Test Results ---")
    print(f"F-Statistic: {round(f_val, 4)}")
    print(f"Mean Squares: MS Between = {round(ms_between, 4)}, MS Within = {round(ms_within, 4)}")
    print(f"Critical Upper Bound (alpha = {alpha}): {round(f_critical, 4)}")
    print(f"Hypothesis Result (H0: equal group means): {'FAILED TO REJECT H0 (No significant difference)' if h0_passed else 'REJECT H0 (Statistically significant difference)'}")
    print(f"Strength of association between payment method and revenue (Eta Squared): {round(eta_squared, 4)}")

    print("\n=== 6. CHI-SQUARED TEST & CRAMÉR'S V (Categorical vs. Categorical) ===")
    contingency_table = pd.crosstab(df["device"], df["payment_method"], margins=True, margins_name="Total")
    print(contingency_table)

    observed = pd.crosstab(df["device"], df["payment_method"]).values
    chi2_val, p_val, dof, expected = stats.chi2_contingency(observed)

    chi2_critical = stats.chi2.ppf(1 - alpha, dof)
    chi2_h0_passed = chi2_val <= chi2_critical

    cramer_v = association(observed, method="cramer")

    print("\n--- Chi-Squared Test of Independence Results ---")
    print(f"Chi-Square Statistic (chi2): {round(chi2_val, 4)}")
    print(f"Critical Upper Bound (alpha = {alpha}): {round(chi2_critical, 4)}")
    print(f"Hypothesis Result (H0: variables are independent): {'FAILED TO REJECT H0 (Variables are independent)' if chi2_h0_passed else 'REJECT H0 (Statistically significant association)'}")
    print(f"Cramér's V (device vs. payment method): {round(cramer_v, 4)}")

    print("\n=== 7. TIME-SERIES TREND ANALYSIS (Revenue) ===")
    revenue = df["revenue"].to_numpy()
    ad_spend = df["ad_spend"].to_numpy()
    ln_revenue = np.log(revenue)

    t_0based = np.arange(0, len(revenue))
    t_1based = np.arange(1, len(revenue) + 1)
    ln_t = np.log(t_1based)

    lin_trend = stats.linregress(t_0based, revenue)
    lin_pred = lin_trend.intercept + lin_trend.slope * t_0based
    lin_trend_mse = mean_squared_error(revenue, lin_pred)

    exp_trend = stats.linregress(t_0based, ln_revenue)
    exp_a = np.exp(exp_trend.intercept)
    exp_b_factor = np.exp(exp_trend.slope)
    exp_pred = exp_a * np.exp(exp_trend.slope * t_0based)
    exp_trend_mse = mean_squared_error(revenue, exp_pred)

    log_trend = stats.linregress(ln_t, revenue)
    log_pred = log_trend.intercept + log_trend.slope * ln_t
    log_trend_mse = mean_squared_error(revenue, log_pred)

    print(pd.DataFrame({
        "Linear": {"intercept_a": lin_trend.intercept, "slope_b": lin_trend.slope, "mse": lin_trend_mse},
        "Exponential": {"intercept_a": exp_a, "slope_b": exp_b_factor, "mse": exp_trend_mse},
        "Logarithmic": {"intercept_a": log_trend.intercept, "slope_b": log_trend.slope, "mse": log_trend_mse},
    }).T.round(4))

    print("\n=== 8. BIVARIATE REGRESSION MODELS (Ad Spend -> Revenue) ===")
    lin_reg = sm.OLS(revenue, sm.add_constant(ad_spend)).fit()
    lin_pred = lin_reg.predict(sm.add_constant(ad_spend))
    lin_rsd = np.sqrt(np.sum((revenue - lin_pred) ** 2) / lin_reg.df_resid)

    exp_reg = sm.OLS(ln_revenue, sm.add_constant(ad_spend)).fit()
    exp_b0 = np.exp(exp_reg.params[0])
    exp_b1_factor = np.exp(exp_reg.params[1])
    exp_pred = exp_b0 * np.exp(exp_reg.params[1] * ad_spend)
    exp_rsd = np.sqrt(np.sum((revenue - exp_pred) ** 2) / exp_reg.df_resid)

    pow_reg = sm.OLS(ln_revenue, sm.add_constant(np.log(ad_spend))).fit()
    pow_b0 = np.exp(pow_reg.params[0])
    pow_b1 = pow_reg.params[1]
    pow_pred = pow_b0 * (ad_spend ** pow_b1)
    pow_rsd = np.sqrt(np.sum((revenue - pow_pred) ** 2) / pow_reg.df_resid)

    print(pd.DataFrame({
        "Linear": {
            "b0_intercept": lin_reg.params[0],
            "b1_slope": lin_reg.params[1],
            "rsd": lin_rsd,
        },
        "Exponential": {
            "b0_intercept": exp_b0,
            "b1_slope": exp_b1_factor,
            "rsd": exp_rsd,
        },
        "Power": {
            "b0_intercept": pow_b0,
            "b1_slope": pow_b1,
            "rsd": pow_rsd,
        },
    }).T.round(4))

    t1 = time.perf_counter()
    print(f"\n🏁 Total pipeline execution time: {round(t1 - t0, 3)} seconds.")

if __name__ == "__main__":
    pipeline()
Enter fullscreen mode Exit fullscreen mode

Execution Time Performance

Running both full pipelines end-to-end yielded the following execution times:

  • TypeStats (Node.js):
    🏁 Total pipeline execution time: 1.397 seconds.

  • Python (Pandas / SciPy / Statsmodels / Pingouin):
    🏁 Total pipeline execution time: 1.143 seconds.

(Note: These figures represent single quick execution runs to provide a general benchmark baseline, and thus include standard runtime noise.)

Top comments (0)