{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# default_exp transforms.user_grouping"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# User Grouping\n",
    "> Generate user groups for training group recommenders."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "from nbdev.showdoc import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "import os\n",
    "import shutil\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import scipy.sparse as sp\n",
    "from collections import Counter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "class GroupGenerator(object):\n",
    "    \"\"\"\n",
    "    Group Data Generator\n",
    "    \"\"\"\n",
    "    def __init__(self,\n",
    "                 user_ids,\n",
    "                 item_ids,\n",
    "                 ratings,\n",
    "                 output_path,\n",
    "                 rating_threshold,\n",
    "                 num_groups,\n",
    "                 group_sizes,\n",
    "                 min_num_ratings,\n",
    "                 train_ratio,\n",
    "                 val_ratio,\n",
    "                 negative_sample_size,\n",
    "                 verbose=True,\n",
    "                 ):\n",
    "        self.users = user_ids\n",
    "        self.items = item_ids\n",
    "        self.ratings = ratings\n",
    "        self.rating_threshold = rating_threshold\n",
    "        self.negative_sample_size = negative_sample_size\n",
    "        rating_mat, timestamp_mat = \\\n",
    "                    self.load_ratings_file()\n",
    "\n",
    "        groups, group_ratings, groups_rated_items_dict, groups_rated_items_set = \\\n",
    "            self.generate_group_ratings(self.users, rating_mat, timestamp_mat,\n",
    "                                        num_groups=num_groups,\n",
    "                                        group_sizes=group_sizes,\n",
    "                                        min_num_ratings=min_num_ratings)\n",
    "        members, group_ratings_train, group_ratings_val, group_ratings_test, \\\n",
    "            group_negative_items_val, group_negative_items_test, \\\n",
    "            user_ratings_train, user_ratings_val, user_ratings_test, \\\n",
    "            user_negative_items_val, user_negative_items_test = \\\n",
    "            self.split_ratings(group_ratings, rating_mat, timestamp_mat,\n",
    "                               groups, groups_rated_items_dict, groups_rated_items_set,\n",
    "                               train_ratio=train_ratio, val_ratio=val_ratio)\n",
    "\n",
    "        os.makedirs(output_path, exist_ok=True)\n",
    "        groups_path = os.path.join(output_path, 'groupMember.dat')\n",
    "        group_ratings_train_path = os.path.join(output_path, 'groupRatingTrain.dat')\n",
    "        group_ratings_val_path = os.path.join(output_path, 'groupRatingVal.dat')\n",
    "        group_ratings_test_path = os.path.join(output_path, 'groupRatingTest.dat')\n",
    "        group_negative_items_val_path = os.path.join(output_path, 'groupRatingValNegative.dat')\n",
    "        group_negative_items_test_path = os.path.join(output_path, 'groupRatingTestNegative.dat')\n",
    "        user_ratings_train_path = os.path.join(output_path, 'userRatingTrain.dat')\n",
    "        user_ratings_val_path = os.path.join(output_path, 'userRatingVal.dat')\n",
    "        user_ratings_test_path = os.path.join(output_path, 'userRatingTest.dat')\n",
    "        user_negative_items_val_path = os.path.join(output_path, 'userRatingValNegative.dat')\n",
    "        user_negative_items_test_path = os.path.join(output_path, 'userRatingTestNegative.dat')\n",
    "\n",
    "        self.save_groups(groups_path, groups)\n",
    "        self.save_ratings(group_ratings_train, group_ratings_train_path)\n",
    "        self.save_ratings(group_ratings_val, group_ratings_val_path)\n",
    "        self.save_ratings(group_ratings_test, group_ratings_test_path)\n",
    "        self.save_negative_samples(group_negative_items_val, group_negative_items_val_path)\n",
    "        self.save_negative_samples(group_negative_items_test, group_negative_items_test_path)\n",
    "        self.save_ratings(user_ratings_train, user_ratings_train_path)\n",
    "        self.save_ratings(user_ratings_val, user_ratings_val_path)\n",
    "        self.save_ratings(user_ratings_test, user_ratings_test_path)\n",
    "        self.save_negative_samples(user_negative_items_val, user_negative_items_val_path)\n",
    "        self.save_negative_samples(user_negative_items_test, user_negative_items_test_path)\n",
    "\n",
    "        if verbose:\n",
    "            num_group_ratings = len(group_ratings)\n",
    "            num_user_ratings = len(user_ratings_train) + len(user_ratings_val) + len(user_ratings_test)\n",
    "            num_rated_items = len(groups_rated_items_set)\n",
    "\n",
    "            print('Save data: ' + output_path)\n",
    "            print('# Users: ' + str(len(members)))\n",
    "            print('# Items: ' + str(num_rated_items))\n",
    "            print('# Groups: ' + str(len(groups)))\n",
    "            print('# U-I ratings: ' + str(num_user_ratings))\n",
    "            print('# G-I ratings: ' + str(num_group_ratings))\n",
    "            print('Avg. # ratings / user: {:.2f}'.format(num_user_ratings / len(members)))\n",
    "            print('Avg. # ratings / group: {:.2f}'.format(num_group_ratings / len(groups)))\n",
    "            print('Avg. group size: {:.2f}'.format(np.mean(list(map(len, groups)))))\n",
    "\n",
    "    def load_ratings_file(self):\n",
    "        rating_mat = sp.dok_matrix((max(self.users) + 1, max(self.items) + 1),\n",
    "                                   dtype=np.int)\n",
    "        timestamp_mat = rating_mat.copy()\n",
    "\n",
    "        for _, row in self.ratings.iterrows():\n",
    "            arr = row.values\n",
    "            user, item, rating, timestamp = \\\n",
    "                int(arr[0]), int(arr[1]), int(arr[2]), int(arr[3])\n",
    "            rating_mat[user, item] = rating\n",
    "            timestamp_mat[user, item] = timestamp\n",
    "\n",
    "        return rating_mat, timestamp_mat\n",
    "\n",
    "    def generate_group_ratings(self, users, rating_mat, timestamp_mat,\n",
    "                               num_groups, group_sizes, min_num_ratings):\n",
    "        np.random.seed(0)\n",
    "        groups = set()\n",
    "        groups_ratings = []\n",
    "        groups_rated_items_dict = {}\n",
    "        groups_rated_items_set = set()\n",
    "\n",
    "        while len(groups) < num_groups:\n",
    "            group_id = len(groups) + 1\n",
    "\n",
    "            while True:\n",
    "                group = tuple(np.sort(\n",
    "                    np.random.choice(users, np.random.choice(group_sizes),\n",
    "                                     replace=False)))\n",
    "                if group not in groups:\n",
    "                    break\n",
    "\n",
    "            pos_group_rating_counter = Counter()\n",
    "            neg_group_rating_counter = Counter()\n",
    "            group_rating_list = []\n",
    "            group_rated_items = set()\n",
    "\n",
    "            for member in group:\n",
    "                _, items = rating_mat[member, :].nonzero()\n",
    "                pos_items = [item for item in items\n",
    "                             if rating_mat[member, item] >= self.rating_threshold]\n",
    "                neg_items = [item for item in items\n",
    "                             if rating_mat[member, item] < self.rating_threshold]\n",
    "                pos_group_rating_counter.update(pos_items)\n",
    "                neg_group_rating_counter.update(neg_items)\n",
    "\n",
    "            for item, num_ratings in pos_group_rating_counter.items():\n",
    "                if num_ratings == len(group):\n",
    "                    timestamp = max([timestamp_mat[member, item]\n",
    "                                     for member in group])\n",
    "                    group_rated_items.add(item)\n",
    "                    group_rating_list.append((group_id, item, 1, timestamp))\n",
    "\n",
    "            for item, num_ratings in neg_group_rating_counter.items():\n",
    "                if (num_ratings == len(group)) \\\n",
    "                        or (num_ratings + pos_group_rating_counter[item] == len(group)):\n",
    "                    timestamp = max([timestamp_mat[member, item]\n",
    "                                     for member in group])\n",
    "                    group_rated_items.add(item)\n",
    "                    group_rating_list.append((group_id, item, 0, timestamp))\n",
    "\n",
    "            if len(group_rating_list) >= min_num_ratings:\n",
    "                groups.add(group)\n",
    "                groups_rated_items_dict[group_id] = group_rated_items\n",
    "                groups_rated_items_set.update(group_rated_items)\n",
    "                for group_rating in group_rating_list:\n",
    "                    groups_ratings.append(group_rating)\n",
    "\n",
    "        return list(groups), groups_ratings, groups_rated_items_dict, groups_rated_items_set\n",
    "\n",
    "    def split_ratings(self, group_ratings, rating_mat, timestamp_mat,\n",
    "                      groups, groups_rated_items_dict, groups_rated_items_set, train_ratio, val_ratio):\n",
    "        num_group_ratings = len(group_ratings)\n",
    "        num_train = int(num_group_ratings * train_ratio)\n",
    "        num_test = int(num_group_ratings * (1 - train_ratio - val_ratio))\n",
    "\n",
    "        group_ratings = \\\n",
    "            sorted(group_ratings, key=lambda group_rating: group_rating[-1])\n",
    "        group_ratings_train = group_ratings[:num_train]\n",
    "        group_ratings_val = group_ratings[num_train:-num_test]\n",
    "        group_ratings_test = group_ratings[-num_test:]\n",
    "\n",
    "        timestamp_split_train = group_ratings_train[-1][-1]\n",
    "        timestamp_split_val = group_ratings_val[-1][-1]\n",
    "\n",
    "        user_ratings_train = []\n",
    "        user_ratings_val = []\n",
    "        user_ratings_test = []\n",
    "\n",
    "        members = set()\n",
    "        users_rated_items_dict = {}\n",
    "\n",
    "        for group in groups:\n",
    "            for member in group:\n",
    "                if member in members:\n",
    "                    continue\n",
    "                members.add(member)\n",
    "                user_rated_items = set()\n",
    "                _, items = rating_mat[member, :].nonzero()\n",
    "                for item in items:\n",
    "                    if item not in groups_rated_items_set:\n",
    "                        continue\n",
    "                    user_rated_items.add(item)\n",
    "                    if rating_mat[member, item] >= self.rating_threshold:\n",
    "                        rating_tuple = (member, item, 1,\n",
    "                                        timestamp_mat[member, item])\n",
    "                    else:\n",
    "                        rating_tuple = (member, item, 0,\n",
    "                                        timestamp_mat[member, item])\n",
    "                    if timestamp_mat[member, item] <= timestamp_split_train:\n",
    "                        user_ratings_train.append(rating_tuple)\n",
    "                    elif timestamp_split_train < timestamp_mat[member, item] <= timestamp_split_val:\n",
    "                        user_ratings_val.append(rating_tuple)\n",
    "                    else:\n",
    "                        user_ratings_test.append(rating_tuple)\n",
    "\n",
    "                users_rated_items_dict[member] = user_rated_items\n",
    "\n",
    "        np.random.seed(0)\n",
    "\n",
    "        user_negative_items_val = self.get_negative_samples(\n",
    "            user_ratings_val, groups_rated_items_set, users_rated_items_dict)\n",
    "        user_negative_items_test = self.get_negative_samples(\n",
    "            user_ratings_test, groups_rated_items_set, users_rated_items_dict)\n",
    "        group_negative_items_val = self.get_negative_samples(\n",
    "            group_ratings_val, groups_rated_items_set, groups_rated_items_dict)\n",
    "        group_negative_items_test = self.get_negative_samples(\n",
    "            group_ratings_test, groups_rated_items_set, groups_rated_items_dict)\n",
    "\n",
    "        return members, group_ratings_train, group_ratings_val, group_ratings_test, \\\n",
    "            group_negative_items_val, group_negative_items_test, \\\n",
    "            user_ratings_train, user_ratings_val, user_ratings_test, \\\n",
    "            user_negative_items_val, user_negative_items_test\n",
    "\n",
    "    def get_negative_samples(self, ratings, groups_rated_items_set, rated_items_dict):\n",
    "        negative_items_list = []\n",
    "        for sample in ratings:\n",
    "            sample_id, item, _, _ = sample\n",
    "            missed_items = groups_rated_items_set - rated_items_dict[sample_id]\n",
    "            negative_items = \\\n",
    "                np.random.choice(list(missed_items), self.negative_sample_size,\n",
    "                                 replace=(len(missed_items) < self.negative_sample_size))\n",
    "            negative_items_list.append((sample_id, item, negative_items))\n",
    "        return negative_items_list\n",
    "\n",
    "    def save_groups(self, groups_path, groups):\n",
    "        with open(groups_path, 'w') as file:\n",
    "            for i, group in enumerate(groups):\n",
    "                file.write(str(i + 1) + ' '\n",
    "                           + ','.join(map(str, list(group))) + '\\n')\n",
    "\n",
    "    def save_ratings(self, ratings, ratings_path):\n",
    "        with open(ratings_path, 'w') as file:\n",
    "            for rating in ratings:\n",
    "                file.write(' '.join(map(str, list(rating))) + '\\n')\n",
    "\n",
    "    def save_negative_samples(self, negative_items, negative_items_path):\n",
    "        with open(negative_items_path, 'w') as file:\n",
    "            for samples in negative_items:\n",
    "                user, item, negative_items = samples\n",
    "                file.write('({},{}) '.format(user, item)\n",
    "                           + ' '.join(map(str, list(negative_items))) + '\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ml-1m.zip           100%[===================>]   5.64M  18.2MB/s    in 0.3s    \n",
      "Archive:  ml-1m.zip\n",
      "   creating: ml-1m/\n",
      "  inflating: ml-1m/movies.dat        \n",
      "  inflating: ml-1m/ratings.dat       \n",
      "  inflating: ml-1m/README            \n",
      "  inflating: ml-1m/users.dat         \n"
     ]
    }
   ],
   "source": [
    "!wget -q --show-progress https://files.grouplens.org/datasets/movielens/ml-1m.zip\n",
    "!unzip ml-1m.zip"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "  <div id=\"df-d0425a99-c8f0-4b45-be45-742e08f72615\">\n",
       "    <div class=\"colab-df-container\">\n",
       "      <div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>0</th>\n",
       "      <th>1</th>\n",
       "      <th>2</th>\n",
       "      <th>3</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>1193</td>\n",
       "      <td>5</td>\n",
       "      <td>978300760</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>661</td>\n",
       "      <td>3</td>\n",
       "      <td>978302109</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1</td>\n",
       "      <td>914</td>\n",
       "      <td>3</td>\n",
       "      <td>978301968</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1</td>\n",
       "      <td>3408</td>\n",
       "      <td>4</td>\n",
       "      <td>978300275</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1</td>\n",
       "      <td>2355</td>\n",
       "      <td>5</td>\n",
       "      <td>978824291</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>\n",
       "      <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-d0425a99-c8f0-4b45-be45-742e08f72615')\"\n",
       "              title=\"Convert this dataframe to an interactive table.\"\n",
       "              style=\"display:none;\">\n",
       "        \n",
       "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
       "       width=\"24px\">\n",
       "    <path d=\"M0 0h24v24H0V0z\" fill=\"none\"/>\n",
       "    <path d=\"M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z\"/><path d=\"M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z\"/>\n",
       "  </svg>\n",
       "      </button>\n",
       "      \n",
       "  <style>\n",
       "    .colab-df-container {\n",
       "      display:flex;\n",
       "      flex-wrap:wrap;\n",
       "      gap: 12px;\n",
       "    }\n",
       "\n",
       "    .colab-df-convert {\n",
       "      background-color: #E8F0FE;\n",
       "      border: none;\n",
       "      border-radius: 50%;\n",
       "      cursor: pointer;\n",
       "      display: none;\n",
       "      fill: #1967D2;\n",
       "      height: 32px;\n",
       "      padding: 0 0 0 0;\n",
       "      width: 32px;\n",
       "    }\n",
       "\n",
       "    .colab-df-convert:hover {\n",
       "      background-color: #E2EBFA;\n",
       "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
       "      fill: #174EA6;\n",
       "    }\n",
       "\n",
       "    [theme=dark] .colab-df-convert {\n",
       "      background-color: #3B4455;\n",
       "      fill: #D2E3FC;\n",
       "    }\n",
       "\n",
       "    [theme=dark] .colab-df-convert:hover {\n",
       "      background-color: #434B5C;\n",
       "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
       "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
       "      fill: #FFFFFF;\n",
       "    }\n",
       "  </style>\n",
       "\n",
       "      <script>\n",
       "        const buttonEl =\n",
       "          document.querySelector('#df-d0425a99-c8f0-4b45-be45-742e08f72615 button.colab-df-convert');\n",
       "        buttonEl.style.display =\n",
       "          google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
       "\n",
       "        async function convertToInteractive(key) {\n",
       "          const element = document.querySelector('#df-d0425a99-c8f0-4b45-be45-742e08f72615');\n",
       "          const dataTable =\n",
       "            await google.colab.kernel.invokeFunction('convertToInteractive',\n",
       "                                                     [key], {});\n",
       "          if (!dataTable) return;\n",
       "\n",
       "          const docLinkHtml = 'Like what you see? Visit the ' +\n",
       "            '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
       "            + ' to learn more about interactive tables.';\n",
       "          element.innerHTML = '';\n",
       "          dataTable['output_type'] = 'display_data';\n",
       "          await google.colab.output.renderOutput(dataTable, element);\n",
       "          const docLink = document.createElement('div');\n",
       "          docLink.innerHTML = docLinkHtml;\n",
       "          element.appendChild(docLink);\n",
       "        }\n",
       "      </script>\n",
       "    </div>\n",
       "  </div>\n",
       "  "
      ],
      "text/plain": [
       "   0     1  2          3\n",
       "0  1  1193  5  978300760\n",
       "1  1   661  3  978302109\n",
       "2  1   914  3  978301968\n",
       "3  1  3408  4  978300275\n",
       "4  1  2355  5  978824291"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ratings = pd.read_csv('ml-1m/ratings.dat', sep='::', engine='python', header=None)\n",
    "ratings.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0          6040\n",
       "1          3952\n",
       "2             5\n",
       "3    1046454590\n",
       "dtype: int64"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ratings.max()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Save data: ./data/silver\n",
      "# Users: 23\n",
      "# Items: 360\n",
      "# Groups: 10\n",
      "# U-I ratings: 2866\n",
      "# G-I ratings: 503\n",
      "Avg. # ratings / user: 124.61\n",
      "Avg. # ratings / group: 50.30\n",
      "Avg. group size: 2.30\n"
     ]
    }
   ],
   "source": [
    "group_generator = GroupGenerator(\n",
    "                    user_ids=np.arange(6041),\n",
    "                    item_ids=np.arange(3953),\n",
    "                    ratings=ratings,\n",
    "                    output_path='./data/silver',\n",
    "                    rating_threshold=4,\n",
    "                    num_groups=10,\n",
    "                    group_sizes=[2, 3],\n",
    "                    min_num_ratings=20,\n",
    "                    train_ratio=0.7,\n",
    "                    val_ratio=0.1,\n",
    "                    negative_sample_size=10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "53.0K: userRatingTrain.dat\n",
      "0.1K: groupMember.dat\n",
      "5.4K: groupRatingTestNegative.dat\n",
      "5.0K: userRatingValNegative.dat\n",
      "1.8K: userRatingVal.dat\n",
      "15.2K: userRatingTestNegative.dat\n",
      "5.7K: userRatingTest.dat\n",
      "6.4K: groupRatingTrain.dat\n",
      "1.8K: groupRatingTest.dat\n",
      "0.9K: groupRatingVal.dat\n",
      "2.8K: groupRatingValNegative.dat\n"
     ]
    }
   ],
   "source": [
    "path = './data/silver'\n",
    "fun = lambda x : os.path.isfile(os.path.join(path,x))\n",
    "files_list = filter(fun, os.listdir(path))\n",
    "size_of_file = [\n",
    "    (f,os.stat(os.path.join(path, f)).st_size)\n",
    "    for f in files_list\n",
    "]\n",
    "for f,s in size_of_file:\n",
    "    print(\"{:.1f}K: {}\".format(round(s/(1024),3),f))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Author: Sparsh A.\n",
      "\n",
      "Last updated: 2021-12-19 08:58:22\n",
      "\n",
      "Compiler    : GCC 7.5.0\n",
      "OS          : Linux\n",
      "Release     : 5.4.104+\n",
      "Machine     : x86_64\n",
      "Processor   : x86_64\n",
      "CPU cores   : 2\n",
      "Architecture: 64bit\n",
      "\n",
      "pandas : 1.1.5\n",
      "IPython: 5.5.0\n",
      "numpy  : 1.19.5\n",
      "scipy  : 1.4.1\n",
      "\n"
     ]
    }
   ],
   "source": [
    "#hide\n",
    "!pip install -q watermark\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
}
