{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# default_exp models.spop"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# S-Pop\n",
    "> Session Popularity\n",
    "\n",
    "Session popularity predictor that gives higher scores to items with higher number of occurrences in the session. Ties are broken up by adding the popularity score of the item.\n",
    "\n",
    "The score is given by $r_{s,i} = supp_{s,i} + \\frac{supp_i}{(1+supp_i)}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "from nbdev.showdoc import *\n",
    "from fastcore.nb_imports import *\n",
    "from fastcore.test import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "import numpy as np\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class SessionPop:\n",
    "    '''\n",
    "    SessionPop(top_n=100, item_key='ItemId', support_by_key=None)\n",
    "    Session popularity predictor that gives higher scores to items with higher number of occurrences in the session. \n",
    "    Ties are broken up by adding the popularity score of the item.\n",
    "    The score is given by:\n",
    "    .. math::\n",
    "        r_{s,i} = supp_{s,i} + \\\\frac{supp_i}{(1+supp_i)}\n",
    "    Parameters\n",
    "    --------\n",
    "    top_n : int\n",
    "        Only give back non-zero scores to the top N ranking items. Should be higher or equal than the cut-off of your evaluation. (Default value: 100)\n",
    "    item_key : string\n",
    "        The header of the item IDs in the training data. (Default value: 'ItemId')\n",
    "    '''    \n",
    "    def __init__(self, top_n = 1000, session_key = 'SessionId', item_key = 'ItemId'):\n",
    "        self.top_n = top_n\n",
    "        self.item_key = item_key\n",
    "        self.session_id = session_key\n",
    "        \n",
    "    def fit(self, data):\n",
    "        '''\n",
    "        Trains the predictor.\n",
    "        Parameters\n",
    "        --------\n",
    "        data: pandas.DataFrame\n",
    "            Training data. It contains the transactions of the sessions. \n",
    "            It has one column for session IDs, one for item IDs.\n",
    "        '''\n",
    "        self.items = data[self.item_key].unique()\n",
    "        grp = data.groupby(self.item_key)\n",
    "        self.pop_list = grp.size()\n",
    "        self.pop_list = self.pop_list / (self.pop_list + 1)\n",
    "        self.pop_list.sort_values(ascending=False, inplace=True)\n",
    "        self.pop_list = self.pop_list.head(self.top_n)\n",
    "        self.prev_session_id = -1\n",
    "         \n",
    "    def predict_next(self, last_items, k):\n",
    "        '''\n",
    "        Gives predicton scores for a selected set of items on how likely they be the next item in the session.\n",
    "        Parameters\n",
    "        --------\n",
    "        last_items : list of items clicked in current session\n",
    "        k : number of items to recommend and evaluate based on it\n",
    "        Returns\n",
    "        --------\n",
    "        out : pandas.Series\n",
    "            Prediction scores for selected items on how likely to be the next item of this session. Indexed by the item IDs.\n",
    "        '''\n",
    "        pers = {}\n",
    "        for i in last_items:\n",
    "            pers[i] = pers[i] + 1 if i in pers.keys() else  1\n",
    "        \n",
    "        preds = np.zeros(len(self.items))\n",
    "        mask = np.in1d(self.items, self.pop_list.index)\n",
    "        ser = pd.Series(pers)\n",
    "        preds[mask] = self.pop_list[self.items[mask]]\n",
    "        \n",
    "        mask = np.in1d(self.items, ser.index)\n",
    "        preds[mask] += ser[self.items[mask]]\n",
    "        \n",
    "        series = pd.Series(data=preds, index=self.items)\n",
    "        series = series / series.max()    \n",
    "        return series.nlargest(k).index.values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import time\n",
    "import argparse\n",
    "import pandas as pd\n",
    "from recohut.utils.common_utils import download_url"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Downloading https://github.com/RecoHut-Datasets/yoochoose/raw/v4/yoochoose_train.txt\n",
      "Downloading https://github.com/RecoHut-Datasets/yoochoose/raw/v4/yoochoose_valid.txt\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'/content/data/yoochoose_valid.txt'"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data_root = '/content/data'\n",
    "download_url('https://github.com/RecoHut-Datasets/yoochoose/raw/v4/yoochoose_train.txt', data_root)\n",
    "download_url('https://github.com/RecoHut-Datasets/yoochoose/raw/v4/yoochoose_valid.txt', data_root)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Finished Reading Data \n",
      "Start Model Fitting...\n",
      "End Model Fitting with total time = 0.10341858863830566 \n",
      " Start Predictions...\n",
      "Finished Prediction for  5000 items.\n",
      "Recall: 0.313485342019544\n",
      "\n",
      "MRR: 0.11998186799961241\n",
      "End Model Predictions with total time = 33.76607871055603\n"
     ]
    }
   ],
   "source": [
    "parser = argparse.ArgumentParser()\n",
    "parser.add_argument('--K', type=int, default=20, help=\"K items to be used in Recall@K and MRR@K\")\n",
    "parser.add_argument('--topn', type=int, default=100, help=\"Number of top items to return non zero scores for them (most popular)\")\n",
    "parser.add_argument('--itemid', default='sid', type=str)\n",
    "parser.add_argument('--sessionid', default='uid', type=str)\n",
    "parser.add_argument('--valid_data', default='yoochoose_valid.txt', type=str)\n",
    "parser.add_argument('--train_data', default='yoochoose_train.txt', type=str)\n",
    "parser.add_argument('--data_folder', default=data_root, type=str)\n",
    "\n",
    "# Get the arguments\n",
    "args = parser.parse_args([])\n",
    "train_data = os.path.join(args.data_folder, args.train_data)\n",
    "x_train = pd.read_csv(train_data)\n",
    "valid_data = os.path.join(args.data_folder, args.valid_data)\n",
    "x_valid = pd.read_csv(valid_data)\n",
    "x_valid.sort_values(args.sessionid, inplace=True)\n",
    "\n",
    "print('Finished Reading Data \\nStart Model Fitting...')\n",
    "# Fitting AR Model\n",
    "t1 = time.time()\n",
    "model = SessionPop(top_n = args.topn, session_key = args.sessionid, item_key = args.itemid)\n",
    "model.fit(x_train)\n",
    "t2 = time.time()\n",
    "print('End Model Fitting with total time =', t2 - t1, '\\n Start Predictions...')\n",
    "\n",
    "# Test Set Evaluation\n",
    "test_size = 0.0\n",
    "hit = 0.0\n",
    "MRR = 0.0\n",
    "cur_length = 0\n",
    "cur_session = -1\n",
    "last_items = []\n",
    "t1 = time.time()\n",
    "index_item = x_valid.columns.get_loc(args.itemid)\n",
    "index_session = x_valid.columns.get_loc(args.sessionid)\n",
    "train_items = model.items\n",
    "counter = 0\n",
    "for row in x_valid.itertuples( index=False ):\n",
    "    counter += 1\n",
    "    if counter % 5000 == 0:\n",
    "        print('Finished Prediction for ', counter, 'items.')\n",
    "    session_id, item_id = row[index_session], row[index_item]\n",
    "    if session_id != cur_session:\n",
    "        cur_session = session_id\n",
    "        last_items = []\n",
    "        cur_length = 0\n",
    "    \n",
    "    if item_id in train_items:\n",
    "        if len(last_items) > cur_length: #make prediction\n",
    "            cur_length += 1\n",
    "            test_size += 1\n",
    "            # Predict the most similar items to items\n",
    "            predictions = model.predict_next(last_items, k = args.K)\n",
    "            # Evaluation\n",
    "            rank = 0\n",
    "            for predicted_item in predictions:\n",
    "                rank += 1\n",
    "                if predicted_item == item_id:\n",
    "                    hit += 1.0\n",
    "                    MRR += 1/rank\n",
    "                    break\n",
    "        \n",
    "        last_items.append(item_id)\n",
    "t2 = time.time()\n",
    "print('Recall: {}'.format(hit / test_size))\n",
    "print ('\\nMRR: {}'.format(MRR / test_size))\n",
    "print('End Model Predictions with total time =', t2 - t1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **References:-**\n",
    "- [https://github.com/mmaher22/iCV-SBR/tree/master/Source Codes/S-POP_Python](https://github.com/mmaher22/iCV-SBR/tree/master/Source%20Codes/S-POP_Python)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Author: Sparsh A.\n",
      "\n",
      "Last updated: 2022-01-01 06:12:09\n",
      "\n",
      "Compiler    : GCC 7.5.0\n",
      "OS          : Linux\n",
      "Release     : 5.4.144+\n",
      "Machine     : x86_64\n",
      "Processor   : x86_64\n",
      "CPU cores   : 2\n",
      "Architecture: 64bit\n",
      "\n",
      "pandas  : 1.1.5\n",
      "numpy   : 1.19.5\n",
      "argparse: 1.1\n",
      "IPython : 5.5.0\n",
      "\n"
     ]
    }
   ],
   "source": [
    "#hide\n",
    "%reload_ext watermark\n",
    "%watermark -a \"Sparsh A.\" -m -iv -u -t -d"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
