|
@@ -0,0 +1,619 @@
|
|
|
+{
|
|
|
+ "cells": [
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "# Purpose\n",
|
|
|
+ "\n",
|
|
|
+ "Weighter is designed to be a hacky weight tracking app using Slack as a frontend and Google Sheets as a database! \n",
|
|
|
+ "Weights are entered through a Slack Channel, stored in a Google Sheet, and reported back to users through Slack. Users will have the option to view various stats and graphs by sending different slack messages. \n",
|
|
|
+ "\n",
|
|
|
+ "Weighter also features additive modeling forecasts using the Facebook Prophet library. "
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "## Setup Libraries and Access to the Google Sheet"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 125,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": false
|
|
|
+ },
|
|
|
+ "outputs": [],
|
|
|
+ "source": [
|
|
|
+ "# pandas and numpy for data manipulation\n",
|
|
|
+ "import pandas as pd\n",
|
|
|
+ "import numpy as np\n",
|
|
|
+ "\n",
|
|
|
+ "# fbprophet for additive models\n",
|
|
|
+ "import fbprophet\n",
|
|
|
+ "\n",
|
|
|
+ "# gspread for Google Sheets access\n",
|
|
|
+ "import gspread\n",
|
|
|
+ "\n",
|
|
|
+ "# slacker for interacting with Slack\n",
|
|
|
+ "from slacker import Slacker\n",
|
|
|
+ "\n",
|
|
|
+ "# oauth2client for authorizing access to Google Sheets\n",
|
|
|
+ "from oauth2client.service_account import ServiceAccountCredentials"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 126,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": true
|
|
|
+ },
|
|
|
+ "outputs": [],
|
|
|
+ "source": [
|
|
|
+ "# matplotlib for plotting in the notebook\n",
|
|
|
+ "import matplotlib.pyplot as plt\n",
|
|
|
+ "%matplotlib inline\n",
|
|
|
+ "\n",
|
|
|
+ "import matplotlib"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "### Google Sheet Access\n",
|
|
|
+ "\n",
|
|
|
+ "The json file is the credentials for accessing the google sheet generated from the Google Developers API. To access a specific sheet, you need to share the sheet with the email address in the json file. "
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 127,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": false
|
|
|
+ },
|
|
|
+ "outputs": [
|
|
|
+ {
|
|
|
+ "name": "stderr",
|
|
|
+ "output_type": "stream",
|
|
|
+ "text": [
|
|
|
+ "INFO:oauth2client.client:Refreshing access_token\n"
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "source": [
|
|
|
+ "# google sheets access\n",
|
|
|
+ "scope = ['https://spreadsheets.google.com/feeds']\n",
|
|
|
+ "\n",
|
|
|
+ "# Use local stored credentials in json file\n",
|
|
|
+ "# make sure to first share the sheet with the email in the json file\n",
|
|
|
+ "credentials = ServiceAccountCredentials.from_json_keyfile_name('weighter-2038ffb4e5a6.json', scope)\n",
|
|
|
+ "\n",
|
|
|
+ "# Authorize access\n",
|
|
|
+ "gc = gspread.authorize(credentials);"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "### Open the sheet and convert to a pandas dataframe"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 129,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": true
|
|
|
+ },
|
|
|
+ "outputs": [],
|
|
|
+ "source": [
|
|
|
+ "# Open the sheet, need to share the sheet with email specified in json file\n",
|
|
|
+ "gsheet = gc.open('Auto Weight Challenge').sheet1\n",
|
|
|
+ "\n",
|
|
|
+ "# List of lists with each row in the sheet as a list\n",
|
|
|
+ "weight_lists = gsheet.get_all_values()\n",
|
|
|
+ "\n",
|
|
|
+ "# Headers are the first list\n",
|
|
|
+ "# Pop returns the element (list in this case) and removes it from the list\n",
|
|
|
+ "headers = weight_lists.pop(0)\n",
|
|
|
+ "\n",
|
|
|
+ "# Convert list of lists to a dataframe with specified column header\n",
|
|
|
+ "weights = pd.DataFrame(weight_lists, columns=headers)\n",
|
|
|
+ "\n",
|
|
|
+ "# Record column should be a boolean\n",
|
|
|
+ "weights['Record'] = weights['Record'].astype(bool)\n",
|
|
|
+ "\n",
|
|
|
+ "# Name column is a string\n",
|
|
|
+ "weights['Name'] = weights['Name'].astype(str)\n",
|
|
|
+ "\n",
|
|
|
+ "# Convert dates to datetime, then set as index, then set the time zone\n",
|
|
|
+ "weights['Date'] = pd.to_datetime(weights['Date'], unit='s')\n",
|
|
|
+ "weights = weights.set_index('Date', drop = True).tz_localize(tz='US/Eastern')"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 130,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": false
|
|
|
+ },
|
|
|
+ "outputs": [
|
|
|
+ {
|
|
|
+ "data": {
|
|
|
+ "text/html": [
|
|
|
+ "<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>Name</th>\n",
|
|
|
+ " <th>Entry</th>\n",
|
|
|
+ " <th>Record</th>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>Date</th>\n",
|
|
|
+ " <th></th>\n",
|
|
|
+ " <th></th>\n",
|
|
|
+ " <th></th>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " </thead>\n",
|
|
|
+ " <tbody>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2017-08-18 00:00:00-04:00</th>\n",
|
|
|
+ " <td>koehrcl</td>\n",
|
|
|
+ " <td>235.2</td>\n",
|
|
|
+ " <td>True</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2017-08-19 00:00:00-04:00</th>\n",
|
|
|
+ " <td>koehrcl</td>\n",
|
|
|
+ " <td>235.6</td>\n",
|
|
|
+ " <td>True</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2017-08-20 00:00:00-04:00</th>\n",
|
|
|
+ " <td>koehrcl</td>\n",
|
|
|
+ " <td>233</td>\n",
|
|
|
+ " <td>True</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2017-08-21 00:00:00-04:00</th>\n",
|
|
|
+ " <td>koehrcl</td>\n",
|
|
|
+ " <td>232.6</td>\n",
|
|
|
+ " <td>True</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2017-08-22 00:00:00-04:00</th>\n",
|
|
|
+ " <td>koehrcl</td>\n",
|
|
|
+ " <td>234.4</td>\n",
|
|
|
+ " <td>True</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " </tbody>\n",
|
|
|
+ "</table>\n",
|
|
|
+ "</div>"
|
|
|
+ ],
|
|
|
+ "text/plain": [
|
|
|
+ " Name Entry Record\n",
|
|
|
+ "Date \n",
|
|
|
+ "2017-08-18 00:00:00-04:00 koehrcl 235.2 True\n",
|
|
|
+ "2017-08-19 00:00:00-04:00 koehrcl 235.6 True\n",
|
|
|
+ "2017-08-20 00:00:00-04:00 koehrcl 233 True\n",
|
|
|
+ "2017-08-21 00:00:00-04:00 koehrcl 232.6 True\n",
|
|
|
+ "2017-08-22 00:00:00-04:00 koehrcl 234.4 True"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ "execution_count": 130,
|
|
|
+ "metadata": {},
|
|
|
+ "output_type": "execute_result"
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "source": [
|
|
|
+ "weights.head()"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 131,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": false
|
|
|
+ },
|
|
|
+ "outputs": [
|
|
|
+ {
|
|
|
+ "data": {
|
|
|
+ "text/html": [
|
|
|
+ "<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>Name</th>\n",
|
|
|
+ " <th>Entry</th>\n",
|
|
|
+ " <th>Record</th>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>Date</th>\n",
|
|
|
+ " <th></th>\n",
|
|
|
+ " <th></th>\n",
|
|
|
+ " <th></th>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " </thead>\n",
|
|
|
+ " <tbody>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2018-01-19 14:34:56-05:00</th>\n",
|
|
|
+ " <td>koehrcl</td>\n",
|
|
|
+ " <td>221.1</td>\n",
|
|
|
+ " <td>False</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2018-01-19 15:30:25-05:00</th>\n",
|
|
|
+ " <td>willkoehrsen</td>\n",
|
|
|
+ " <td>137.3</td>\n",
|
|
|
+ " <td>False</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2018-01-19 18:11:49-05:00</th>\n",
|
|
|
+ " <td>fletcher</td>\n",
|
|
|
+ " <td>188.4</td>\n",
|
|
|
+ " <td>False</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2018-01-20 15:39:12-05:00</th>\n",
|
|
|
+ " <td>willkoehrsen</td>\n",
|
|
|
+ " <td>137</td>\n",
|
|
|
+ " <td>False</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " <tr>\n",
|
|
|
+ " <th>2018-01-20 15:49:52-05:00</th>\n",
|
|
|
+ " <td>koehrcl</td>\n",
|
|
|
+ " <td>220.4</td>\n",
|
|
|
+ " <td>False</td>\n",
|
|
|
+ " </tr>\n",
|
|
|
+ " </tbody>\n",
|
|
|
+ "</table>\n",
|
|
|
+ "</div>"
|
|
|
+ ],
|
|
|
+ "text/plain": [
|
|
|
+ " Name Entry Record\n",
|
|
|
+ "Date \n",
|
|
|
+ "2018-01-19 14:34:56-05:00 koehrcl 221.1 False\n",
|
|
|
+ "2018-01-19 15:30:25-05:00 willkoehrsen 137.3 False\n",
|
|
|
+ "2018-01-19 18:11:49-05:00 fletcher 188.4 False\n",
|
|
|
+ "2018-01-20 15:39:12-05:00 willkoehrsen 137 False\n",
|
|
|
+ "2018-01-20 15:49:52-05:00 koehrcl 220.4 False"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ "execution_count": 131,
|
|
|
+ "metadata": {},
|
|
|
+ "output_type": "execute_result"
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "source": [
|
|
|
+ "weights.tail()"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "+ Date is the index (in Eastern time here)\n",
|
|
|
+ "+ Name is the slack username\n",
|
|
|
+ "+ Entry is either weight or a string to display results\n",
|
|
|
+ "+ Record is whether or not the entry has been processed by weighter"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "markdown",
|
|
|
+ "metadata": {},
|
|
|
+ "source": [
|
|
|
+ "# Weighter Class\n",
|
|
|
+ "\n",
|
|
|
+ "The class will include a number of different methods for analyzing the data and graphing results. These results can then be sent back to Slack depending on the message entered by the user."
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 136,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": false
|
|
|
+ },
|
|
|
+ "outputs": [],
|
|
|
+ "source": [
|
|
|
+ "class Weighter():\n",
|
|
|
+ " \n",
|
|
|
+ " \"\"\"\n",
|
|
|
+ " When weighter is initialized, we need to convert the usernames,\n",
|
|
|
+ " get a dictionary of the unrecorded entries, construct a dictionary\n",
|
|
|
+ " of the actions to take, and make sure all data is formatted correctly\n",
|
|
|
+ " \"\"\"\n",
|
|
|
+ " \n",
|
|
|
+ " def __init__(self, weights):\n",
|
|
|
+ " \n",
|
|
|
+ " # Weights is a dataframe\n",
|
|
|
+ " self.weights = weights.copy()\n",
|
|
|
+ " \n",
|
|
|
+ " # Users is a list of the unique users in the data\n",
|
|
|
+ " self.users = list(set(self.weights['Name']))\n",
|
|
|
+ " \n",
|
|
|
+ " correct_names = []\n",
|
|
|
+ " # Name changes\n",
|
|
|
+ " for user in self.weights['Name']:\n",
|
|
|
+ " \n",
|
|
|
+ " # Have to hardcode in name changes\n",
|
|
|
+ " if user == 'koehrcl':\n",
|
|
|
+ " correct_names.append('Craig')\n",
|
|
|
+ " elif user == 'willkoehrsen':\n",
|
|
|
+ " correct_names.append('Will')\n",
|
|
|
+ " elif user == 'fletcher':\n",
|
|
|
+ " correct_names.append('Fletcher')\n",
|
|
|
+ " \n",
|
|
|
+ " # Currently do not handle new users\n",
|
|
|
+ " else:\n",
|
|
|
+ " print('New User Detected')\n",
|
|
|
+ " return\n",
|
|
|
+ " \n",
|
|
|
+ " self.weights['Name'] = correct_names\n",
|
|
|
+ " \n",
|
|
|
+ " # Users is a list of the unique users in the data\n",
|
|
|
+ " self.users = list(set(self.weights['Name']))\n",
|
|
|
+ " \n",
|
|
|
+ " # Create a dataframe of the unrecorded entries\n",
|
|
|
+ " self.unrecorded = self.weights[self.weights['Record'] != True]\n",
|
|
|
+ " \n",
|
|
|
+ " # Process the unrecorded entries\n",
|
|
|
+ " self.process_unrecorded()\n",
|
|
|
+ " \n",
|
|
|
+ " # The remaning entries will all be weights\n",
|
|
|
+ " self.weights['Entry'] = [float(weight) for weight in self.weights['Entry']]\n",
|
|
|
+ " \n",
|
|
|
+ " # Build the user dictionary\n",
|
|
|
+ " self.build_user_dict()\n",
|
|
|
+ " \n",
|
|
|
+ " \n",
|
|
|
+ " \"\"\" \n",
|
|
|
+ " Constructs a dictionary for each user with critical information\n",
|
|
|
+ " This forms the basis for the summarize function\n",
|
|
|
+ " \"\"\"\n",
|
|
|
+ " \n",
|
|
|
+ " def build_user_dict(self):\n",
|
|
|
+ " \n",
|
|
|
+ " user_dict = {}\n",
|
|
|
+ " \n",
|
|
|
+ " user_goals = {'Craig': 215.0, 'Fletcher': 200.0, 'Will': 155.0}\n",
|
|
|
+ " \n",
|
|
|
+ " for i, user in enumerate(self.users):\n",
|
|
|
+ " \n",
|
|
|
+ " user_weights = self.weights[self.weights['Name'] == user]\n",
|
|
|
+ " goal = user_goals.get(user)\n",
|
|
|
+ "\n",
|
|
|
+ " start_weight = user_weights.ix[min(user_weights.index), 'Entry'] \n",
|
|
|
+ " \n",
|
|
|
+ " # Find minimum weight and date on which it occurs\n",
|
|
|
+ " min_weight = min(user_weights['Entry'])\n",
|
|
|
+ " min_weight_date = ((user_weights[user_weights['Entry'] == min_weight].index)[0])\n",
|
|
|
+ " \n",
|
|
|
+ " # Find maximum weight and date on which it occurs\n",
|
|
|
+ " max_weight = max(user_weights['Entry'])\n",
|
|
|
+ " max_weight_date = ((user_weights[user_weights['Entry'] == max_weight].index)[0])\n",
|
|
|
+ " \n",
|
|
|
+ " most_recent_weight = user_weights.ix[max(user_weights.index), 'Entry']\n",
|
|
|
+ " \n",
|
|
|
+ " if goal < start_weight:\n",
|
|
|
+ " change = start_weight - most_recent_weight\n",
|
|
|
+ " obj = 'lose'\n",
|
|
|
+ " elif goal > start_weight:\n",
|
|
|
+ " change = most_recent_weight - start_weight\n",
|
|
|
+ " obj = 'gain'\n",
|
|
|
+ " \n",
|
|
|
+ " pct_change = 100 *change / start_weight\n",
|
|
|
+ " \n",
|
|
|
+ " pct_to_goal = 100 * (change / abs(start_weight - goal) )\n",
|
|
|
+ " \n",
|
|
|
+ " user_dict[user] = {'min_weight': min_weight, 'max_weight': max_weight,\n",
|
|
|
+ " 'min_date': min_weight_date, 'max_date': max_weight_date,\n",
|
|
|
+ " 'recent': most_recent_weight, 'abs_change': change,\n",
|
|
|
+ " 'pct_change': pct_change, 'pct_towards_goal': pct_to_goal,\n",
|
|
|
+ " 'objective': obj}\n",
|
|
|
+ " \n",
|
|
|
+ " self.user_dict = user_dict\n",
|
|
|
+ " \n",
|
|
|
+ " def process_unrecorded(self):\n",
|
|
|
+ " \n",
|
|
|
+ " entries = {name:[] for name in self.users}\n",
|
|
|
+ " drop = []\n",
|
|
|
+ " \n",
|
|
|
+ " for index in self.unrecorded.index:\n",
|
|
|
+ "\n",
|
|
|
+ " entry = self.unrecorded.ix[index, 'Entry']\n",
|
|
|
+ " user = str(self.unrecorded.ix[index, 'Name'])\n",
|
|
|
+ " \n",
|
|
|
+ " try:\n",
|
|
|
+ " entry = float(entry)\n",
|
|
|
+ " entries[user].append(entry)\n",
|
|
|
+ " \n",
|
|
|
+ " except: \n",
|
|
|
+ " entry = str(entry)\n",
|
|
|
+ " entries[user].append(entry)\n",
|
|
|
+ " \n",
|
|
|
+ " drop.append(index)\n",
|
|
|
+ "\n",
|
|
|
+ " \n",
|
|
|
+ "# if entry == 'Summary':\n",
|
|
|
+ "# self.summarize()\n",
|
|
|
+ "# elif entry == 'Future':\n",
|
|
|
+ "# self.future()\n",
|
|
|
+ "# elif entry == 'Trends':\n",
|
|
|
+ "# self.trends()\n",
|
|
|
+ "# elif entry == 'Percent':\n",
|
|
|
+ "# self.percent()\n",
|
|
|
+ "# elif entry == 'Change':\n",
|
|
|
+ "# self.changepoints()\n",
|
|
|
+ "\n",
|
|
|
+ " # Drop the rows which do not contain a weight\n",
|
|
|
+ " self.weights.drop(drop, axis=0, inplace=True)\n",
|
|
|
+ " \n",
|
|
|
+ " # Entries is all of the new entries\n",
|
|
|
+ " self.entries = entries\n",
|
|
|
+ " \n",
|
|
|
+ " # This will be automatically called for each new entry\n",
|
|
|
+ " def basic_message(self):\n",
|
|
|
+ " \n",
|
|
|
+ " for user in self.users:\n",
|
|
|
+ " if user in self.entries.keys():\n",
|
|
|
+ " user_entries = self.entries.get(user)\n",
|
|
|
+ " user_info = self.user_dict.get(user)\n",
|
|
|
+ " \n",
|
|
|
+ " for entry in user_entries:\n",
|
|
|
+ " \n",
|
|
|
+ " if type(entry) == float:\n",
|
|
|
+ " \n",
|
|
|
+ " if user_info['objective'] == 'lose':\n",
|
|
|
+ " weight_change = user_info['max_weight'] - entry\n",
|
|
|
+ " pct_change = 100 * weight_change / user_info['max_weight']\n",
|
|
|
+ " \n",
|
|
|
+ " elif user_info['objective'] == 'gain':\n",
|
|
|
+ " weight_change = entry - user_info['min_weight']\n",
|
|
|
+ " pct_change = 100 * weight_change / user_info['min_weight']\n",
|
|
|
+ " \n",
|
|
|
+ " print('\\nUser: {}'.format(user))\n",
|
|
|
+ " print('Total Weight Change = {:.2f} lbs.'.format(weight_change))\n",
|
|
|
+ " print('Percentage Weight Change = %{:.2f}.'.format(pct_change))\n",
|
|
|
+ " \n",
|
|
|
+ " # Display comprehensive stats about the users progress\n",
|
|
|
+ " def summary(self, user):\n",
|
|
|
+ " user_info = self.user_dict.get(user)\n",
|
|
|
+ " print(user_info)"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 133,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": false,
|
|
|
+ "scrolled": false
|
|
|
+ },
|
|
|
+ "outputs": [],
|
|
|
+ "source": [
|
|
|
+ "weighter = Weighter(weights)"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 134,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": false
|
|
|
+ },
|
|
|
+ "outputs": [
|
|
|
+ {
|
|
|
+ "name": "stdout",
|
|
|
+ "output_type": "stream",
|
|
|
+ "text": [
|
|
|
+ "\n",
|
|
|
+ "User: Will\n",
|
|
|
+ "Total Weight Change = 14.20 lbs.\n",
|
|
|
+ "Percentage Weight Change = %11.49.\n",
|
|
|
+ "\n",
|
|
|
+ "User: Will\n",
|
|
|
+ "Total Weight Change = 13.70 lbs.\n",
|
|
|
+ "Percentage Weight Change = %11.08.\n",
|
|
|
+ "\n",
|
|
|
+ "User: Will\n",
|
|
|
+ "Total Weight Change = 13.40 lbs.\n",
|
|
|
+ "Percentage Weight Change = %10.84.\n",
|
|
|
+ "\n",
|
|
|
+ "User: Fletcher\n",
|
|
|
+ "Total Weight Change = 4.00 lbs.\n",
|
|
|
+ "Percentage Weight Change = %2.17.\n",
|
|
|
+ "\n",
|
|
|
+ "User: Fletcher\n",
|
|
|
+ "Total Weight Change = 3.80 lbs.\n",
|
|
|
+ "Percentage Weight Change = %2.06.\n",
|
|
|
+ "\n",
|
|
|
+ "User: Craig\n",
|
|
|
+ "Total Weight Change = 15.40 lbs.\n",
|
|
|
+ "Percentage Weight Change = %6.54.\n",
|
|
|
+ "\n",
|
|
|
+ "User: Craig\n",
|
|
|
+ "Total Weight Change = 14.50 lbs.\n",
|
|
|
+ "Percentage Weight Change = %6.15.\n",
|
|
|
+ "\n",
|
|
|
+ "User: Craig\n",
|
|
|
+ "Total Weight Change = 15.20 lbs.\n",
|
|
|
+ "Percentage Weight Change = %6.45.\n"
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "source": [
|
|
|
+ "weighter.basic_message()"
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "cell_type": "code",
|
|
|
+ "execution_count": 135,
|
|
|
+ "metadata": {
|
|
|
+ "collapsed": false
|
|
|
+ },
|
|
|
+ "outputs": [
|
|
|
+ {
|
|
|
+ "name": "stdout",
|
|
|
+ "output_type": "stream",
|
|
|
+ "text": [
|
|
|
+ "{'min_weight': 219.6, 'max_weight': 235.6, 'min_date': Timestamp('2017-12-27 15:14:17-0500', tz='US/Eastern'), 'max_date': Timestamp('2017-08-19 00:00:00-0400', tz='US/Eastern'), 'recent': 220.40000000000001, 'abs_change': 14.799999999999983, 'pct_change': 6.2925170068027141, 'pct_towards_goal': 73.267326732673226, 'objective': 'lose'}\n"
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "source": [
|
|
|
+ "weighter.summary('Craig')"
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "metadata": {
|
|
|
+ "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.6.0"
|
|
|
+ }
|
|
|
+ },
|
|
|
+ "nbformat": 4,
|
|
|
+ "nbformat_minor": 2
|
|
|
+}
|