{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "104bcf2b-9cd7-a200-04f0-3900bc909943"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import pandas as pd\n",
        "import numpy as np\n",
        "import time \n",
        "import seaborn as sns\n",
        "from sklearn import cross_validation"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "bc494bf2-a36f-859f-1251-de8652685944"
      },
      "source": [
        "In some comments on the forum, the representative for Gruppo Bimbo highlighted returns, and the problem of trying to predict returns. I'm still grappling with understanding the nuances of the dataset, but one question I was interested in was whether returns predict future sales. For example, if there are a lot of returns for a product by a client, will there be less sales for it in the future? If so, then we could use returns - as opposed to just demand - in our model to help forecast future demand.\n",
        "\n",
        "I decided to inspect a random sample of the histories of some products with some clients in the dataset, watching how the sales and returns changed from week 3 to week 7. Could I see any obvious relationship between returns and future sales? "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "993e358f-dd59-56d7-7e8f-1b8935c7905b"
      },
      "outputs": [],
      "source": [
        "# This bit of memory-saving code is from Eric Couto, https://www.kaggle.com/ericcouto/grupo-bimbo-inventory-demand/using-82-less-memory/code\n",
        "types = {'Semana':np.uint8, 'Agencia_ID':np.uint16, 'Canal_ID':np.uint8,\n",
        "         'Ruta_SAK':np.uint16, 'Cliente_ID':np.uint32, 'Producto_ID':np.uint16,'Venta_uni_hoy':np.uint32,\n",
        "         'Dev_uni_proxima':np.uint32, 'Demanda_uni_equil':np.uint32 }\n",
        "train = pd.read_csv('../input/train.csv', usecols=types.keys(), dtype=types)\n",
        "print(train.info(memory_usage=True))\n",
        "new_names = [\"week\",\"depot\",\"channel\",\"route\",\"client\",\"prod\",\"sales_units\",\"return_units_next_week\",\"demand\"]\n",
        "train.columns = new_names"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "0b6acfe8-5f17-6866-3d4e-b5a26bf32928"
      },
      "outputs": [],
      "source": [
        "def plot_weekly_samples(w, h, pt_sales, pt_returns):\n",
        "    plt.figure(figsize=[12,12])\n",
        "    ind = np.arange(len(pt_sales.columns))\n",
        "    width = 0.3\n",
        "    for i in range(len(pt_sales)):\n",
        "        plt.subplot(h,w,i+1)\n",
        "        plt.bar(left=ind, height=pt_sales.iloc[i], width=width,linewidth=4,color='b',edgecolor='b')\n",
        "        plt.bar(left=ind+width+0.2, height=pt_returns.iloc[i], width=width,linewidth=4,color='r',edgecolor='r')\n",
        "        plt.title(pt_sales.iloc[i].name)\n",
        "        plt.xlim([0,7])\n",
        "        plt.xticks(ind+width+0.1,pt_sales.columns)\n",
        "    plt.subplots_adjust(hspace = 0.7, left = 0.05, right = 0.95, bottom = 0.05, top=0.95)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "1b8588d0-89df-00c4-91db-be767d751d79"
      },
      "outputs": [],
      "source": [
        "# Number of rows and columns to have in the output\n",
        "w = 4\n",
        "h = 8\n",
        "sample_size = w*h\n",
        "\n",
        "randseed = 7\n",
        "\n",
        "# Select a random sample of sample_size rows from the training data\n",
        "dummy, sample_rows = cross_validation.train_test_split(range(len(train)), test_size=sample_size, train_size=0, random_state=randseed)\n",
        "selected = train.iloc[sample_rows][['client','route','prod']]\n",
        "\n",
        "# For each unique client, route, and product combo from those rows, look up all the weeks of data\n",
        "# associated with them (this will also result in averaging over multiple depots, for example, if there\n",
        "# are different entries for them)\n",
        "selected_train = pd.DataFrame()\n",
        "for i,t in selected.iterrows():\n",
        "    selected_train = pd.concat([selected_train, train.loc[(train.loc[:,'client']==t.client) & (train.loc[:,'route'] == t.route) & (train.loc[:,'prod'] == t.loc['prod']),:]])\n",
        "\n",
        "# Make pivot tables for sales and returns with weeks for columns and samples for rows\n",
        "pt_sales = selected_train.pivot_table(values='sales_units',index=['client','route','prod'],columns=['week'],aggfunc=np.mean)\n",
        "pt_returns = selected_train.pivot_table(values='return_units_next_week',index=['client','route','prod'],columns=['week'],aggfunc=np.mean)\n",
        "\n",
        "# Plot these sample product histories in a grid (arbitrarily)\n",
        "plot_weekly_samples(w, h, pt_sales, pt_returns)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "0d7ae111-0868-0a90-faf8-ef0c98193d2c"
      },
      "source": [
        "The title of each subplot is the (client, route, product) combination that uniquely specifies it. The horizontal axis is week, the vertical axis is number of units, blue bars indicate sales and red bars indicate returns (placed at the x value of return_units_next_week). When the red bar is barely visible, that means that it was 0. When the blue or red bars are not present that means there were no entries for that week.\n",
        "\n",
        "Looking at one example of the graphs that are produced, I see first of all not much in the way of trends or predictability in sales by week, so that it probably won't be worth forecasting future weeks based on anything more than the aggregate of sales.\n",
        "\n",
        "Looking at the returns, the first thing I notice is that they are rare. My intuition is that very few product-client histories are affected by returns. This is borne out by the numbers:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "f46ad5a5-8a09-a3eb-10fa-297ff01fd478"
      },
      "outputs": [],
      "source": [
        "print(\"Training entries that have any return units at all: %.1f%%\" % (100*np.mean(train.return_units_next_week > 0)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "5a3ac4a4-0c45-6fb1-1927-9137e1fbb5a3"
      },
      "source": [
        "They would appear even rarer if I counted weeks where there were no training entries (probably because there were no sales) Therefore, I decided to just look at those product-client histories where there was at least one return in the target weeks, using the same type of graphs."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "4e98eccf-c89d-f0fc-b84f-1b332e6439a3"
      },
      "outputs": [],
      "source": [
        "with_returns_indices = np.nonzero(train['return_units_next_week'] >0)[0]\n",
        "dummy, sample_indices = cross_validation.train_test_split(range(len(with_returns_indices)), test_size=sample_size, train_size=0, random_state=randseed)\n",
        "sample_rows = with_returns_indices[sample_indices]\n",
        "\n",
        "selected = train.iloc[sample_rows][['client','route','prod']]\n",
        "\n",
        "selected_train = pd.DataFrame()\n",
        "for i,t in selected.iterrows():\n",
        "    selected_train = pd.concat([selected_train, train.loc[(train.loc[:,'client']==t.client) & (train.loc[:,'route'] == t.route) & (train.loc[:,'prod'] == t.loc['prod']),:]])\n",
        "\n",
        "pt_sales = selected_train.pivot_table(values='sales_units',index=['client','route','prod'],columns=['week'],aggfunc=np.mean)\n",
        "pt_returns = selected_train.pivot_table(values='return_units_next_week',index=['client','route','prod'],columns=['week'],aggfunc=np.mean)\n",
        "\n",
        "plot_weekly_samples(w, h, pt_sales, pt_returns)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "6ad7057a-fca7-a929-6c5c-4357c11e35fd"
      },
      "source": [
        "Now, looking at client-product histories that had plenty of returns, I see a few possible trends: there seem to often be weeks after the occurrence of returns where there are no sales (only returns, since they occur the week after). Where there are sales later, they appear to be lower after the returns. I also see some returns that are cumulatively larger than the sales during this period, and so must reflect sales from before. \n",
        "\n",
        "One other thing I noticed is that there are many entries where only one or two units were sold, and then immediately returned. I made one more of these figures to focus on the histories where more than two returns happened."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "c08eb4e0-3b32-c25a-0890-aee523109213"
      },
      "outputs": [],
      "source": [
        "with_returns_indices = np.nonzero(train['return_units_next_week'] > 2)[0]\n",
        "dummy, sample_indices = cross_validation.train_test_split(range(len(with_returns_indices)), test_size=sample_size, train_size=0, random_state=randseed)\n",
        "sample_rows = with_returns_indices[sample_indices]\n",
        "\n",
        "selected = train.iloc[sample_rows][['client','route','prod']]\n",
        "\n",
        "selected_train = pd.DataFrame()\n",
        "for i,t in selected.iterrows():\n",
        "    selected_train = pd.concat([selected_train, train.loc[(train.loc[:,'client']==t.client) & (train.loc[:,'route'] == t.route) & (train.loc[:,'prod'] == t.loc['prod']),:]])\n",
        "\n",
        "pt_sales = selected_train.pivot_table(values='sales_units',index=['client','route','prod'],columns=['week'],aggfunc=np.mean)\n",
        "pt_returns = selected_train.pivot_table(values='return_units_next_week',index=['client','route','prod'],columns=['week'],aggfunc=np.mean)\n",
        "\n",
        "plot_weekly_samples(w, h, pt_sales, pt_returns)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "f31573e0-bc66-5e80-d651-edeb4294d26c"
      },
      "source": [
        "The same patterns appear to be present: a dropoff in sales after substantial returns. I decided to look at this a bit more systematically, randomly sampling histories with returns from the training dataset as a whole and seeing the impact of a return week on subsequent sales. I decided to focus on client-product histories that had only one week with returns, which appear to be relatively common, to make the impact of the returns clearer. I did not look at the size or percentage of return, but only whether one occurred at all."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "6288d1cd-e532-6332-697f-604ba96a1bb9"
      },
      "outputs": [],
      "source": [
        "# Make a heatmap of sales relative to weeks where returns occurred, based on a sample \n",
        "# from the training set\n",
        "\n",
        "lerandseed=7\n",
        "n_samples_for_heatmap = 10000\n",
        "dummy, sample_indices = cross_validation.train_test_split(range(len(with_returns_indices)), test_size=n_samples_for_heatmap, train_size=0, random_state=randseed)\n",
        "sample_rows = with_returns_indices[sample_indices]\n",
        "selected = train.iloc[sample_rows][['client','route','prod']]\n",
        "selected_train = train.loc[train.loc[:,'client'].isin(selected.client) & train.loc[:,'route'].isin(selected.route) & train.loc[:,'prod'].isin(selected.loc[:,'prod']) ,:]\n",
        "\n",
        "pt_sales = selected_train.pivot_table(values='sales_units',index=['client','route','prod'],columns=['week'],aggfunc=np.mean)\n",
        "pt_returns = selected_train.pivot_table(values='return_units_next_week',index=['client','route','prod'],columns=['week'],aggfunc=np.mean)\n",
        "\n",
        "with_one_return = np.nansum(pt_returns.loc[:,3:8]>0,1) == 1\n",
        "one_return = pt_returns.iloc[with_one_return,:-1].fillna(0).values # Weeks are columns\n",
        "one_return_sales = pt_sales.iloc[with_one_return,:].fillna(0).values\n",
        "\n",
        "return_weeks = np.nonzero(one_return)[1]\n",
        "week_sales = np.zeros([7,7])\n",
        "for rw in range(6):\n",
        "    rows_for_week = (return_weeks==rw)\n",
        "    values = np.nanmean(one_return_sales[rows_for_week],0)\n",
        "    week_sales[rw,:] = values\n",
        "week_sales[-1,:] = np.nanmean(one_return_sales,0)\n",
        "plt.figure()\n",
        "yticks = [str(a) for a in range(4,10)] + [\"Mean for \\none-return \\nweeks\"]\n",
        "sns.set(font_scale=1.2)\n",
        "ax = sns.heatmap(week_sales, cmap=\"Blues\", annot=False, xticklabels=range(3,10), yticklabels=yticks)\n",
        "plt.ylabel('Week return occurred')\n",
        "plt.xlabel('Week')\n",
        "cax = plt.gcf().axes[-1]\n",
        "cax.set_ylabel('Mean units sold for week')\n",
        "plt.draw()\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "9d218c2b-f053-e421-c9bf-513bcc938d2a"
      },
      "source": [
        "This heatmap shows the relation of the return week (in weeks with only one return) to sales both before and after. Focusing on the first row, for example, it summarizes client-product sales histories where the return happened in week 4 (so the first sales data is for the previous week), with each cell in that column representing the mean units sold across all the client-product histories in my sample. And so forth.\n",
        "\n",
        "The most noticeable result is high sales the week before the returns, which makes sense because there must be something to return (and many histories seem to consist of a client trying a product, returning all units, and never buying it again). \n",
        "\n",
        "There is also an increase two weeks before the return, which could indicate ramping up sales too high or accumulating too much inventory.\n",
        "\n",
        "Beyond that, it's hard to see patterns, in particular no drop in the week with the returns. Jowever this may be due to the fact that client-product histories deal with vastly different values for units sold, and so certain histories may numerically dominate the cells. Therefore I normalized each history across all 7 weeks, and recreated the heat map."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "_cell_guid": "5b182474-7813-93f6-ce42-16075a83d3f9"
      },
      "outputs": [],
      "source": [
        "# Make a heatmap of sales relative to weeks where returns occurred, based on a sample \n",
        "# from the training set\n",
        "\n",
        "one_return_sales_scaled = (one_return_sales-one_return_sales.mean(axis=1, keepdims=True)) / one_return_sales.std(axis=1, keepdims=True)\n",
        "week_sales = np.zeros([7,7])\n",
        "for rw in range(6):\n",
        "    rows_for_week = (return_weeks==rw)\n",
        "    values = np.nanmean(one_return_sales_scaled[rows_for_week],0)\n",
        "    week_sales[rw,:] = values\n",
        "week_sales[-1,:] = np.nanmean(one_return_sales_scaled,0)\n",
        "plt.figure()\n",
        "yticks = [str(a) for a in range(4,10)] + [\"Mean for \\none-return \\nweeks\"]\n",
        "sns.set(font_scale=1.2)\n",
        "ax = sns.heatmap(week_sales, cmap=\"Greens\", annot=False, xticklabels=range(3,10), yticklabels=yticks)\n",
        "plt.ylabel('Week return occurred')\n",
        "plt.xlabel('Week')\n",
        "cax = plt.gcf().axes[-1]\n",
        "cax.set_ylabel('Mean units sold for week (Normalized)')\n",
        "plt.draw()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "_cell_guid": "536605c6-e01c-458e-dd57-77ef8e9097c4"
      },
      "source": [
        "This normalization has greatly smoothed the plot, and now shows clearly that there were lower sales during the week of returns, but higher sales one and two weeks before. Nevertheless, it's hard to see any clear drop in sales in the weeks after the return week.\n",
        "\n",
        "## Conclusion\n",
        "Returns are present in only a small percentage of training examples (3.4%), and do not appear to predict future sales in a consistent way. Therefore incorporating returns into a model, as opposed to simple demand, should provide only very small improvements to predictive power at best. "
      ]
    }
  ],
  "metadata": {
    "_change_revision": 0,
    "_is_fork": false,
    "kernelspec": {
      "display_name": "Python 3",
      "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.5.2"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}