{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "Analysing Strava activities using Colab, Pandas and Matplotlib (Part 4)",
      "version": "0.3.2",
      "views": {},
      "default_view": {},
      "provenance": [
        {
          "file_id": "1Png2gQvNaKJrq_YVhoFbkIoY9vzqq5sC",
          "timestamp": 1528233508683
        },
        {
          "file_id": "1eR2xoY-eOFodCIQuRROGytZrkioHLddt",
          "timestamp": 1527888313301
        }
      ],
      "private_outputs": true,
      "collapsed_sections": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "metadata": {
        "id": "PV6ioN1CXslD",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        "# Preparation"
      ]
    },
    {
      "metadata": {
        "id": "EHDKAkTztUbi",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Install gspread\n",
        "!pip install --upgrade -q gspread"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "E51hj0o2tUcN",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Import Strava activitities from Google Sheets\n",
        "\n",
        "from google.colab import auth\n",
        "auth.authenticate_user()\n",
        "\n",
        "import gspread\n",
        "from oauth2client.client import GoogleCredentials\n",
        "\n",
        "gc = gspread.authorize(GoogleCredentials.get_application_default())\n",
        "\n",
        "worksheet = gc.open('Strava 2018-07-02').sheet1\n",
        "\n",
        "# get_all_values gives a list of rows.\n",
        "rows = worksheet.get_all_values()\n",
        "\n",
        "# Convert to a DataFrame and render.\n",
        "import pandas as pd\n",
        "activities_raw = pd.DataFrame.from_records(rows[1:], columns=rows[0]);"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "CXFqnmhmt9wM",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Index activities, convert data types, convert units\n",
        "activities = activities_raw.copy()\n",
        "\n",
        "# Project\n",
        "activities = activities[['date', 'type', 'distance', 'elapsed_time', 'commute', 'name']]\n",
        "\n",
        "# Set data types\n",
        "activities['distance'] = activities['distance'].astype(float)\n",
        "activities['elapsed_time'] = activities['elapsed_time'].astype(float)\n",
        "activities['commute'] = activities['commute'].apply(lambda x: 1 if x == 'TRUE' else 0).astype(int)\n",
        "\n",
        "# Convert units\n",
        "activities['distance'] = activities['distance'] / 1000\n",
        "activities['elapsed_time'] = activities['elapsed_time'] / 60\n",
        "\n",
        "# Number of activities (at this aggregation level, trivially 1)\n",
        "activities['count'] = 1\n",
        "\n",
        "# Index\n",
        "activities['date'] = pd.to_datetime(activities_raw['date'])\n",
        "activities.set_index(['date', 'type'], inplace=True)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "JZ9PgCHMHqK0",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Define formatters for readability\n",
        "formatters = {\n",
        "    'distance': '{:.0f}km'.format,\n",
        "    'elapsed_time':' {:.0f}min'.format,\n",
        "}\n",
        "\n",
        "detailed_formatters = {\n",
        "    'distance': '{:.1f}km'.format,\n",
        "    'elapsed_time':' {:.1f}min'.format,\n",
        "}"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "MFCgnpUiXraH",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        "# Exploration"
      ]
    },
    {
      "metadata": {
        "id": "KmrQVbsdawmR",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Print a few random activities\n",
        "print(\n",
        "    activities[['distance', 'elapsed_time', 'commute', 'count']]\n",
        "      .sample(5)\n",
        "      .sort_index()\n",
        "      .to_string(formatters=formatters))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "_71DHKJuH8z-",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Print totals\n",
        "print(\n",
        "    activities\n",
        "      .groupby(level='type')\n",
        "      .sum()\n",
        "      .sort_values(by='count', ascending=False)\n",
        "      .to_string(formatters=formatters))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "2jiWJhR0hJv8",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Show date range\n",
        "\n",
        "first_date = activities.index.levels[0].min()\n",
        "last_date = activities.index.levels[0].max()\n",
        "print('Activities recorded from %s to %s, total of %d days.' % (\n",
        "    first_date.strftime('%Y-%m-%d'),\n",
        "    last_date.strftime('%Y-%m-%d'),\n",
        "    (last_date - first_date).days))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "3t-tHRDxwa68",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Activities with the longest distance\n",
        "print(\n",
        "    activities['distance']\n",
        "        .groupby('type').nlargest(3)\n",
        "        .to_string(float_format='%.1fkm'))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "n6rUBZJ40v0-",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Activities with the longest duration\n",
        "print(\n",
        "    (activities['elapsed_time']\n",
        "        .groupby('type').nlargest(4) / 60)\n",
        "        .to_string(float_format='%.1fh'))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "ijelhc4oPasQ",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Summarize by day and activity type\n",
        "import numpy as np\n",
        "\n",
        "activities_by_day_type = (\n",
        "    activities\n",
        "        .groupby([pd.Grouper(freq='D', level=0), 'type'])\n",
        "        .sum())\n",
        "\n",
        "activities_by_day_type = (\n",
        "    activities_by_day_type\n",
        "        .reset_index(level='type')\n",
        "        .to_period('D')\n",
        "        .reset_index()\n",
        "        .set_index(['date', 'type']))\n",
        "\n",
        "first_day = activities_by_day_type.index.levels[0].min()\n",
        "last_day = activities_by_day_type.index.levels[0].max()\n",
        "days = pd.period_range(first_day, last_day, freq='D')\n",
        "types = activities_by_day_type.index.levels[1].unique()\n",
        "index = pd.MultiIndex.from_product([days, types], names=['day', 'type'])\n",
        "activities_by_day_type = activities_by_day_type.reindex(index, fill_value=0)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "Nx8UebDLjCL-",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Print a few days\n",
        "print(activities_by_day_type.tail(12).to_string(formatters=formatters))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "zNOVoBwJRr3m",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Activities by day\n",
        "activities_by_day = activities_by_day_type.groupby('day').sum()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "Q_T5AAfURtmu",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Activities by type\n",
        "activities_by_type = activities_by_day_type.groupby('type').sum()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "nn6KeWl6RB2e",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Daily average\n",
        "print(\n",
        "    activities_by_day[['distance', 'elapsed_time']]\n",
        "        .mean()\n",
        "        .to_frame('mean')\n",
        "        .T\n",
        "        .to_string(formatters=detailed_formatters))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "iy9HdX6zueNF",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Quick sanity check\n",
        "\n",
        "print(\"average distance per day: %.1fkm\" % (\n",
        "    activities['distance'].sum() /\n",
        "    (last_day - first_day)))\n",
        "print(\"average time per day: %.1fmin\" % (\n",
        "    activities['elapsed_time'].sum() /\n",
        "    (last_day - first_day)))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "V6BReJIW4Qmj",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Find days, months, quarters, years with extremes\n",
        "\n",
        "frequencies = ['D', 'M', 'Q', 'Y']\n",
        "\n",
        "for f in frequencies:\n",
        "  extremes_by_type = (\n",
        "    activities_by_day_type\n",
        "      .unstack('type')\n",
        "      .groupby(pd.Grouper(freq=f)).sum()\n",
        "      .agg(['idxmax', 'max']))\n",
        "  extremes_by_type.index = [f, 'max']\n",
        "\n",
        "  print(\n",
        "    extremes_by_type.T\n",
        "      .unstack(level=0)\n",
        "      .swaplevel(axis=1)\n",
        "      .sort_index(axis=1))\n",
        "  print('freq=%s\\n' % f)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "92bmpDsVtlzq",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Weekdays with largest means\n",
        "weekday_abbr = 'Mon Tue Wed Thu Fri Sat Sun'.split()\n",
        "print(\n",
        "    activities_by_day_type[['distance', 'elapsed_time', 'commute', 'count']]\n",
        "        .unstack('type')\n",
        "        .groupby(lambda d: weekday_abbr[d.weekday]).mean()\n",
        "        .agg(['idxmax', 'max'])\n",
        "        .T.unstack(level=0)\n",
        "        .swaplevel(axis=1).sort_index(axis=1))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "Pf2M5xKB3yPS",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Months of the year with largest means\n",
        "months = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split()\n",
        "print(\n",
        "    activities_by_day_type[['distance', 'elapsed_time', 'commute', 'count']]\n",
        "        .unstack('type')\n",
        "        .groupby(pd.Grouper(freq='M')).sum()\n",
        "        .groupby(lambda m: months[m.month-1]).mean()\n",
        "        .agg(['idxmax', 'max'])\n",
        "        .T.unstack(level=0)\n",
        "        .swaplevel(axis=1).sort_index(axis=1))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "E83KyhMcxit0",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Quarters of the year with largest means\n",
        "print(\n",
        "    activities_by_day_type[['distance', 'elapsed_time', 'commute', 'count']]\n",
        "        .unstack('type')\n",
        "        .groupby(pd.Grouper(freq='Q')).sum()\n",
        "        .groupby(lambda d: \"Q%d\" % d.quarter).mean()\n",
        "        .agg(['idxmax', 'max'])\n",
        "        .T.unstack(level=0)\n",
        "        .swaplevel(axis=1).sort_index(axis=1))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "wPlf42imXnEY",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        "# Plotting"
      ]
    },
    {
      "metadata": {
        "id": "ZKGz_9yK0f7F",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Plot running activity, quarterly, summed\n",
        "import matplotlib.pyplot as plt\n",
        "from matplotlib.ticker import StrMethodFormatter\n",
        "\n",
        "fig, (ax1, ax2, ax3) = (\n",
        "    plt.subplots(\n",
        "        nrows=3, ncols=1,\n",
        "        sharex=True, sharey=False,\n",
        "        figsize=(8.025, 10)))\n",
        "\n",
        "runs_q_sum = (\n",
        "    activities.loc[(slice(None), 'Run'), :]\n",
        "        .reset_index('type', drop=True)\n",
        "        .to_period('D')\n",
        "        .groupby(pd.Grouper(freq='Q')).sum())\n",
        "\n",
        "runs_q_sum['distance'].plot(ax=ax1, kind='bar', color='#7799cc')\n",
        "ax1.set_ylabel('km')\n",
        "ax1.set_title('Distance')\n",
        "\n",
        "(runs_q_sum['elapsed_time'] / 60).plot(ax=ax2, kind='bar', color='#7799cc')\n",
        "ax2.set_ylabel('h')\n",
        "ax2.set_title('Duration')\n",
        "\n",
        "runs_q_sum['count'].plot(ax=ax3, kind='bar', color='#7799cc')\n",
        "ax3.set_ylabel('number')\n",
        "ax3.set_title('Count')\n",
        "\n",
        "fig.autofmt_xdate()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "fissOhZ9aOqF",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Plot running activity, quarterly, mean\n",
        "\n",
        "from matplotlib import ticker\n",
        "\n",
        "fig2, (ax4, ax5) = (\n",
        "    plt.subplots(\n",
        "        nrows=2, ncols=1,\n",
        "        sharex=True, sharey=False,\n",
        "        figsize=(8.025, 10/3*2)))\n",
        "\n",
        "(runs_q_sum['distance'] / runs_q_sum['count']).plot(ax=ax4, kind='bar', color='#7799cc');\n",
        "ax4.set_ylabel('km')\n",
        "ax4.set_title('Mean distance')\n",
        "ax4.yaxis.set_major_locator(ticker.MultipleLocator(5))\n",
        "\n",
        "(runs_q_sum['elapsed_time'] / runs_q_sum['count']).plot(ax=ax5, kind='bar', color='#7799cc');\n",
        "ax5.set_ylabel('min')\n",
        "ax5.set_title('Mean duration')\n",
        "ax5.yaxis.set_major_locator(ticker.MultipleLocator(60))\n",
        "\n",
        "fig2.autofmt_xdate()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "hruPUXTjcQHR",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Plot running activity, quarterly, median\n",
        "fig3, (ax6, ax7) = (\n",
        "    plt.subplots(\n",
        "        nrows=2, ncols=1,\n",
        "        sharex=True, sharey=False,\n",
        "        figsize=(8.025, 10/3*2)))\n",
        "\n",
        "runs_q_median = (\n",
        "    activities.loc[(slice(None), 'Run'), :]\n",
        "        .reset_index('type', drop=True)\n",
        "        .to_period('D')\n",
        "        .groupby(pd.Grouper(freq='Q')).median())\n",
        "\n",
        "runs_q_median['distance'].plot(ax=ax6, kind='bar', color='#7799cc')\n",
        "ax6.set_ylabel('km')\n",
        "ax6.set_title('Median distance')\n",
        "\n",
        "runs_q_median['elapsed_time'].plot(ax=ax7, kind='bar', color='#7799cc')\n",
        "ax7.set_ylabel('min')\n",
        "ax7.set_title('Median duration')\n",
        "ax7.yaxis.set_major_locator(ticker.MultipleLocator(30))\n",
        "\n",
        "fig3.autofmt_xdate()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "Lz5vBqBSgNTF",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Plot running activity, quarterly, pace\n",
        "fig4, (ax8, ax9) = (\n",
        "    plt.subplots(\n",
        "        nrows=2, ncols=1,\n",
        "        sharex=True, sharey=False,\n",
        "        figsize=(8.025, 10/3*2)))\n",
        "\n",
        "(runs_q_sum['elapsed_time'] / runs_q_sum['distance']).plot(ax=ax8, kind='bar', color='#7799cc');\n",
        "ax8.set_ylabel('min / km')\n",
        "ax8.set_title('Pace')\n",
        "\n",
        "(runs_q_sum['distance'] / (runs_q_sum['elapsed_time'] / 60)).plot(ax=ax9, kind='bar', color='#7799cc');\n",
        "ax9.set_ylabel('km / h')\n",
        "ax9.set_title('Speed')\n",
        "\n",
        "fig4.autofmt_xdate()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "pR73_kFXXjY9",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        "# Machine learning"
      ]
    },
    {
      "metadata": {
        "id": "YY7LQE7NZWW0",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        "Strava allows users to tag activities that were commutes. It is very easy to forget to tag an activity, which then reduces the quality of the collected data. Here, we will build a classifier which predicts whether an activity was a commute, so that we can go back to Strava and add missing tags if necessary. Thus we will consider any activity, even those which happened after the activity for which we predict the commute label; _leakage_ (not-a-time-machine) should not be an issue here."
      ]
    },
    {
      "metadata": {
        "id": "qEvSwsjnXdjn",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Split data into training and test\n",
        "\n",
        "import sklearn\n",
        "from sklearn.model_selection import train_test_split\n",
        "\n",
        "X = activities.reset_index()[['date', 'type', 'distance', 'elapsed_time', 'name']]\n",
        "y = activities.reset_index()['commute']\n",
        "\n",
        "X_train, X_test, y_train, y_test = (\n",
        "    train_test_split(\n",
        "        X, y,\n",
        "        train_size=0.8,\n",
        "        test_size=0.2,\n",
        "        random_state=314,\n",
        "        shuffle=True))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "wu7VG4V0MGnv",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Extract features\n",
        "\n",
        "import nltk\n",
        "from nltk.stem import WordNetLemmatizer\n",
        "\n",
        "nltk.download('wordnet')\n",
        "\n",
        "\n",
        "def type_features(X):\n",
        "  return pd.get_dummies(X[['type']])\n",
        "\n",
        "\n",
        "def weekend_features(X):\n",
        "  is_weekend = lambda d: d.dayofweek >= 5\n",
        "  weekend = X['date'].apply(is_weekend)\n",
        "  return pd.get_dummies(weekend, prefix='weekend')\n",
        "\n",
        "\n",
        "def time_features(X):\n",
        "  time = pd.cut(X['date'].apply(lambda d: d.hour),\n",
        "                     right=False,\n",
        "                     bins=[0, 6, 9, 12, 15, 18, 21, 24],\n",
        "                     labels=['night',\n",
        "                             'morning',\n",
        "                             'late_morning',\n",
        "                             'early_afternoon',\n",
        "                             'late_afternoon',\n",
        "                             'evening',\n",
        "                             'late_evening'])\n",
        "  return pd.get_dummies(time, prefix='time')\n",
        "\n",
        "\n",
        "def distance_features(X):\n",
        "  distance = pd.cut(X['distance'],\n",
        "                    bins=np.logspace(1, 7, base=2, num=10),\n",
        "                    labels=['xxxs', 'xxs',\n",
        "                            'xs', 's', 'm',\n",
        "                            'l', 'xl', 'xxl', 'xxxl'])\n",
        "  return pd.get_dummies(distance, prefix='distance')\n",
        "\n",
        "\n",
        "def duration_features(X):\n",
        "  duration = pd.cut(X['elapsed_time'],\n",
        "                    bins=np.logspace(4, 8, base=2, num=10),\n",
        "                    labels=['xxxs', 'xxs',\n",
        "                            'xs', 's', 'm',\n",
        "                            'l', 'xl', 'xxl', 'xxxl'])\n",
        "  return pd.get_dummies(duration, prefix='duration')\n",
        "\n",
        "\n",
        "def word_features(X, train_indices):\n",
        "  lemmatizer = WordNetLemmatizer()\n",
        "  lemmatize_words = lambda s: [lemmatizer.lemmatize(w) for w in s]\n",
        "\n",
        "  word_separators = r'[,:()!? ]'\n",
        "  word_hyphenators = r'[\\-_]'\n",
        "  remove_short_words = lambda s: list(filter(lambda w: len(w) >= 5, s))\n",
        "  make_unique = lambda s: list(set(s))\n",
        "  prefix_with_word = lambda s: ['word_' + w for w in s]\n",
        "\n",
        "  words = (\n",
        "      X['name']\n",
        "        .str.replace(word_separators, ' ')\n",
        "        .str.replace(word_hyphenators, '')\n",
        "        .str.lower()\n",
        "        .str.split(' ')\n",
        "        .apply(lambda s: [w.lower() for w in s])\n",
        "        .apply(remove_short_words)\n",
        "        .apply(make_unique)\n",
        "        .apply(lemmatize_words)\n",
        "        .apply(prefix_with_word)\n",
        "        .apply(pd.Series)\n",
        "        .stack()\n",
        "        .reset_index(level=1, drop=True))\n",
        "\n",
        "  words = words.reset_index().set_index(['index', 0])\n",
        "  words['present'] = 1\n",
        "  words = words.unstack(0, fill_value=0)\n",
        "  words.columns.names = ['present', 'word']\n",
        "  words.columns = words.columns.droplevel(level='present')\n",
        "\n",
        "  # Correct for words only in test set; we could not possibly know\n",
        "  # these words prior to prediction time.\n",
        "  words_train = words.reindex(train_indices)\n",
        "  words_only_in_test = (\n",
        "      words_train.columns\n",
        "          .to_series()\n",
        "          .loc[words_train.sum() == 0]\n",
        "          .index.values)\n",
        "  words = words.drop(columns=words_only_in_test)\n",
        "  return words\n",
        "\n",
        "\n",
        "def features(X_features_list, train_index, test_index):\n",
        "  X_features = pd.concat(X_features_list, axis=1).fillna(value=0)\n",
        "  return (\n",
        "      X_features.iloc[train_index],\n",
        "      X_features.iloc[test_index]\n",
        "  )\n",
        "\n",
        "\n",
        "X_distance_duration_train, X_distance_duration_test = (\n",
        "    features([\n",
        "        distance_features(X),\n",
        "        duration_features(X),\n",
        "    ],\n",
        "    X_train.index,\n",
        "    X_test.index))\n",
        "\n",
        "X_no_word_train, X_no_word_test = (\n",
        "    features([\n",
        "        type_features(X),\n",
        "        weekend_features(X),\n",
        "        time_features(X),\n",
        "        distance_features(X),\n",
        "        duration_features(X),\n",
        "    ],\n",
        "    X_train.index,\n",
        "    X_test.index))\n",
        "\n",
        "X_all_train, X_all_test = (\n",
        "    features([\n",
        "        type_features(X),\n",
        "        weekend_features(X),\n",
        "        time_features(X),\n",
        "        distance_features(X),\n",
        "        duration_features(X),\n",
        "        word_features(X, X_train.index)\n",
        "    ],\n",
        "    X_train.index,\n",
        "    X_test.index))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "b3qYPwsFqW3C",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Examine type features\n",
        "print(\n",
        "    pd.crosstab(\n",
        "        type_features(X_train).idxmax(axis=1),\n",
        "        y_train,\n",
        "        rownames=[''],\n",
        "        colnames=['commute']))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "jPpQZGF4tF05",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Examine weekend features\n",
        "\n",
        "print(\n",
        "    pd.crosstab(\n",
        "        weekend_features(X_train).idxmax(axis=1),\n",
        "        y_train,\n",
        "        rownames=[''],\n",
        "        colnames=['commute']))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "SH_HHp13qpPy",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Examine time features\n",
        "\n",
        "print(\n",
        "    pd.crosstab(\n",
        "        time_features(X_train).idxmax(axis=1),\n",
        "        y_train,\n",
        "        rownames=[''],\n",
        "        colnames=['commute']))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "TCVQ6SfbtXv2",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Examine distance features\n",
        "\n",
        "print(\n",
        "    pd.crosstab(\n",
        "        distance_features(X_train).idxmax(axis=1),\n",
        "        y_train,\n",
        "        rownames=[''],\n",
        "        colnames=['commute']))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "pnCz5IcdtXlL",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Examine duration features\n",
        "\n",
        "print(\n",
        "    pd.crosstab(\n",
        "        duration_features(X_train).idxmax(axis=1),\n",
        "        y_train,\n",
        "        rownames=[''],\n",
        "        colnames=['commute']))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "oUNoPTCrF3DQ",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Establish baseline\n",
        "\n",
        "from sklearn.dummy import DummyClassifier\n",
        "\n",
        "dummy = DummyClassifier('most_frequent')\n",
        "dummy.fit(X_all_train, y_train)\n",
        "dummy.score(X_all_train, y_train)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "uFxRiuPCG9h_",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Logistic Regression model\n",
        "\n",
        "from sklearn.linear_model import LogisticRegression\n",
        "from sklearn.model_selection import GridSearchCV\n",
        "\n",
        "\n",
        "def train_logistic_regression_model(X_features_train, y_train):\n",
        "  lr_gs = GridSearchCV(LogisticRegression(random_state=99),\n",
        "                           {'C': [0.01, 0.1, 1.0, 10, 100]},\n",
        "                           scoring='roc_auc')\n",
        "  lr_gs.fit(X_features_train, y_train)\n",
        "  return lr_gs.best_estimator_, lr_gs.best_score_"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "7_HzItNCah47",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Naive Bayes classifier\n",
        "\n",
        "from sklearn.naive_bayes import BernoulliNB\n",
        "\n",
        "\n",
        "def train_naive_bayes_model(X_features_train, y_train):\n",
        "  nb_gs = GridSearchCV(BernoulliNB(),\n",
        "                       {'alpha': [0.001, 0.01, 0.1, 1, 10]},\n",
        "                       scoring='roc_auc')\n",
        "  nb_gs.fit(X_features_train, y_train)\n",
        "  return nb_gs.best_estimator_, nb_gs.best_score_"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "PMSN_7xra-eG",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Support Vector Machine\n",
        "\n",
        "from sklearn.svm import SVC\n",
        "\n",
        "\n",
        "def train_support_vector_machine(X_features_train, y_train):\n",
        "  svm_gs = GridSearchCV(SVC(probability=True, random_state=63),\n",
        "                        {'C': [0.01, 0.1, 1, 10, 100],\n",
        "                         'gamma': [0.001, 0.01, 0.1, 1, 10]},\n",
        "                        scoring='roc_auc')\n",
        "  svm_gs.fit(X_features_train, y_train)\n",
        "  return svm_gs.best_estimator_, svm_gs.best_score_"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "nzi_YPLkRvyz",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title K-Nearest Neighbors\n",
        "\n",
        "from sklearn.neighbors import KNeighborsClassifier\n",
        "\n",
        "\n",
        "def train_knn_classifier(X_features_train, y_train):\n",
        "  knn_gs = GridSearchCV(KNeighborsClassifier(p=1, weights='uniform'),\n",
        "                        {'n_neighbors': [1, 3, 5, 7, 9, 11, 13, 15]},\n",
        "                        scoring='roc_auc')\n",
        "  knn_gs.fit(X_features_train, y_train)\n",
        "  return knn_gs.best_estimator_, knn_gs.best_score_"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "Tvcff8SISZGJ",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Decision tree\n",
        "\n",
        "from sklearn.tree import DecisionTreeClassifier\n",
        "\n",
        "\n",
        "def train_decision_tree(X_features_train, y_train):\n",
        "  tree_gs = GridSearchCV(DecisionTreeClassifier(splitter='best', random_state=112),\n",
        "                          {'min_samples_leaf': [5, 7, 9, 11, 13],\n",
        "                           'max_depth': [None, 3, 5, 7, 9, 11]},\n",
        "                          scoring='roc_auc')\n",
        "  tree_gs.fit(X_features_train, y_train)\n",
        "  return tree_gs.best_estimator_, tree_gs.best_score_"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "qvVYWLsUGiE6",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Train models\n",
        "\n",
        "from sklearn.model_selection import cross_val_score\n",
        "\n",
        "training_functions = {\n",
        "  'Logistic Regression': train_logistic_regression_model,\n",
        "  'Naive Bayes': train_naive_bayes_model,\n",
        "  'Support Vector Machine': train_support_vector_machine,\n",
        "  'K-Nearest Neighbors': train_knn_classifier,\n",
        "  'Decision Tree': train_decision_tree,\n",
        "}\n",
        "\n",
        "features_train = {\n",
        "   'distance_duration': X_distance_duration_train,\n",
        "   'no_word': X_no_word_train,\n",
        "   'all': X_all_train,\n",
        "}\n",
        "\n",
        "models = []\n",
        "\n",
        "for c, f in training_functions.items():\n",
        "  for d, x in features_train.items():\n",
        "    m_n = '%s_%s' % (c, d)\n",
        "    print('Training classifier \"%s\" with features \"%s\"...' % (c, d))\n",
        "    m, _ = f(x, y_train)\n",
        "    models.append({\n",
        "        'classifier': c,\n",
        "        'features': d,\n",
        "        'model': m,\n",
        "    })"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "hKG7TsSfXOa3",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Test models\n",
        "\n",
        "from sklearn.metrics import f1_score\n",
        "from sklearn.metrics import precision_score\n",
        "from sklearn.metrics import recall_score\n",
        "from sklearn.metrics import roc_auc_score\n",
        "\n",
        "features_test = {\n",
        "   'distance_duration': X_distance_duration_test,\n",
        "   'no_word': X_no_word_test,\n",
        "   'all': X_all_test,\n",
        "}\n",
        "\n",
        "for m in models:\n",
        "  X_features_train = features_train[m['features']]\n",
        "  X_features_test = features_test[m['features']]\n",
        "  y_score_train = m['model'].predict_proba(X_features_train)[:, 1]\n",
        "  y_pred_train = m['model'].predict(X_features_train)\n",
        "  y_pred_test = m['model'].predict(X_features_test)\n",
        "  y_score_test = m['model'].predict_proba(X_features_test)[:, 1]\n",
        "  m['AUC (training)'] = roc_auc_score(y_train, y_score_train, average='micro')\n",
        "  m['AUC (test)'] = roc_auc_score(y_test, y_score_test, average='micro')\n",
        "  m['F1 (training)'] = f1_score(y_train, y_pred_train, average='micro')\n",
        "  m['F1 (test)'] = f1_score(y_test, y_pred_test, average='micro')\n",
        "  m['Precision (training)'] = precision_score(y_train, y_pred_train, average='micro')\n",
        "  m['Precision (test)'] = precision_score(y_test, y_pred_test, average='micro')\n",
        "  m['Recall (training)'] = recall_score(y_train, y_pred_train, average='micro')\n",
        "  m['Recall (test)'] = recall_score(y_test, y_pred_test, average='micro')\n",
        "  \n",
        "models_df = pd.DataFrame.from_records(models, index=['classifier', 'features'])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "iEpCw2hSlxbW",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Plot AUC, F1, Precision, Recall\n",
        "\n",
        "from matplotlib import ticker\n",
        "\n",
        "def plot_scores(ax, models_df, features, score, title):\n",
        "  (models_df\n",
        "         .loc[(slice(None), features), score]\n",
        "         .reset_index('features')\n",
        "         .sort_index()\n",
        "         .plot(ax=ax, kind='barh', title=title, legend=True))\n",
        "  ax.set_ylabel('')\n",
        "  ax.set_xlim(0, 1)\n",
        "  ax.xaxis.set_major_locator(ticker.FixedLocator(np.linspace(0, 1, 6)))\n",
        "  \n",
        "def plot_all_scores(models_df, features):\n",
        "  fig, ((ax1, ax2),\n",
        "        (ax3, ax4)) = plt.subplots(nrows=2,\n",
        "                                 ncols=2,\n",
        "                                 sharex=True, \n",
        "                                 sharey=True,\n",
        "                                 figsize=(6, 6))\n",
        "  fig.suptitle('Feature set: %s' % features, fontsize=11, color='#444444')\n",
        "  plot_scores(ax1, models_df, features, ['AUC (test)', 'AUC (training)'], 'AUC')\n",
        "  plot_scores(ax2, models_df, features, ['F1 (test)', 'F1 (training)'], 'F1')\n",
        "  plot_scores(ax3, models_df, features, ['Precision (test)', 'Precision (training)'], 'Precision')\n",
        "  plot_scores(ax4, models_df, features, ['Recall (test)', 'Recall (training)'], 'Recall')\n",
        "\n",
        "  \n",
        "for f in features_test.keys():\n",
        "  plot_all_scores(models_df, f)\n",
        "\n",
        "fig.tight_layout()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "IxcYQehQXzZt",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Inspect coefficients from Logistic Regression model\n",
        "\n",
        "coef = pd.Series(models_df.loc[('Logistic Regression', 'no_word'), 'model'].coef_[0], index=X_no_word_train.columns)\n",
        "print('\\nPositive:')\n",
        "print(coef.loc[coef > 0].sort_values(ascending=False))\n",
        "print('\\nNegative:')\n",
        "print(coef.loc[coef < 0].sort_values(ascending=True))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "z03VlqJPS2bl",
        "colab_type": "code",
        "colab": {
          "autoexec": {
            "startup": false,
            "wait_interval": 0
          }
        },
        "cellView": "form"
      },
      "cell_type": "code",
      "source": [
        "#@title Plot ROC curves\n",
        "from sklearn.metrics import roc_auc_score\n",
        "from sklearn.metrics import roc_curve\n",
        "\n",
        "def plot_roc_curve(ax, classifier, features):\n",
        "  decision_function = (\n",
        "      models_df.loc[(classifier, features), 'model']\n",
        "               .decision_function(features_test[features]))\n",
        "  fpr, tpr, _ = roc_curve(y_test, decision_function)\n",
        "  ax.plot(fpr, tpr, color='darkorange', label='ROC curve')\n",
        "  ax.fill_between(fpr, 0, tpr, color='#FFDFBA')\n",
        "  ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')\n",
        "  ax.set_xlabel('False Positive Rate')\n",
        "  ax.set_ylabel('True Positive Rate')\n",
        "  ax.set_title('%s' % features)\n",
        "  ax.legend(loc=\"lower right\")\n",
        "  \n",
        "def plot_roc_curves_for_logistic_regression_and_svm():\n",
        "  fig, ((ax1, ax2),\n",
        "        (ax3, ax4),\n",
        "        (ax5, ax6)) = plt.subplots(nrows=3,\n",
        "                                 ncols=2,\n",
        "                                 sharex=True, \n",
        "                                 sharey=True,\n",
        "                                 figsize=(6, 9))\n",
        "\n",
        "  plot_roc_curve(ax1, 'Logistic Regression', 'distance_duration')\n",
        "  plot_roc_curve(ax2, 'Support Vector Machine', 'distance_duration')\n",
        "  plot_roc_curve(ax3, 'Logistic Regression', 'no_word')\n",
        "  plot_roc_curve(ax4, 'Support Vector Machine', 'no_word')\n",
        "  plot_roc_curve(ax5, 'Logistic Regression', 'all')\n",
        "  plot_roc_curve(ax6, 'Support Vector Machine', 'all')\n",
        "\n",
        "  fig.tight_layout()\n",
        "\n",
        "plot_roc_curves_for_logistic_regression_and_svm()"
      ],
      "execution_count": 0,
      "outputs": []
    }
  ]
}