{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "9c02da3c-5d89-40f7-bced-2d8e73d4b92b", "metadata": {}, "outputs": [], "source": [ "# Cell 1: Install saspy (if not already present)\n", "!pip install --quiet saspy\n", "\n", "# Cell 2: Import all our libraries\n", "import saspy\n", "import pandas as pd\n", "import numpy as np\n", "import time\n", "from IPython.display import display, clear_output\n", "\n", "# Connect to our SAS session\n", "sas = saspy.SASsession()\n", "print(\"SAS Session is ready!\")" ] }, { "cell_type": "code", "execution_count": null, "id": "f337e392-e404-4d0d-8ea9-2d077ba5c6b6", "metadata": {}, "outputs": [], "source": [ "# Cell 3: Create the \"baseline\" data using SAS code\n", "sas.submitLST(f\"\"\"\n", " DATA WORK.BASELINE;\n", " /* Create 50 normal transactions */\n", " DO time = 1 TO 50;\n", " /* Mean = $50, Std Dev = $15 */\n", " amount = RAND('NORMAL', 50, 15); \n", " OUTPUT;\n", " END;\n", " RUN;\n", "\"\"\", print_log=False)\n", "\n", "print(\"Baseline 'normal' data created in SAS.\")" ] }, { "cell_type": "code", "execution_count": null, "id": "3f7b0ded-1d8c-4daf-af68-8328860a418f", "metadata": {}, "outputs": [], "source": [ "# Cell 4: Calculate and save the control limits\n", "sas.submitLST(f\"\"\"\n", " /* Run the Shewhart procedure on our baseline data */\n", " PROC SHEWHART DATA=WORK.BASELINE;\n", " \n", " /* Create an Individual & Moving Range chart */\n", " IRCHART amount * time / \n", " NOCHART /* Don't show the chart yet */\n", " OUTLIMITS=WORK.CONTROL_LIMITS; /* Save the limits to a new dataset */\n", " RUN;\n", "\"\"\", print_log=False)\n", "\n", "print(\"Control limits (our 'model') have been calculated and saved.\")" ] }, { "cell_type": "code", "execution_count": null, "id": "209e1029-7a24-49ee-807a-8e5967a1d5df", "metadata": {}, "outputs": [], "source": [ "# Cell 5: The Live Simulation\n", "sas.submitLST(\"DATA WORK.LIVE_STREAM; SET WORK.BASELINE;\", print_log=False)\n", "\n", "# Simulate 20 new transactions\n", "for i in range(1, 21):\n", " # 1. GENERATE new data point in Python\n", " time_id = 50 + i\n", " if i == 15:\n", " # --- THE ANOMALY ---\n", " new_amount = 300\n", " print(f\"Time={time_id}: FRAUDULENT transaction injected: ${new_amount:.2f}\")\n", " else:\n", " # --- NORMAL ---\n", " new_amount = np.random.normal(50, 15)\n", " print(f\"Time={time_id}: New 'normal' transaction: ${new_amount:.2f}\")\n", "\n", " # 2. SEND the single new row to SAS\n", " new_row_df = pd.DataFrame({'time': [time_id], 'amount': [new_amount]})\n", " sas.df2sd(new_row_df, 'WORK.LIVE_STREAM', append=True) # Append the new row\n", "\n", " # 3. RE-RUN the chart\n", " # Tell SAS to use the 'WORK.LIVE_STREAM' data\n", " # but apply the 'WORK.CONTROL_LIMITS' we already built\n", " chart_html = sas.submitLST(f\"\"\"\n", " ODS GRAPHICS ON;\n", " PROC SHEWHART DATA=WORK.LIVE_STREAM LIMITS=WORK.CONTROL_LIMITS;\n", " IRCHART amount * time /\n", " TURNHLABELS /* Makes X-axis labels vertical */\n", " NOLIMITLEGEND; /* Hides the legend */\n", " RUN;\n", " ODS GRAPHICS OFF;\n", " \"\"\")\n", " \n", " # 4. \"ANIMATE\" the chart in Jupyter\n", " clear_output(wait=True) # Clear the previous chart\n", " display(chart_html) # Display the new chart\n", " time.sleep(1) # Pause for 1 second\n", "\n", "print(\"Streaming demo complete!\")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.6" } }, "nbformat": 4, "nbformat_minor": 5 }