{ "cells": [ { "cell_type": "markdown", "id": "8a34a64d", "metadata": {}, "source": [ "# Shapash + FastAPI: real-time inference and explainability\n", "\n", "This tutorial shows how to:\n", "- train a tabular classification model\n", "- compile a SmartExplainer and switch to SmartPredictor\n", "- expose prediction and explainability through a FastAPI service" ] }, { "cell_type": "markdown", "id": "a55596e6", "metadata": {}, "source": [ "## 1. Imports and optional dependencies" ] }, { "cell_type": "code", "execution_count": null, "id": "5379db99", "metadata": {}, "outputs": [], "source": [ "# Uncomment if needed\n", "# !pip install fastapi uvicorn pydantic\n", "\n", "import pandas as pd\n", "from category_encoders import one_hot\n", "from fastapi import FastAPI\n", "from pydantic import BaseModel\n", "from sklearn.ensemble import RandomForestClassifier\n", "from sklearn.model_selection import train_test_split\n", "\n", "from shapash import SmartExplainer\n", "from shapash.data.data_loader import data_loading\n", "from shapash.utils.load_smartpredictor import load_smartpredictor" ] }, { "cell_type": "markdown", "id": "7f6c6aca", "metadata": {}, "source": [ "## 2. Train a model and compile Shapash" ] }, { "cell_type": "code", "execution_count": 2, "id": "499ca55a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO: Shap explainer type - \n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAA4QAAAH0CAYAAABl8+PTAAAgAElEQVR4XuydB7RdRdm/35CQQkxCCCWEgBQhNEFByieKKCAISBClRDA0gVAlgpSPLh0B6b0rHekgiEalSVPEhh8gUgQVCIFAMAGS/Ndv/O+7Tm7uvb+dnHP3nZPzzFosyD1z9sx+3snwPndmz+41c+bMmUGBAAQgAAEIQAACEIAABCAAgZYj0AshbLmYc8MQgAAEIAABCEAAAhCAAAQSAYSQgQABCEAAAhCAAAQgAAEIQKBFCSCELRp4bhsCEIAABCAAAQhAAAIQgABCyBiAAAQgAAEIQAACEIAABCDQogQQwhYNPLcNAQhAAAIQgAAEIAABCEAAIWQMQAACEIAABCAAAQhAAAIQaFECCGGLBp7bhgAEIAABCEAAAhCAAAQggBAyBiAAAQhAAAIQgAAEIAABCLQoAYSwRQPPbUMAAhCAAAQgAAEIQAACEEAIGQMQgAAEIAABCEAAAhCAAARalABC2KKB57YhAAEIQAACEIAABCAAAQgghIwBCEAAAhCAAAQgAAEIQAACLUoAIWzRwHPbEIAABCAAAQhAAAIQgAAEEELGAAQgAAEIQAACEIAABCAAgRYlgBC2aOC5bQhAAAIQgAAEIAABCEAAAgghYwACEIAABCAAAQhAAAIQgECLEkAIWzTw3DYEIAABCEAAAhCAAAQgAAGEkDEAAQhAAAIQgAAEIAABCECgRQkghC0aeG4bAhCAAAQgAAEIQAACEIAAQsgYgAAEIAABCEAAAhCAAAQg0KIEEMIWDTy3DQEIQAACEIAABCAAAQhAACFkDEAAAhCAAAQgAAEIQAACEGhRAghhiwae24YABCAAAQhAAAIQgAAEIIAQMgYgAAEIQAACEIAABCAAAQi0KAGEsEUDz21DAAIQgAAEIAABCEAAAhBACBkDEIAABCAAAQhAAAIQgAAEWpQAQtiigee2IQABCEAAAhCAAAQgAAEIIISMAQhAAAIQgAAEIAABCEAAAi1KACFs0cBz2xCAAAQgAAEIQAACEIAABBBCxgAEIAABCEAAAhCAAAQgAIEWJYAQtmjguW0IQAACEIAABCAAAQhAAAIIIWMAAhCAQAUE7rjjjpg6dWqplvr27RtbbbVVqbrdUWnGjBkx33zzdcelu/WaU6ZMibvvvju1sfbaa8fSSy/dre3ldPFmjVlODOkLBCAAgVYlgBC2auS5bwhAoFICG220UUyaNKlUmwsssEA8+OCDpeo2utLPf/7zOO+88+LWW29t9KW7/Xr/+Mc/YvTo0amd73//+7H55pt3e5s5NPDXv/41Tj755Dj88MNj+eWXz6FL9AECEIAABJqIAELYRMGiqxCAQPMSqBXCZZZZpssbGTx4cFx++eWV3+w999wTRx55ZGr3t7/9beXt19tgKwrhq6++GltuuWVCd/311yOE9Q4ivg8BCECgBQkghC0YdG4ZAhConkAhhF//+tfjf//3f6vvQIkWb7/99rSyhhCWgJVJlRdffDE0phDCTAJCNyAAAQg0IQGEsAmDRpchAIHmI4AQdn/MWnGFECHs/nFFCxCAAATmdQII4bweYe4PAhDIgkC9Qvjvf/87rrzyyvjTn/4Uf/nLX2KJJZaIlVdeOb72ta/FOuus0+E9Pvfcc/GTn/wknn/++fi///u/eP/992Po0KHxmc98JnbbbbdZthceeuihqd7f//73dK3Pf/7z6WCZE088MXSdyy67LP351FNPjT59+szS3u9///vUN31+2mmntR1Ic/rpp8crr7wSu+yySzz88MNxyy23xKBBg+LLX/5y7LDDDqGtsSpPP/103HzzzeneXn755dSvVVddNXbaaadYcsklS8evMyHUM3YXXnhhLL744rH//vvHj370o3j00UdTu6uvvnr8z//8T2pLh/k88MAD6WCaRx55JLW7xhprxL777jsLq+J6Cy+8cOy3335x8cUXpy224qS4KNbf+ta3EuuOym9+85u46aab4plnnonXX389XVuxHDt27GwH4egwogkTJsQGG2wQ/fv3j0svvTQ0Fr7yla9Ev3790jWeeuqp1IzuRUz32GOPdD2V6dOnp+dBn3jiiXj22WcT30UXXTSWW265dPDOmDFjYv7552/rprYN/+xnP0vx1z9i9bvf/S50zyuuuGLisfvuu7fFrvb+3nrrrfjlL3+Z2lK8VXRvW2+9dWy66aazjRsdsvTjH/84nnzyyfjzn/+c6n/qU5+KL3zhC+k7zXiwUenBSkUIQAACGRFACDMKBl2BAATmXQL1COGvfvWr9GyfhK6jIrkaP3589OrVq+1jCdo555zTJdAzzzwzJf0qa665Zod1leArYf/e976XPpfMSJxqiwTisMMOSz96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PPXUU522u+2228aBBx7YsBfKV36DPdCgXpx+6qmnxu233z5b61tssUUcdthh9uXrje7297///dQfvWj+1ltvbfTlO7zeDTfckDioPPLII0lGKRCAAAQg0PwEEMLmjyF3AAEIQKCNQCGEOgWzV69e6efTp0+PP/zhD3HffffFq6++GjvvvHPst99+UCtJ4JRTTokbb7yxrfYSSyyROBZFkn3IIYeUvFpjqiGEjeHIVSAAAQhAIAIhZBRAAAIQmIcIFEL4xBNPzLal7x//+EeMHj06JDR33HHHbHetlbDevXvH/PPP3yGRGTNmxJQpU2LQoEFdEvvggw9S23369Om0ntrS513V0Ze1/bV///6dbk/UdSS+Xa1Wufvq6mZ0z1/+8pdD20U/97nPxYknnpi23b7++uvx9a9/vW177uOPP57YzW1RH3UPhcS767z77rsxbdq0xG/BBRd01Tv8XGzVZlf9/uijj0L/KAasEM4VZr4EAQhAIHsCCGH2IaKDEIAABMoT6EoItVK4wQYbJImpFUa9aP30009Pz8epfPrTn04rXnpGTkWyctppp7VtTVxggQXSdbT1tJCRr3zlK6F/Xnzxxfj1r38dqrP++uunlcja5+zuueeeuPDCC9tW2FZfffX43ve+FyuttFJq6+abb06fH3vssekdf+qTrqWtmd/5znfatmZqy+IPfvCD0DOTKto6ue+++8aGG27YBsvdlyrqO3/961/jrrvu6nDbp5itvfba6ZricfLJJ8fSSy+d/vzggw/O0j9J00knnRS/+MUvYtVVV40zzzwz1Xvvvfdiq622Sv+te91kk03a7vMTn/hEuu61114bQ4cOjUUXXTTJ5pprrhlamSyKVigvvvjiJOs/+tGP4vrrr4/bbrstll122fTzXXfdNbFw31t44YXj5z//eXqPYrHKuc4668TBBx/cdl9q880330z3+stf/jJ1QXWWW2651E8VtoyW/ztJTQhAAAK5E0AIc48Q/YMABCAwBwQ6E0IdCHLFFVfE1VdfnVa6zjrrrDapOeCAA5KMaOujBE+CoRWxO++8M0aMGJHq6nvbbLNNfPKTn4ynn346fvKTn8RGG23UJi0SkULM9t5779ChJ5LMlVdeOS655JIkW1qVlOhJgLbffvu02ij5k6Bq9UlypHaKvn32s59NMvazn/0s9DL47373u7HDDjukw1S0aicJ3G677dKqmgRJQqR/6/qSNXdf6q+2z/7xj3/sUnD0jKD6UBS1+8UvfjH+53/+J9ZYY41ZVtgOPfTQuP/++9N9S9xUxF71VY488sgkh7X3WRve3XffPfFSkTwvtthi6b+/9rWvpftTm+eee2603zJ66aWXxgUXXGC/p2uqDx2Voj2tCCo+xS8IOqqLEM7BX0qqQgACEMicAEKYeYDoHgQgAIE5IVAIoQSvKNpaWJw8KqE7/vjjY+TIkbOIxt133922kvfMM8/EjjvuGFtuuWUcffTRafXpueeeSytLxdZMyZ6KJE1CVgihRLFYQdOhJxIXSeBmm20WX/jCF9L3JYZa9VPRVsu99torPv/5z6cVtUKUdtlll7R6p/L222+nlb+izmOPPRaSTq1QfvOb30x1/vSnP8V1110XW2+9depLIVBd3VdZrmpf9ykRbl8kh+ecc04bz7kRQonv2LFjkzhqZVGsVLQiqp9Lhr/1rW+ln2lV9Etf+tJsQvjvf//bfk8rtptuummSfa3M6oCY//znP4mhxkfxLKRWOLViqKJfAhSrqOPHj28bRwhh2dFDPQhAAAL5E0AI848RPYQABCBQmkAhhIVUaDVNAqWi7YrFipr+/M477yS50DZFyUdR9NycVpGKEywlPFdeeWV69lCrghIYCUXts4aSsPbPJmqrpCRQwiFBk1zstNNOsf/++7e19eGHH6Y+qGhVrxBCrZJp9a0okkFtK9XqZSGI+kwrb1rxXHfddduEtux9lYYaETNnzkyriJKlBx54IElVUSTfxZbTuRHCa665JlZcccW26x100EFpq2bBXxKolU9J9IQJExL3jg6Vcd/75z//meKgInFWLFUuu+yy+O1vf5teX6Etu2J80UUXpc+0LXXJJZdM/130Q/+NEM7J6KEuBCAAgbwJIIR5x4feQQACEJgjAh1tGS1W1CQUehZt8cUXT9d89tlnY8yYMV1eX6IgaTv//POTLBQrjbqWBFOriCoSQknG4YcfPsv19HMJm7Zmqm/Fts/aStraKRnUM38SH20Zvemmm9LzcUXR84kSTm2NVNE2T61wafWsKNpOqdVISXCZ+yoDVverZ+3eeuutJMHa+io5fP7551M/1WcVSZTutSMhlDwW8tXRllE9c/mxj32srTuSreIU2Msvvzyt0KkftaumHQmh+16xGtvVfSve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jAgghBkFg65AAAIQgAAEIAABCFgCCKFFRAUIlCeAEJZnRU0IQAACEIAABCAAgZ4ngBD2fAzowTxEACGch4LJrUAAAhCAAAQgAIEWIIAQtkCQucXqCCCE1bGmJQhAAAIQgAAEIACB+gkghPUz5AoQaCOAEDIYIAABCEAAAhCAAASaiQBC2EzRoq/ZE0AIsw8RHYQABCAAAQhAAAIQqCGAEDIcWp7AxIkTo2/fvjFo0KC6WSCEdSPkAhCAAAQgAAEIQAACFRJACCuETVP5EJgxY0ZccMEF8atf/Srefvvt1LH55psvtttuu9h1113nuqMI4Vyj44sQgAAEIAABCEAAAj1AACHsAeg02fMErr322rjiiiviyCOPjDXWWCNJ4cMPPxzPPPNMHHPMMXPdQYRwrtHxRQhAAAIQgAAEIACBHiCAEPYAdJrseQIHH3xwvPPOO3HRRRd12pnHH388zjrrrHjzzTeTNI4ZMyZWW221uP322+OWW26JU045JYYPHx4ffvhhHHjggfGpT30q5p9//lhlvnExauT7PX+T9AACEIAABCAAAQhAAAKGAELIEGlJAnfffXeceeaZ8aUvfSm+8IUvxKhRo2LYsGFtLF5++eXYbbfd0hbS9ddfP+699964//7744477oiPPvoovv3tb0fv3r3jkksuiR/+8Ifx2GOPxZVXXplEESFsySHFTUMAAhCAAAQgAIGmJIAQNmXY6HS9BCR1krurr746pkyZki637LLLppW+FVZYIc4999yYMGFCHH/88ekz1ddnZ599dqy00krxr3/9K3baaaf4xCc+Ec8//3xceOGFscwyywRbRuuNDN+HAAQgAAEIQAACEKiSAEJYJW3aypLA66+/Hn/84x/j4osvjj59+sQ111wThx56aDz11FNpS2ht2X333eNzn/tc+tHNN9+ctpxus802sccee6SfIYRZhphOQQACEIAABCAAAQh0QgAhZGi0JIH3338/FlhggVnu/bbbbovzzjsv7rnnnvTs4EsvvRTnnHNOh3z0fa0QTp8+PaZOnRpXXXVVLLLIIghhS44mbhoCEIAABCAAAQg0LwGEsHljR8/rIKAVvZVXXjm22GKLGDFiRDz33HNx6qmnpkNh9Czgk08+GYcddljsu+++sfnmm6cDaO67775Ya621Yvnll08riNo2qq2i48aNSyuLWi3U6aU8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xYjYeOON48QTT0yJe1flV7/6VfzhD3+I/fffv63aTjvtFCuvvHIccsghpdrKrdLll18ejz76aFx88cWVdk3MTznllFhjjTXqbveVV16JM844I0477bQk5ypV31f7cXDooYemsbLCCivEV7/61dhwww0Rwroj3ZgLIISN4diMVyH58lEj+fKMEELPCCEsx4g5yXNiTvKMEMIaIZTQXHrppTFt2rS4//7746abbkqCsPrqq1uSZYVQqzm/+MUv4sorr2y75gsvvBALLLBADB8+3LaTY4Wqxalg0Egh/Mtf/hLf+c534p577mlbqa36vmrHwZQpU2KrrbYK/ZJCQthVYYWw+r8VCGH1zHNpkeTLR4LkyzNCCD0jhLAcI+Ykz4k5yTNCCNsJoYSgVjj22muv2HrrrdOPXn311Tj55JPTFr6Pf/zj6eebbrpp+qxWCK+66qq4+eabQ1sAtQ10s802i3322SdefPHF9G9BX3TRRdP3LrvssjjrrLNiueWWi4022ih9rpWhT37yk+nz119/PcaPHx8nnHBCLL300vH444+n+tqiqZWxMWPGxGqrrZbakrw+8sgjMX369NS/ww47LJZZZpnZRoHqSDR0Da2EbrLJJrHnnnvGDTfcEFopO+igg9J3Xnvttfje974XF154YQwaNCj0F0rfe+ihh+KDDz6IUaNGpTbuuuuuJNArrbRSPPzww7H44ovHuHHjYt111+1wBHbGp1evXvHyyy+nVb/nn38+rdStuuqqcfzxx0ffvn1nu5aYf+lLX4rf//738dZbb8Waa64ZRx99dAwYMCBOOumkWGqppeLtt9+OJ554Iq30Kg6XXHJJ6q/6/+lPfzqxXnDBBRNH8Vh44YVjvvnmS/f9u9/9rsv76mo8+L96s9fQfWscqK8777xzGm9FfyR9Tz75ZIexRwjnhnZ930EI6+PXzN8m+fLRI/nyjBBCzwghLMeIOclzYk7yjBDCToTwueeei7333jsuuuiiWHbZZZPEbbfddkmCtJVUcnfmmWfGFVdcESNHjpxFCO+7777o06dPeuarEJzDDz881l577bQt8emnn26TLm0x3W+//ZL4SD4lZiNGjEhio6KVxLvvvjutVupau+22W+rH+uuvH3puUSJ2xx13JNG57bbbkjiq7QkTJiQha/9cn8RR2w8333zz2GKLLeLvf/97PPbYY3HE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umJ/X4yPYQ3hv/gebrnllhU75rp2vFifxLr79NNP/VriO5tVK8Ynff755472m266qWvWrFlWkZjjKsYnmR+WkQbF+KSMDLlOw5AgrBM+vfmbILBy5Up34YUXugULFvjLt2rVyt14442uRYsW30R31us1qfC+7bbb3G9+8xt/3Y022shdddVVrm3bton9sNo/99xzbtSoUWu994knnshU8Priiy+6q6++2gUxc9JJJ7nevXsXnDs24/ft29edcMIJ7qijjlqv87w+Lvb222+7wYMHOwIJbN9993WXXnqpX1P5jPVEG2zEiBE1mvXs2dN99tlnNV7r1q2bGzRo0PoYznq5RrG+584773SPPPJIrm977LGH55clEWT5mPjEvPrqq+7yyy/PfRe33357d+6557o999zTN5VPWnsps+769+/vRXSwAw880F100UWZujFTrE+65JJL3OzZs3NMtt12Wzd69GjXpEkT/5p8UmG3eMMNN7hnnnnGTZ482W2++ebrxYeuj4sU65OifSJWuPLKK93FF1/sDjrooKrySRKE62N16holJUCQ9fTTT7s77rjD3xUk4CSouOKKK0p6nXL8sLlz57rzzz/fi5u99trL3XzzzW7GjBnu8ccfTwwMrPZTp051/FG4/fbbawz3W9/6VqrMYzkyivcJYde9e3d33HHHueOPP9698MIL7rrrrnP33nuvXzdJhkgmMMXOPPPMTArCU045xX9/uKHw/vvvu9NOO82dfvrpDhGXZFOmTHFjxozxWfmOHTsmCsJDDjnEHXHEEbm3I3xCcFYJa8XqY7G+5+GHH3Y77LCDv2Hz3nvvuXPOOccdc8wxPrjPilk+Jj7OWbNmuQ8//NAdfPDBju/myJEjXQjgaCuftLZP4qbNfffd5/3Ydttt51566SX/946/Ax06dMjKUnLF+qRx48a5Aw44wO2+++5u8eLF3odx8+7UU0/NCUL5pOTl8dhjjzn4YVkThMX6pECIJMPZZ5/tb1ZFBWE1+CQYSBBmxpVWz0AI7AkmBg4c6AdNoEqGkC9tmvLJSiZ1/fXXuzfffNMRmGKUovXp08fddNNNrk2bNmsNzWoPM96LwM6qTZ8+3Yuep556yjVo0MAP86c//ak7+uijffYvySjL+uKLL9yAAQP8f1nLEJLJ4+75tdde6/bee2+PgACTQB3Rl2SrVq1yy5cv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R0CCsDheai0CIiACIiACIiACIiACIiACmSEgQZiZqdRAREAEREAEREAEREAEREAERKA4AhKExfFSaxEQAREQAREQAREQAREQARHIDAEJwsxMpQYiAiIgAiIgAiIgAiIgAiIgAsURkCAsjpdai4AIiIAIiIAIiIAIiIAIiEBmCEgQZmYqNRAREAEREAEREAEREAEREAERKI6ABGFxvNRaBERABERABERABERABERABDJDQIIwM1OpgYiACIiACIiACIiACIiACIhAcQQkCIvjpdYiIAIiIAIiIAIiIAIiIAIikBkCEoSZmUoNRAREQAREQAREQAREQAREQASKIyBBWBwvtRYBERABERABERABERABERCBzBCQIMzMVGogIiACIiACIiACIiACIiACIlAcAQnC4niptQiIgAiIgAiIgAiIgAiIgAhkhoAEYWamUgMRAREQAREQAREQAREQAREQgeIISBAWx0utRUAEREAEREAEvgECn3zyifv888/dVltt5Ro0aPAN9ECXFAEREIFsEpAgzOa8alQiIAIiUDEEDjjgALdq1aoa/Z01a1Yu6O/du7d76623avx+6tSprlmzZhUzxnwdPfroo927777rf33qqae6AQMGrLcxcb0//OEP/no/+9nP3JAhQ2p17T//+c/ujjvucMOHD3etWrWq1Wfke9PChQvdzTff7P70pz/VWCN77bWX5/X973+/pNfL92HFjvHee+91t956q/+4nXbayT366KNu2rRp7oILLshd4ne/+53baKONStL/r776yn/+E0884caOHZv7zNNPP93Nnj27znNckk7qQ0RABMqWgARh2U6NOiYCIiAC1UGgffv2aw2UgJqg/7PPPnOHHnroWr9/9tlnXfPmzSseUFQQnnLKKW7gwIHrbUxca86cObUWC0uXLnWXXnqpe/XVV/1nPPnkk27rrbcuWf+Z44suuqjg540ePdr98Ic/LNk14x9U2zEmCcLnnnvOXXjhhSUXhPPmzXPDhg3zNxbInk6ZMiV3jVKJ/nUGWB8sAiJQFgQkCMtiGtQJERABEaheAkmCcNCgQe7EE090M2bMcIMHD5YgXAfLo66C8I033nB9+/bN9ayUgnDZsmXuyCOPrJEV3HXXXd3mm2+eE7FcuEmTJu6pp55yG2+88Tog5Fxtx7ho0SJHdhNr1KiR69ixo1tXgnD8+PEOYYzFBSGCH1GLbb/99q5169brhJM+VAREoLIJSBBW9vyp9yIgAiJQ8QSSBOG+++7rbrvtNnfjjTe6CRMmFBSEH374obv99tt9WSFZkk033dTtv//+juwbnxNsxYoVbty4ce6ll15y//rXv/zLCIoddtjBnXDCCe7ggw/2r913333uhRde8D/37NnT/e9//3OTJ092CxYscN/97nd9lqdNmzYFuZM1++Uvf+n+8Y9/uE8//TR3rS5dungR1aJFC/9aNEPYtWtXRx8p8UNE/PjHP/ZlkaGs8P333/elmWSEEByMc9ttt/XtKPnccMMNc32i/5Qp/uUvf/Gvfec733EHHnigO/bYY3PtkgQhbO68807/HkTW3Xff7X++7LLL3N///vdcn+k/cxPKXfkFgm277bZz119/vW/Hfr/777/fzZw507PDmA8yvj169CjILypyaNinT5/cjYFnnnnGXXzxxf79zAfZsd12283/m9Ji+g+jjz76yP++bdu2vhQXMVnM/MKi0BjpQxj/cccd5x588EH39ttve8asqUceecRfj5+vvPLKtQTh+eef7x566CH/Ge3atXPnnntubl0xBq6PdejQwZ199tnuP//5T40MMuWnlFY//PDDuTUWmLCW+/fv76699lrPAjv88MPd8ccf73/+73//6yZOnOimT5/u1wjfgz322MPPCyXcwc477zzH9wujv4jaF1980X9/DjnkEF8Cm4VMfcHFqF+KQBUQkCCsgknWEEVABESgnAlEBeGee+6ZEzEEu4gngnzERnQfYSgZffPNNx3BeD676aabcgHuySef7ObOnZu3bSg/vOqqq7wAxBBd8f2NVlbq97//vTvttNPyXocsza9+9Su3wQYb1BCESW/o1q2b35uHKD3iiCNqBP7R9ieddJJjvxh2ww03+GA/yWDNHjMOZUkShL/97W/d5ZdfnntrKClFkAVRh7hC+FEuGreQoYIZ74kKxmhbxMoZZ5yRl9EVV1zhfv3rX+d+//zzz7stt9zS/3v16tVe/O++++5+foLF9+hFP5x+IbLIkqWdX8RPoTEec8wxXpjHLYwrvocwniFMGjxjRkByXTKuGEIeYcrNAn4Oxg0TykPZNxg3bjRccskl/oZCfJ/oF1984fguzJ8/P5E/mXky9NhRRx2Vu3mS9F047LDD3DXXXJN3HvULERCByiAg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" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "titanic_df, titanic_dict = data_loading('titanic')\n", "\n", "# Keep a compact feature set for API payload simplicity\n", "features = ['Pclass', 'Age', 'Sex', 'SibSp', 'Parch']\n", "target = 'Survived'\n", "\n", "df = titanic_df[features + [target]].copy()\n", "df['Pclass'] = df['Pclass'].map({'First class': 1, 'Second class': 2, 'Third class': 3})\n", "df['Age'] = df['Age'].fillna(df['Age'].median())\n", "\n", "X = df[features]\n", "y = df[target].astype(int).to_frame()\n", "\n", "encoder = one_hot.OneHotEncoder(cols=['Sex'], use_cat_names=True).fit(X)\n", "X_enc = encoder.transform(X)\n", "\n", "X_train, X_test, y_train, y_test = train_test_split(\n", " X_enc, y,\n", " test_size=0.25,\n", " random_state=79,\n", " stratify=y\n", ")\n", "\n", "clf = RandomForestClassifier(n_estimators=300, random_state=79)\n", "clf.fit(X_train, y_train.values.ravel())\n", "\n", "y_pred = pd.DataFrame(clf.predict(X_test), columns=['pred'], index=X_test.index)\n", "response_dict = {0: 'Not survived', 1: 'Survived'}\n", "\n", "xpl = SmartExplainer(\n", " model=clf,\n", " preprocessing=encoder,\n", " features_dict=titanic_dict,\n", " label_dict=response_dict,\n", " title_story='Titanic API tutorial'\n", ")\n", "xpl.compile(x=X_test, y_pred=y_pred, y_target=y_test)\n", "\n", "xpl.plot.features_importance()" ] }, { "cell_type": "markdown", "id": "f18a8e55", "metadata": {}, "source": [ "## 3. Build and persist SmartPredictor" ] }, { "cell_type": "code", "execution_count": 3, "id": "2391e6e0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "predictor = xpl.to_smartpredictor()\n", "predictor.save('predictor_fastapi.pkl')\n", "predictor_load = load_smartpredictor('predictor_fastapi.pkl')\n", "predictor_load" ] }, { "cell_type": "markdown", "id": "79dd9e76", "metadata": {}, "source": [ "## 4. Quick local test before API" ] }, { "cell_type": "code", "execution_count": 4, "id": "5e38a525", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO: Shap explainer type - \n" ] }, { "data": { "text/plain": [ "( ypred proba\n", " 0 Survived 0.940329,\n", " class_0 class_1\n", " 0 0.059671 0.940329,\n", " ypred proba feature_1 value_1 contribution_1 feature_2 value_2 \\\n", " 0 Survived 0.940329 Ticket class 1 0.419093 Age 28.0 \n", " \n", " contribution_2 feature_3 value_3 contribution_3 \\\n", " 0 0.218704 Sex male -0.141945 \n", " \n", " feature_4 value_4 contribution_4 \n", " 0 Relatives such as brother or wife 0 0.059795 )" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sample = pd.DataFrame([\n", " {'Pclass': 1, 'Age': 28, 'Sex': 'male', 'SibSp': 0, 'Parch': 0},\n", "])\n", "\n", "# SmartPredictor expects raw input with exact dtypes used at fit time\n", "expected_types = predictor_load.features_types.copy()\n", "sample = sample[list(expected_types.keys())].copy()\n", "\n", "for col, dtype in expected_types.items():\n", " if dtype.startswith('int') or dtype.startswith('float'):\n", " sample[col] = pd.to_numeric(sample[col], errors='coerce').astype(dtype)\n", " else:\n", " sample[col] = sample[col].astype(dtype)\n", "\n", "assert all(str(sample[c].dtype) == expected_types[c] for c in sample.columns), (\n", " sample.dtypes.astype(str).to_dict(), expected_types\n", ")\n", "\n", "predictor_load.add_input(x=sample)\n", "predictor_load.modify_mask(max_contrib=4)\n", "\n", "prediction = predictor_load.data['ypred']\n", "proba = predictor_load.predict_proba()\n", "explanation = predictor_load.summarize()\n", "\n", "prediction, proba, explanation" ] }, { "cell_type": "markdown", "id": "800c756f", "metadata": {}, "source": [ "## 5. FastAPI app with /predict and /explain" ] }, { "cell_type": "code", "execution_count": 5, "id": "cb999a23", "metadata": {}, "outputs": [], "source": [ "class TitanicPassenger(BaseModel):\n", " Pclass: int\n", " Age: float\n", " Sex: str\n", " SibSp: int\n", " Parch: int\n", "\n", "\n", "def cast_input_to_predictor_schema(x_input: pd.DataFrame) -> pd.DataFrame:\n", " expected_types = predictor_load.features_types.copy()\n", " x_cast = x_input[list(expected_types.keys())].copy()\n", "\n", " for col, dtype in expected_types.items():\n", " if dtype.startswith('int') or dtype.startswith('float'):\n", " x_cast[col] = pd.to_numeric(x_cast[col], errors='coerce').astype(dtype)\n", " else:\n", " x_cast[col] = x_cast[col].astype(dtype)\n", " return x_cast\n", "\n", "\n", "app = FastAPI(title='Shapash SmartPredictor API')\n", "\n", "\n", "@app.get('/health')\n", "def health():\n", " return {'status': 'ok'}\n", "\n", "\n", "@app.post('/predict')\n", "def predict(payload: TitanicPassenger):\n", " x_input = pd.DataFrame([payload.model_dump()])\n", " x_input = cast_input_to_predictor_schema(x_input)\n", " predictor_load.add_input(x=x_input)\n", "\n", " ypred_df = predictor_load.data['ypred']\n", " ypred_value = ypred_df.iloc[0, 0]\n", "\n", " proba_df = predictor_load.predict_proba()\n", " proba_dict = proba_df.iloc[0].to_dict()\n", "\n", " return {\n", " 'prediction': ypred_value,\n", " 'predict_proba': proba_dict\n", " }\n", "\n", "\n", "@app.post('/explain')\n", "def explain(payload: TitanicPassenger):\n", " x_input = pd.DataFrame([payload.model_dump()])\n", " x_input = cast_input_to_predictor_schema(x_input)\n", " predictor_load.add_input(x=x_input)\n", " predictor_load.modify_mask(max_contrib=4)\n", "\n", " detail = predictor_load.detail_contributions()\n", " summary = predictor_load.summarize()\n", "\n", " if hasattr(detail, 'to_dict'):\n", " detail_out = detail.to_dict(orient='records')[0]\n", " else:\n", " detail_out = detail\n", "\n", " if hasattr(summary, 'to_dict'):\n", " summary_out = summary.to_dict(orient='records')[0]\n", " else:\n", " summary_out = summary\n", "\n", " return {\n", " 'detail_contributions': detail_out,\n", " 'summary': summary_out\n", " }" ] }, { "cell_type": "markdown", "id": "e604a9cf", "metadata": {}, "source": [ "## 6. Run and test API (optional)\n", "\n", "Create the API file from this notebook (next cell), then run in a terminal from repo root:\n", "- `uvicorn tutorial.fastapi_app:app --reload --port 8000`\n", "\n", "Then open:\n", "- `http://127.0.0.1:8000/health`\n", "- `http://127.0.0.1:8000/docs`" ] }, { "cell_type": "code", "execution_count": 6, "id": "2e2158be", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting fastapi_app.py\n" ] } ], "source": [ "%%writefile fastapi_app.py\n", "from pathlib import Path\n", "\n", "import pandas as pd\n", "from fastapi import FastAPI\n", "from pydantic import BaseModel\n", "\n", "from shapash.utils.load_smartpredictor import load_smartpredictor\n", "\n", "BASE_DIR = Path(__file__).resolve().parent\n", "PREDICTOR_PATH = BASE_DIR / 'predictor_fastapi.pkl'\n", "predictor_load = load_smartpredictor(str(PREDICTOR_PATH))\n", "\n", "\n", "class TitanicPassenger(BaseModel):\n", " Pclass: int\n", " Age: float\n", " Sex: str\n", " SibSp: int\n", " Parch: int\n", "\n", "\n", "def cast_input_to_predictor_schema(x_input: pd.DataFrame) -> pd.DataFrame:\n", " expected_types = predictor_load.features_types.copy()\n", " x_cast = x_input[list(expected_types.keys())].copy()\n", "\n", " for col, dtype in expected_types.items():\n", " if dtype.startswith('int') or dtype.startswith('float'):\n", " x_cast[col] = pd.to_numeric(x_cast[col], errors='coerce').astype(dtype)\n", " else:\n", " x_cast[col] = x_cast[col].astype(dtype)\n", " return x_cast\n", "\n", "\n", "app = FastAPI(title='Shapash SmartPredictor API')\n", "\n", "\n", "@app.get('/health')\n", "def health():\n", " return {'status': 'ok'}\n", "\n", "\n", "@app.post('/predict')\n", "def predict(payload: TitanicPassenger):\n", " x_input = pd.DataFrame([payload.model_dump()])\n", " x_input = cast_input_to_predictor_schema(x_input)\n", " predictor_load.add_input(x=x_input)\n", "\n", " ypred_df = predictor_load.data['ypred']\n", " ypred_value = ypred_df.iloc[0, 0]\n", "\n", " proba_df = predictor_load.predict_proba()\n", " proba_dict = proba_df.iloc[0].to_dict()\n", "\n", " return {\n", " 'prediction': ypred_value,\n", " 'predict_proba': proba_dict,\n", " }\n", "\n", "\n", "@app.post('/explain')\n", "def explain(payload: TitanicPassenger):\n", " x_input = pd.DataFrame([payload.model_dump()])\n", " x_input = cast_input_to_predictor_schema(x_input)\n", " predictor_load.add_input(x=x_input)\n", " predictor_load.modify_mask(max_contrib=4)\n", "\n", " detail = predictor_load.detail_contributions()\n", " summary = predictor_load.summarize()\n", "\n", " if hasattr(detail, 'to_dict'):\n", " detail_out = detail.to_dict(orient='records')[0]\n", " else:\n", " detail_out = detail\n", "\n", " if hasattr(summary, 'to_dict'):\n", " summary_out = summary.to_dict(orient='records')[0]\n", " else:\n", " summary_out = summary\n", "\n", " return {\n", " 'detail_contributions': detail_out,\n", " 'summary': summary_out,\n", " }" ] }, { "cell_type": "markdown", "id": "3269539b", "metadata": {}, "source": [ "## 7. API tests from notebook\n", "\n", "Run these cells after starting the API with:\n", "`uvicorn tutorial.fastapi_app:app --reload --port 8000`" ] }, { "cell_type": "code", "execution_count": 7, "id": "62d0a10b", "metadata": {}, "outputs": [], "source": [ "import requests\n", "\n", "BASE_URL = 'http://127.0.0.1:8000'\n", "\n", "def show_response(resp):\n", " print('status:', resp.status_code)\n", " try:\n", " return resp.json()\n", " except Exception:\n", " return resp.text" ] }, { "cell_type": "code", "execution_count": 8, "id": "7c54458a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "status: 200\n" ] }, { "data": { "text/plain": [ "{'status': 'ok'}" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 1) Health check\n", "resp = requests.get(f'{BASE_URL}/health', timeout=10)\n", "show_response(resp)" ] }, { "cell_type": "code", "execution_count": 9, "id": "5fac7791", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "status: 200\n" ] }, { "data": { "text/plain": [ "{'prediction': 'Not survived',\n", " 'predict_proba': {'class_0': 0.7975449735449736,\n", " 'class_1': 0.20245502645502644}}" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 2) Predict endpoint\n", "payload = {\n", " 'Pclass': 3,\n", " 'Age': 24.0,\n", " 'Sex': 'female',\n", " 'SibSp': 0,\n", " 'Parch': 0,\n", "}\n", "resp = requests.post(f'{BASE_URL}/predict', json=payload, timeout=20)\n", "show_response(resp)" ] }, { "cell_type": "code", "execution_count": 10, "id": "f709546f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "status: 200\n" ] }, { "data": { "text/plain": [ "{'detail_contributions': {'ypred': 'Not survived',\n", " 'proba': 0.7975449735449736,\n", " 'Pclass': 0.1632976894295741,\n", " 'Age': 0.17053304797895122,\n", " 'Sex': -0.22136818570220265,\n", " 'SibSp': 0.022301925827067973,\n", " 'Parch': 0.04572959780799086},\n", " 'summary': {'ypred': 'Not survived',\n", " 'proba': 0.7975449735449736,\n", " 'feature_1': 'Sex',\n", " 'value_1': 'female',\n", " 'contribution_1': -0.22136818570220265,\n", " 'feature_2': 'Age',\n", " 'value_2': 24.0,\n", " 'contribution_2': 0.17053304797895122,\n", " 'feature_3': 'Ticket class',\n", " 'value_3': 3,\n", " 'contribution_3': 0.1632976894295741,\n", " 'feature_4': 'Relatives like children or parents',\n", " 'value_4': 0,\n", " 'contribution_4': 0.04572959780799086}}" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# 3) Explain endpoint\n", "resp = requests.post(f'{BASE_URL}/explain', json=payload, timeout=30)\n", "show_response(resp)" ] } ], "metadata": { "kernelspec": { "display_name": "survptf_312", "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.12.12" } }, "nbformat": 4, "nbformat_minor": 5 }