Download src/analytics/benchmark.py from Asmitha-28/subscription-intelligence-pipeline: direct link, hf CLI and curl.
- Browser
- Download file 6 kB
-
https://huggingface.co/spaces/Asmitha-28/subscription-intelligence-pipeline/resolve/main/src/analytics/benchmark.py
- Command line
-
hf download hf://spaces/Asmitha-28/subscription-intelligence-pipeline/src/analytics/benchmark.py
-
curl -L -o benchmark.py https://huggingface.co/spaces/Asmitha-28/subscription-intelligence-pipeline/resolve/main/src/analytics/benchmark.py
6 kB
| """ | |
| benchmark.py - Pandas vs PySpark Performance Comparison | |
| ---------------------------------------------------------- | |
| Runs MRR aggregation on 50K / 100K / 200K records | |
| using both Pandas and PySpark and records wall-clock time. | |
| KEY INSIGHT: This script documents when Spark is worth its overhead, | |
| not just how to run Spark code. | |
| At small local scales Pandas often wins because it avoids JVM and | |
| scheduler overhead. Spark is mainly justified when distributed execution, | |
| fault tolerance, partitioned processing, and larger-than-memory workloads | |
| matter. | |
| Run: | |
| python src/analytics/benchmark.py | |
| """ | |
| import time | |
| import json | |
| import pandas as pd | |
| import os | |
| import sys | |
| import tempfile | |
| from typing import Callable, Tuple, Any | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| try: | |
| from pyspark.sql import functions as F, SparkSession | |
| except ImportError: | |
| F = None | |
| SparkSession = None | |
| from utils.spark_session import get_spark | |
| from utils.paths import data_path | |
| def pandas_mrr(csv_path: str) -> float: | |
| """Compute MRR with Pandas.""" | |
| df = pd.read_csv(csv_path, parse_dates=["event_date"]) | |
| df = df[df["event_type"] == "invoice_paid"] | |
| df["month"] = df["event_date"].dt.to_period("M") | |
| result = df.groupby(["tenant_id", "month"])["amount"].sum().reset_index() | |
| return result["amount"].sum() | |
| def spark_mrr(parquet_path: str, spark: "SparkSession") -> float: | |
| """Compute MRR with PySpark.""" | |
| df = spark.read.parquet(parquet_path) | |
| result = ( | |
| df.filter(F.col("event_type") == "invoice_paid") | |
| .groupBy("tenant_id", "event_year", "event_month") | |
| .agg(F.sum("amount").alias("mrr")) | |
| ) | |
| return result.agg(F.sum("mrr")).collect()[0][0] | |
| def time_it(fn: Callable, *args: Any, **kwargs: Any) -> Tuple[float, Any]: | |
| start = time.perf_counter() | |
| result = fn(*args, **kwargs) | |
| elapsed = round(time.perf_counter() - start, 3) | |
| return elapsed, result | |
| def run() -> None: | |
| print("\n[BENCHMARK] Pandas vs PySpark MRR Aggregation\n") | |
| spark = get_spark("Benchmark") | |
| if spark is None: | |
| print(" [WARN] Spark is unavailable or disabled for this local run. Running Pandas-only benchmark.\n") | |
| print(f" {'Records':<12} {'Pandas (s)':<14} {'PySpark (s)':<14} {'Winner'}") | |
| print(" " + "-" * 55) | |
| else: | |
| print(f" {'Records':<12} {'Pandas (s)':<14} {'PySpark (s)':<14} {'Winner'}") | |
| print(" " + "-" * 55) | |
| results = [] | |
| temp_dir = tempfile.gettempdir() | |
| for n in [50_000, 100_000, 200_000]: | |
| # Sample from full data | |
| csv_path = data_path("raw", "billing_events.csv") | |
| if not os.path.exists(csv_path): | |
| print(f" [ERROR] Raw billing events CSV not found at {csv_path}. Please run data generator first.") | |
| return | |
| full_df = pd.read_csv(csv_path) | |
| sample = full_df.sample(n=min(n, len(full_df)), random_state=42) | |
| sample_path = os.path.join(temp_dir, f"events_{n}.csv") | |
| sample.to_csv(sample_path, index=False) | |
| # Pandas timing | |
| t_pandas, _ = time_it(pandas_mrr, sample_path) | |
| spark_error = None | |
| if spark is None: | |
| t_spark = "N/A" | |
| winner = "Pandas (local Spark unavailable)" | |
| print(f" {n:<12,} {t_pandas:<14} {t_spark:<14} {winner}") | |
| else: | |
| # PySpark timing (re-use existing parquet, or write sample) | |
| try: | |
| sample_spark = spark.createDataFrame(sample) | |
| sample_spark = ( | |
| sample_spark | |
| .withColumn("event_date", F.to_timestamp("event_date", "yyyy-MM-dd HH:mm:ss")) | |
| .withColumn("event_year", F.year("event_date")) | |
| .withColumn("event_month", F.month("event_date")) | |
| ) | |
| pq_path = os.path.join(temp_dir, f"events_spark_{n}") | |
| sample_spark.write.mode("overwrite").parquet(pq_path) | |
| t_spark, _ = time_it(spark_mrr, pq_path, spark) | |
| winner = "Pandas" if t_pandas < t_spark else "PySpark" | |
| print(f" {n:<12,} {t_pandas:<14} {t_spark:<14} {winner}") | |
| except Exception as exc: | |
| t_spark = "Error" | |
| winner = "Pandas (Spark write error)" | |
| spark_error = str(exc).splitlines()[0][:160] | |
| print(f" {n:<12,} {t_pandas:<14} {t_spark:<14} {winner}") | |
| print(f" [WARN] Spark benchmark failed: {spark_error}") | |
| row = { | |
| "records": n, | |
| "pandas_seconds": t_pandas, | |
| "spark_seconds": t_spark, | |
| "winner": winner | |
| } | |
| if spark is not None and t_spark == "Error": | |
| row["spark_error"] = spark_error | |
| results.append(row) | |
| # Cleanup temp files | |
| try: | |
| if os.path.exists(sample_path): | |
| os.remove(sample_path) | |
| except Exception: | |
| pass | |
| # Save results for README / notebook | |
| os.makedirs("docs", exist_ok=True) | |
| with open("docs/benchmark_results.json", "w") as f: | |
| json.dump(results, f, indent=2) | |
| print("\n [OK] Results saved -> docs/benchmark_results.json") | |
| if spark is not None: | |
| print(""" | |
| KEY INSIGHT: | |
| Spark has real fixed costs: JVM startup, task scheduling, | |
| partition management, and file commit coordination. If Spark loses | |
| on small local data, that is expected. It becomes the right choice | |
| when distributed scale and operational guarantees justify the overhead. | |
| """) | |
| else: | |
| print(""" | |
| KEY INSIGHT: | |
| Pandas was executed locally because Spark is unavailable or disabled | |
| for this environment. On Windows, local Spark writes also need proper | |
| Hadoop native binaries. Use Linux, WSL, Docker, or a managed Spark | |
| cluster for a cluster-backed Spark benchmark. | |
| """) | |
| if spark is not None: | |
| spark.stop() | |
| print("[OK] Benchmark complete.\n") | |
| if __name__ == "__main__": | |
| run() | |