├── README.md ├── excel ├── Cable_models.xlsx └── Powerball analysis.xlsx ├── .gitignore ├── LICENSE └── notebooks ├── Discovering the Dirichlet.ipynb ├── Bayesian updating of probability distributions.ipynb ├── .ipynb_checkpoints └── Bayesian Non-Parametrics-checkpoint.ipynb ├── PuLP Shopping with a Data Scientist.ipynb ├── Bayesian likelihood of normal distribution.ipynb ├── Bayesian Non-Parametrics.ipynb ├── Predicting Divvy bike availability with MRJob and Python.ipynb └── Slow testing with no control.ipynb /README.md: -------------------------------------------------------------------------------- 1 | # research 2 | A collection of IPython Notebooks containing my research. 3 | -------------------------------------------------------------------------------- /excel/Cable_models.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcbozonier/research/HEAD/excel/Cable_models.xlsx -------------------------------------------------------------------------------- /excel/Powerball analysis.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcbozonier/research/HEAD/excel/Powerball analysis.xlsx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | .DS_Store 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Justin Bozonier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /notebooks/Discovering the Dirichlet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 46, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import scipy.stats as ss\n", 12 | "import numpy as np" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 197, 18 | "metadata": { 19 | "collapsed": true 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "hypotheses = [[.8, .2], [.5,.5], [.2,.8]]\n", 24 | "pdf_score = np.array([ss.beta.pdf(hypothesis[0], 1+1,1+5) for hypothesis in hypotheses])\n", 25 | "probabilities = pdf_score/pdf_score.sum()\n", 26 | "\n", 27 | "list(zip(hypotheses, probabilities))" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 199, 33 | "metadata": { 34 | "collapsed": false 35 | }, 36 | "outputs": [ 37 | { 38 | "data": { 39 | "text/plain": [ 40 | "[([0.8, 0.2], 0.00071554864238494969),\n", 41 | " ([0.5, 0.5], 0.26656264155542536),\n", 42 | " ([0.2, 0.8], 0.73272180980218971)]" 43 | ] 44 | }, 45 | "execution_count": 199, 46 | "metadata": {}, 47 | "output_type": "execute_result" 48 | } 49 | ], 50 | "source": [ 51 | "hypotheses = [[.8, .2], [.5,.5], [.2,.8]]\n", 52 | "pdf_score = np.array([ss.beta.pdf(hypothesis[0], 1+1+2,1+5+3) for hypothesis in hypotheses])\n", 53 | "probabilities = pdf_score/pdf_score.sum()\n", 54 | "\n", 55 | "list(zip(hypotheses, probabilities))" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 123, 61 | "metadata": { 62 | "collapsed": false 63 | }, 64 | "outputs": [ 65 | { 66 | "data": { 67 | "text/plain": [ 68 | "array([ 0.00314431, 0.19191324, 0.80494246])" 69 | ] 70 | }, 71 | "execution_count": 123, 72 | "metadata": {}, 73 | "output_type": "execute_result" 74 | } 75 | ], 76 | "source": [ 77 | "pdf_array = np.array([ss.beta.pdf(0.8, 2, 6), ss.beta.pdf(0.5, 2, 6), ss.beta.pdf(0.2, 2, 6)])\n", 78 | "pdf_array/pdf_array.sum()" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 51, 84 | "metadata": { 85 | "collapsed": true 86 | }, 87 | "outputs": [], 88 | "source": [] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 202, 93 | "metadata": { 94 | "collapsed": false 95 | }, 96 | "outputs": [ 97 | { 98 | "name": "stdout", 99 | "output_type": "stream", 100 | "text": [ 101 | "[([0.8, 0.2], 0.00071554864238495164), ([0.5, 0.5], 0.26656264155542514), ([0.2, 0.8], 0.73272180980218982)]\n" 102 | ] 103 | } 104 | ], 105 | "source": [ 106 | "hypotheses = [[.8, .2], \n", 107 | " [.5,.5], \n", 108 | " [.2,.8]]\n", 109 | "# Notice how we swapped out the Beta for\n", 110 | "# a Dirichlet. The only difference is we\n", 111 | "# now pass a list of counts to the pdf\n", 112 | "# function. We'll get to why in a bit.\n", 113 | "pdf_score = np.array([ss.dirichlet.pdf(hypothesis, [1+1+2,1+5+3]) for hypothesis in hypotheses])\n", 114 | "probabilities = pdf_score/pdf_score.sum()\n", 115 | "\n", 116 | "print(list(zip(hypotheses, probabilities)))" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 191, 122 | "metadata": { 123 | "collapsed": false 124 | }, 125 | "outputs": [], 126 | "source": [ 127 | "hypotheses = [[1/3,1/3,1/3], [.8, .1, .1], [.1,.8,.1],[.1,.1,.8]]\n", 128 | "pdf_score = np.array([ss.dirichlet.pdf(hypothesis, [1+1, 1+5, 1+2]) for hypothesis in hypotheses])\n", 129 | "probabilities = pdf_score/pdf_score.sum()" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 192, 135 | "metadata": { 136 | "collapsed": false 137 | }, 138 | "outputs": [ 139 | { 140 | "data": { 141 | "text/plain": [ 142 | "[([0.3333333333333333, 0.3333333333333333, 0.3333333333333333],\n", 143 | " 0.31699414486454858),\n", 144 | " ([0.8, 0.1, 0.1], 0.00016638388675650501),\n", 145 | " ([0.1, 0.8, 0.1], 0.68150840015464287),\n", 146 | " ([0.1, 0.1, 0.8], 0.0013310710940520379)]" 147 | ] 148 | }, 149 | "execution_count": 192, 150 | "metadata": {}, 151 | "output_type": "execute_result" 152 | } 153 | ], 154 | "source": [ 155 | "list(zip(hypotheses, probabilities))" 156 | ] 157 | }, 158 | { 159 | "cell_type": "code", 160 | "execution_count": 203, 161 | "metadata": { 162 | "collapsed": false 163 | }, 164 | "outputs": [ 165 | { 166 | "data": { 167 | "text/plain": [ 168 | "[([0.3333333333333333, 0.3333333333333333, 0.3333333333333333],\n", 169 | " 0.31699414486454858),\n", 170 | " ([0.8, 0.1, 0.1], 0.00016638388675650501),\n", 171 | " ([0.1, 0.8, 0.1], 0.68150840015464287),\n", 172 | " ([0.1, 0.1, 0.8], 0.0013310710940520379)]" 173 | ] 174 | }, 175 | "execution_count": 203, 176 | "metadata": {}, 177 | "output_type": "execute_result" 178 | } 179 | ], 180 | "source": [ 181 | "hypotheses = [[1/3,1/3,1/3], [.8, .1, .1], [.1,.8,.1],[.1,.1,.8]]\n", 182 | "pdf_score = np.array([ss.dirichlet.pdf(hypothesis, [1+1, 1+5, 1+2]) for hypothesis in hypotheses])\n", 183 | "probabilities = pdf_score/pdf_score.sum()\n", 184 | "list(zip(hypotheses, probabilities))" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": 204, 190 | "metadata": { 191 | "collapsed": false 192 | }, 193 | "outputs": [ 194 | { 195 | "data": { 196 | "text/plain": [ 197 | "[([0.3333333333333333, 0.3333333333333333, 0.3333333333333333],\n", 198 | " 0.85202998933671381),\n", 199 | " ([0.8, 0.1, 0.1], 7.042002669045256e-08),\n", 200 | " ([0.1, 0.8, 0.1], 0.14768149981393544),\n", 201 | " ([0.1, 0.1, 0.8], 0.00028844042932409396)]" 202 | ] 203 | }, 204 | "execution_count": 204, 205 | "metadata": {}, 206 | "output_type": "execute_result" 207 | } 208 | ], 209 | "source": [ 210 | "hypotheses = [[1/3,1/3,1/3], [.8, .1, .1], [.1,.8,.1],[.1,.1,.8]]\n", 211 | "pdf_score = np.array([ss.dirichlet.pdf(hypothesis, [1+1+1, 1+5+4, 1+2+4]) for hypothesis in hypotheses])\n", 212 | "probabilities = pdf_score/pdf_score.sum()\n", 213 | "list(zip(hypotheses, probabilities))" 214 | ] 215 | } 216 | ], 217 | "metadata": { 218 | "kernelspec": { 219 | "display_name": "Python 3", 220 | "language": "python", 221 | "name": "python3" 222 | }, 223 | "language_info": { 224 | "codemirror_mode": { 225 | "name": "ipython", 226 | "version": 3 227 | }, 228 | "file_extension": ".py", 229 | "mimetype": "text/x-python", 230 | "name": "python", 231 | "nbconvert_exporter": "python", 232 | "pygments_lexer": "ipython3", 233 | "version": "3.4.4" 234 | } 235 | }, 236 | "nbformat": 4, 237 | "nbformat_minor": 0 238 | } 239 | -------------------------------------------------------------------------------- /notebooks/Bayesian updating of probability distributions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "" 4 | }, 5 | "nbformat": 3, 6 | "nbformat_minor": 0, 7 | "worksheets": [ 8 | { 9 | "cells": [ 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "As I\u2019ve started learning more about Bayesian statistical methods the biggest light bulb moment for me was when I started thinking in terms of distributions. I was raised on the notion of single number answers. Because of that, I thought I had mastered Bayesian methods when I could recite P(A|B) = P(B|A) P(A) / P(B). WRONG.\n", 15 | "\n", 16 | "Today\u2019s algorithm will show a simple example of how to deal with probabilities as distributions. There are two key concepts: updating our distribution with data, and normalizing the distribution.\n", 17 | "\n", 18 | "First allow me to introduce today\u2019s concrete example: Flipping a coin!\n", 19 | "\n", 20 | "## Chance of Heads\n", 21 | "\n", 22 | "If you flip a coin and it comes up tails 3 times out of 4 how likely is it your coin is actually a fair coin? Let\u2019s say fair means 45-55% of the time it comes up heads.\n", 23 | "\n", 24 | "The chance it could come up heads could be anything between 0%-100% right? 0% would mean it\u2019s completely unfair and somehow NEVER comes up heads. 100% would be precisely the opposite. We\u2019re hoping for somewhere in the middle.\n", 25 | "\n", 26 | "Now before we\u2019ve flipped the coin we need to decide what the probabilities of our hypotheses between 0% and 100% are. To keep it simple we\u2019re going to assume that each hypothesis is equally likely. This is known as a \u201cuniform distribution.\u201d In python that looks like this:" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "collapsed": false, 32 | "input": [ 33 | "possibilities = [1.0]*101" 34 | ], 35 | "language": "python", 36 | "metadata": {}, 37 | "outputs": [], 38 | "prompt_number": 1 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "Now we need to normalize this so it represents probabilities. For that, I wrote this function:" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "collapsed": false, 50 | "input": [ 51 | "def normalize(possibilities):\n", 52 | " possibility_sum = sum(possibilities)\n", 53 | " for hypothesis in xrange(0,101):\n", 54 | " possibility = possibilities[hypothesis]\n", 55 | " possibilities[hypothesis] = possibility/possibility_sum" 56 | ], 57 | "language": "python", 58 | "metadata": {}, 59 | "outputs": [], 60 | "prompt_number": 2 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "Basically this just makes sure all of the likelihoods for our hypotheses add up to 100% like good little probabilities.\n", 67 | "\n", 68 | "This is what this uniform distribution looks like visually:
\n", 69 | "" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "## Updating with new information\n", 77 | "So now we flip our coin and let\u2019s say we get heads. Let\u2019s think through what we expect to happen to that uniform distribution above. Starting from 0% and working right\u2026\n", 78 | "\n", 79 | "0% Now that we\u2019ve gotten heads once we know that it\u2019s impossible to never get heads. This goes to zero.\n", 80 | "\n", 81 | "1% The chances seem very low that if there\u2019s only a 1% chance of getting heads that it\u2019d be the first we\u2019d run into.\n", 82 | "\n", 83 | "50% Completely possible. We\u2019ve only flipped once so we can\u2019t have 50% probability. This should be somewhere in the middle.\n", 84 | "\n", 85 | "100% Well, we\u2019ve only flipped heads once so we can\u2019t say this is certain but we also can\u2019t rule this out.\n", 86 | "\n", 87 | "So how do we update our data in python?\n", 88 | "\n", 89 | "We use this function when we flip heads:" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "collapsed": false, 95 | "input": [ 96 | "def update_success(possibilities):\n", 97 | " for hypothesis in xrange(0, 101):\n", 98 | " likelihood = possibilities[hypothesis]\n", 99 | " possibilities[hypothesis] = likelihood * hypothesis/100.0\n", 100 | " normalize(possibilities)" 101 | ], 102 | "language": "python", 103 | "metadata": {}, 104 | "outputs": [] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "And this function when we flip tails:" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "collapsed": false, 116 | "input": [ 117 | "def update_failure(possibilities):\n", 118 | " for hypothesis in xrange(0, 101):\n", 119 | " likelihood = possibilities[hypothesis]\n", 120 | " possibilities[hypothesis] = likelihood*(1.0-hypothesis/100.0)\n", 121 | " normalize(possibilities)" 122 | ], 123 | "language": "python", 124 | "metadata": {}, 125 | "outputs": [] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "They are different in one subtle way. Look closely at the fourth line of each function.\n", 132 | "\n", 133 | "On the success function we are multiplying the likelihood by each hypothesized probability of us getting heads. Once we\u2019ve done that for every hypothesis we normalize to get back to a percentage. The failure function multiplies the likelihood of each hypothesis by the probability of the hypothesis being false (according to the hypothesis).\n", 134 | "\n", 135 | "This leaves us with the following histogram of likelihoods for each hypothesis: " 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "That\u2019s all of the moving parts. Let\u2019s walk through some more examples so you can convince yourself this works.\n", 150 | "\n", 151 | "## Baby stepping through our scenario\n", 152 | "Next let\u2019s say we flip tails and think through what we expect to see. We\u2019ve already ruled out 0% chance of seeing heads and now we can rule out 100% chance of seeing heads. We do now have one of each though, so I expect the middle of the distribution to swell a bit. Whether it\u2019s a triangle shape or a curve, I\u2019m not sure but something like that.\n", 153 | "\n", 154 | "Here\u2019s that distribution: " 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "" 162 | ] 163 | }, 164 | { 165 | "cell_type": "markdown", 166 | "metadata": {}, 167 | "source": [ 168 | "It\u2019s curved! Of course! Well\u2026 It\u2019s not really obvious, but it does seem rational at least.\n", 169 | "\n", 170 | "Now let\u2019s say we flip two more times and they\u2019re both heads: " 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "metadata": {}, 176 | "source": [ 177 | "" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "metadata": {}, 183 | "source": [ 184 | "## Getting to an answer\n", 185 | "Let\u2019s stop here and answer the question I initially asked. How likely is this coin to have a ~50% chance of flipping heads (where that really means 45%-55% chance since statistics is an inexact science)? Here\u2019s how we can approximate the answer. From the histogram we have, we just need to add up the probability from each hypothesis.\n", 186 | "\n", 187 | "45% 0.68%\n", 188 | "46% 0.73%\n", 189 | "47% 0.78%\n", 190 | "48% 0.83%\n", 191 | "49% 0.88%\n", 192 | "50% 0.94%\n", 193 | "51% 0.99%\n", 194 | "52% 1.05%\n", 195 | "53% 1.11%\n", 196 | "54% 1.17%\n", 197 | "55% 1.24%\n", 198 | "Adding them all up equals a 10% chance.\n", 199 | "\n", 200 | "Notice how each individual hypothesis has a really low likelihood? It being exactly 50% is only a .94% chance! What\u2019s that about?\n", 201 | "\n", 202 | "This is actually a really cool aspect of this method. You see anytime you deal with probabilities (even in frequentist statistics) you need to deal with ranges of likelihoods. Call them a confidence interval, credibility interval, highest density interval, etc. It\u2019s the same concept.\n", 203 | "\n", 204 | "Also, this whole idea of treating individual probabilities as hypotheses was life altering for me. This concept applies generally to anything where you want to evaluate likelihoods. The y-axis will always be probabilities but the x-axis can be any numerical value. Be it money, time, or what have you.\n", 205 | "\n", 206 | "Actually since I\u2019ve mentioned credibility intervals, let\u2019s get that from this data as well." 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "## Credibility interval\n", 214 | "The simplest way to explain a credibility interval is to say it\u2019s essentially a confidence interval that you can easily get from a histogram. It entails saying which hypotheses encompass 95% of the likelihoods you have.\n", 215 | "\n", 216 | "I\u2019m running short on time so we\u2019ll just estimate it. I\u2019ve grabbed the data from the program and put it in a spreadsheet. My next step is to sort the data by its likelihoods in descending order and grab the first N values that add up to 95%. Then I need to find the minimum hypothesis in that range and the maximum.\n", 217 | "\n", 218 | "I\u2019ve done that for our experiment. 95% of the likelihood falls between 42%-98%. We can interpret this to mean we can\u2019t eliminate the possibility that our coin may still come up heads 50% of the time\u2026 but then we also have some confidence our coin will come up heads greater than 90% of the time. Really this interval is so wide, it really should be interpreted to mean that we need more data." 219 | ] 220 | }, 221 | { 222 | "cell_type": "markdown", 223 | "metadata": {}, 224 | "source": [ 225 | "## Wrap it up\n", 226 | "That\u2019s the algorithm. It\u2019s extremely straight forward, but those ~12 lines of code took me a while to truly understand. If you want to learn more I suggest reading about the binomial distribution. I think you might see some similarities between that and what we did. Read more here.\n", 227 | "\n", 228 | "Also here\u2019s my full code file in case you want to dive into the code to understand this deeper:" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "collapsed": false, 234 | "input": [ 235 | "def normalize(possibilities):\n", 236 | " possibility_sum = sum(possibilities)\n", 237 | " for hypothesis in xrange(0,101):\n", 238 | " possibility = possibilities[hypothesis]\n", 239 | " possibilities[hypothesis] = possibility/possibility_sum\n", 240 | "\n", 241 | "def update_success(possibilities):\n", 242 | " for hypothesis in xrange(0, 101):\n", 243 | " likelihood = possibilities[hypothesis]\n", 244 | " possibilities[hypothesis] = likelihood*hypothesis/100.0\n", 245 | " normalize(possibilities)\n", 246 | "\n", 247 | "def update_failure(possibilities):\n", 248 | " for hypothesis in xrange(0, 101):\n", 249 | " likelihood = possibilities[hypothesis]\n", 250 | " possibilities[hypothesis] = likelihood*(1.0-hypothesis/100.0)\n", 251 | " normalize(possibilities)\n", 252 | "\n", 253 | "# set every possibility to be equally possible\n", 254 | "possibilities = [1.0]*101\n", 255 | "\n", 256 | "# Coin flip, probability of heads\n", 257 | "normalize(possibilities)\n", 258 | "update_success(possibilities)\n", 259 | "update_failure(possibilities)\n", 260 | "update_success(possibilities)\n", 261 | "update_success(possibilities)" 262 | ], 263 | "language": "python", 264 | "metadata": {}, 265 | "outputs": [], 266 | "prompt_number": 4 267 | } 268 | ], 269 | "metadata": {} 270 | } 271 | ] 272 | } -------------------------------------------------------------------------------- /notebooks/.ipynb_checkpoints/Bayesian Non-Parametrics-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import numpy as np" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 131, 17 | "metadata": { 18 | "collapsed": false 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "bin_count = 50\n", 23 | "bins = np.array([0]*bin_count)\n", 24 | "\n", 25 | "bins[5] += 10\n", 26 | "bins[6] += 1\n", 27 | "\n", 28 | "\n", 29 | "bins[46] += 1\n", 30 | "\n", 31 | "bin_values = np.array([i for i in range(0,bin_count)])" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 132, 37 | "metadata": { 38 | "collapsed": false 39 | }, 40 | "outputs": [], 41 | "source": [ 42 | "possibility_distribution = [(bin_values*np.random.dirichlet(bins, bins.sum())/bins.sum()).sum() for i in range(5000)]" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 133, 48 | "metadata": { 49 | "collapsed": false 50 | }, 51 | "outputs": [ 52 | { 53 | "data": { 54 | "text/plain": [ 55 | "''" 56 | ] 57 | }, 58 | "execution_count": 133, 59 | "metadata": {}, 60 | "output_type": "execute_result" 61 | }, 62 | { 63 | "data": { 64 | "image/png": "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\nqgAAAABJRU5ErkJggg==\n", 65 | "text/plain": [ 66 | "" 67 | ] 68 | }, 69 | "metadata": {}, 70 | "output_type": "display_data" 71 | } 72 | ], 73 | "source": [ 74 | "%matplotlib inline\n", 75 | "import matplotlib.pyplot as plt\n", 76 | "\n", 77 | "plt.xlim(0,bin_count)\n", 78 | "plt.hist(possibility_distribution, bins=50)\n", 79 | "''" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 13, 85 | "metadata": { 86 | "collapsed": false 87 | }, 88 | "outputs": [ 89 | { 90 | "data": { 91 | "text/plain": [ 92 | "0.11303761322863445" 93 | ] 94 | }, 95 | "execution_count": 13, 96 | "metadata": {}, 97 | "output_type": "execute_result" 98 | } 99 | ], 100 | "source": [ 101 | "(bin_values*np.random.dirichlet(bins, 2)).mean()" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 54, 107 | "metadata": { 108 | "collapsed": false 109 | }, 110 | "outputs": [ 111 | { 112 | "data": { 113 | "text/plain": [ 114 | "array([[ 0.17338139, 0.03455451, 0.02325679, 0.02124659, 0.02573916,\n", 115 | " 0.14553389, 0.00479338, 0.04005475, 0.04634759, 0.01063245,\n", 116 | " 0.10830318, 0.03707728, 0.03097544, 0.0554245 , 0.00107638,\n", 117 | " 0.11514135, 0.01751338, 0.05830946, 0.02503389, 0.02560464]])" 118 | ] 119 | }, 120 | "execution_count": 54, 121 | "metadata": {}, 122 | "output_type": "execute_result" 123 | } 124 | ], 125 | "source": [ 126 | "bins = np.array([1]*20)\n", 127 | "bins[10] += 1\n", 128 | "bins[15] += 1\n", 129 | "observations = 1\n", 130 | "\n", 131 | "np.random.dirichlet(bins, 1)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 30, 137 | "metadata": { 138 | "collapsed": false 139 | }, 140 | "outputs": [ 141 | { 142 | "data": { 143 | "text/plain": [ 144 | "12.944118435272863" 145 | ] 146 | }, 147 | "execution_count": 30, 148 | "metadata": {}, 149 | "output_type": "execute_result" 150 | } 151 | ], 152 | "source": [ 153 | "(bin_values*np.random.dirichlet(bins, 2)/2.0).sum()" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 20, 159 | "metadata": { 160 | "collapsed": true 161 | }, 162 | "outputs": [], 163 | "source": [ 164 | "np.random.multinomial?" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": 53, 170 | "metadata": { 171 | "collapsed": true 172 | }, 173 | "outputs": [], 174 | "source": [ 175 | "np.random.dirichlet?" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 136, 181 | "metadata": { 182 | "collapsed": true 183 | }, 184 | "outputs": [], 185 | "source": [ 186 | "def custom_dirichlet(observations):\n", 187 | " total_observations = len(observations) + observations.sum()\n", 188 | " sample = np.array([np.random.beta(1+total_observations-obs, 1+obs) for obs in observations])\n", 189 | " return sample" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 137, 195 | "metadata": { 196 | "collapsed": false 197 | }, 198 | "outputs": [ 199 | { 200 | "ename": "ValueError", 201 | "evalue": "a <= 0", 202 | "output_type": "error", 203 | "traceback": [ 204 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 205 | "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", 206 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mcustom_dirichlet\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbins\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 207 | "\u001b[0;32m\u001b[0m in \u001b[0;36mcustom_dirichlet\u001b[0;34m(observations)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mcustom_dirichlet\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobservations\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mtotal_observations\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobservations\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mobservations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0msample\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbeta\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0mobservations\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0mobs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0mobs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mobs\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mobservations\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0msample\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 208 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mcustom_dirichlet\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobservations\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mtotal_observations\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mobservations\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mobservations\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0msample\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbeta\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0mobservations\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0mobs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0mobs\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mobs\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mobservations\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0msample\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 209 | "\u001b[0;32mmtrand.pyx\u001b[0m in \u001b[0;36mmtrand.RandomState.beta (numpy/random/mtrand/mtrand.c:12967)\u001b[0;34m()\u001b[0m\n", 210 | "\u001b[0;31mValueError\u001b[0m: a <= 0" 211 | ] 212 | } 213 | ], 214 | "source": [ 215 | "custom_dirichlet(bins)" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": null, 221 | "metadata": { 222 | "collapsed": true 223 | }, 224 | "outputs": [], 225 | "source": [] 226 | } 227 | ], 228 | "metadata": { 229 | "kernelspec": { 230 | "display_name": "Python 3", 231 | "language": "python", 232 | "name": "python3" 233 | }, 234 | "language_info": { 235 | "codemirror_mode": { 236 | "name": "ipython", 237 | "version": 3 238 | }, 239 | "file_extension": ".py", 240 | "mimetype": "text/x-python", 241 | "name": "python", 242 | "nbconvert_exporter": "python", 243 | "pygments_lexer": "ipython3", 244 | "version": "3.4.3" 245 | } 246 | }, 247 | "nbformat": 4, 248 | "nbformat_minor": 0 249 | } 250 | -------------------------------------------------------------------------------- /notebooks/PuLP Shopping with a Data Scientist.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import pulp as p" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "# Shopping with a Data Scientist" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 2, 24 | "metadata": { 25 | "collapsed": false 26 | }, 27 | "outputs": [], 28 | "source": [ 29 | "model_a = p.LpProblem(\"Albon Shopping Problem\", p.LpMinimize)" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "We define four cable types: Lightning, Micro-USB, USB, and USB-C (encoded below as $L$, $U^m$, $U$, and $U^c$ respectively). Each is available in 1 foot, 3 feet, and 6 feet lengths (encoded below as subscripts of 1, 3, and 6 respectively)." 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 3, 42 | "metadata": { 43 | "collapsed": true 44 | }, 45 | "outputs": [], 46 | "source": [ 47 | "lightning_1 = p.LpVariable('lightning_1', lowBound=0, cat='Integer')\n", 48 | "lightning_3 = p.LpVariable('lightning_3', lowBound=0, cat='Integer')\n", 49 | "lightning_6 = p.LpVariable('lightning_6', lowBound=0, cat='Integer')\n", 50 | "\n", 51 | "microusb_1 = p.LpVariable('microusb_1', lowBound=0, cat='Integer')\n", 52 | "microusb_3 = p.LpVariable('microusb_3', lowBound=0, cat='Integer')\n", 53 | "microusb_6 = p.LpVariable('microusb_6', lowBound=0, cat='Integer')\n", 54 | "\n", 55 | "usb_1 = p.LpVariable('usb_1', lowBound=0, cat='Integer')\n", 56 | "usb_3 = p.LpVariable('usb_3', lowBound=0, cat='Integer')\n", 57 | "usb_6 = p.LpVariable('usb_6', lowBound=0, cat='Integer')\n", 58 | "\n", 59 | "usbc_1 = p.LpVariable('usbc_1', lowBound=0, cat='Integer')\n", 60 | "usbc_3 = p.LpVariable('usbc_3', lowBound=0, cat='Integer')\n", 61 | "usbc_6 = p.LpVariable('usbc_6', lowBound=0, cat='Integer')" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "Our objective function is expressed a bit tersely in math but is perhaps more readable in code. The cost of each cord we make related to length. 1 foot cords cost \\$10, 3 feet cords cost \\$15, and 6 feet cords cost \\$20. Ideally, we want to minimize our costs subject to the constraints.\n", 69 | "\n", 70 | "$$ F = 10 * (L_1 + U^{m}_1 + U_1 + U^{c}_1) + \n", 71 | " 15 * (L_3 + U^{m}_3 + U_3 + U^{c}_3) + \n", 72 | " 20 * (L_6 + U^{m}_6 + U_6 + U^{c}_6)$$" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 4, 78 | "metadata": { 79 | "collapsed": true 80 | }, 81 | "outputs": [], 82 | "source": [ 83 | "model_a += 10 * (lightning_1 + microusb_1 + usb_1 + usbc_1) \\\n", 84 | " + 15 * (lightning_3 + microusb_3 + usb_3 + usbc_3) \\\n", 85 | " + 20 * (lightning_6 + microusb_6 + usb_6 + usbc_6), \"Cost\"" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "Next we need a way to identify which cables can be used with which devices. The below code codifies this. The mathematical expressions are left as an exercise for the reader." 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 5, 98 | "metadata": { 99 | "collapsed": false 100 | }, 101 | "outputs": [], 102 | "source": [ 103 | "power_supply_connectors = usbc_6\n", 104 | "ipad_connectors = lightning_1 + lightning_3 + lightning_6\n", 105 | "iphone_connectors = ipad_connectors\n", 106 | "android_connectors = microusb_1 + microusb_3 + microusb_6\n", 107 | "camera_connectors = microusb_3 + microusb_6\n", 108 | "battery_usb_connectors = usb_3 + usb_6\n", 109 | "battery_usbc_connectors = usbc_3 + usbc_6\n", 110 | "battery_connectors = battery_usb_connectors + battery_usbc_connectors\n", 111 | "hd_connectors = usbc_3 + usbc_6" 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "Now that we've associated all of the concepts in our model, it's time to add some constraints! The below basically defines that every device (not every cable!) requires at least one cable. Notice that if we have a single lightning cable, that will satisfy both the iPhone and iPad constraints." 119 | ] 120 | }, 121 | { 122 | "cell_type": "code", 123 | "execution_count": 6, 124 | "metadata": { 125 | "collapsed": true 126 | }, 127 | "outputs": [], 128 | "source": [ 129 | "model_a += power_supply_connectors >= 1\n", 130 | "model_a += ipad_connectors >= 1\n", 131 | "model_a += iphone_connectors >= 1\n", 132 | "model_a += android_connectors >= 1\n", 133 | "model_a += camera_connectors >= 1\n", 134 | "model_a += battery_connectors >= 1\n", 135 | "model_a += hd_connectors >= 1" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "Lastly we solve the optimization problem." 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 7, 148 | "metadata": { 149 | "collapsed": false 150 | }, 151 | "outputs": [ 152 | { 153 | "data": { 154 | "text/plain": [ 155 | "'Optimal'" 156 | ] 157 | }, 158 | "execution_count": 7, 159 | "metadata": {}, 160 | "output_type": "execute_result" 161 | } 162 | ], 163 | "source": [ 164 | "model_a.solve()\n", 165 | "p.LpStatus[model_a.status]" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": 8, 171 | "metadata": { 172 | "collapsed": false 173 | }, 174 | "outputs": [ 175 | { 176 | "name": "stdout", 177 | "output_type": "stream", 178 | "text": [ 179 | "lightning_1 = 1.0\n", 180 | "microusb_3 = 1.0\n", 181 | "usbc_6 = 1.0\n" 182 | ] 183 | } 184 | ], 185 | "source": [ 186 | "variables = [\n", 187 | " lightning_1,\n", 188 | " lightning_3,\n", 189 | " lightning_6,\n", 190 | " microusb_1,\n", 191 | " microusb_3,\n", 192 | " microusb_6,\n", 193 | " usb_1,\n", 194 | " usb_3,\n", 195 | " usb_6,\n", 196 | " usbc_1,\n", 197 | " usbc_3,\n", 198 | " usbc_6\n", 199 | "]\n", 200 | "\n", 201 | "for variable in variables:\n", 202 | " if variable.varValue > 0:\n", 203 | " print(\"{0} = {1}\".format(variable.getName(), variable.varValue))" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "metadata": {}, 209 | "source": [ 210 | "And finally we can print out the cost of the solution that was found." 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "execution_count": 9, 216 | "metadata": { 217 | "collapsed": false 218 | }, 219 | "outputs": [ 220 | { 221 | "name": "stdout", 222 | "output_type": "stream", 223 | "text": [ 224 | "45.0\n" 225 | ] 226 | } 227 | ], 228 | "source": [ 229 | "print(p.value(model_a.objective))" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "So for $45 we can connect all of our devices to the laptop!" 237 | ] 238 | }, 239 | { 240 | "cell_type": "markdown", 241 | "metadata": {}, 242 | "source": [ 243 | "# More Complex Model" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 33, 249 | "metadata": { 250 | "collapsed": true 251 | }, 252 | "outputs": [], 253 | "source": [ 254 | "model_b = p.LpProblem(\"Albon Shopping Problem (Use all USB-C ports)\", p.LpMinimize)" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 34, 260 | "metadata": { 261 | "collapsed": true 262 | }, 263 | "outputs": [], 264 | "source": [ 265 | "lightning_1 = p.LpVariable('lightning_1', lowBound=0, cat='Integer')\n", 266 | "lightning_3 = p.LpVariable('lightning_3', lowBound=0, cat='Integer')\n", 267 | "lightning_6 = p.LpVariable('lightning_6', lowBound=0, cat='Integer')\n", 268 | "\n", 269 | "microusb_1 = p.LpVariable('microusb_1', lowBound=0, cat='Integer')\n", 270 | "microusb_3 = p.LpVariable('microusb_3', lowBound=0, cat='Integer')\n", 271 | "microusb_6 = p.LpVariable('microusb_6', lowBound=0, cat='Integer')\n", 272 | "\n", 273 | "usb_1 = p.LpVariable('usb_1', lowBound=0, cat='Integer')\n", 274 | "usb_3 = p.LpVariable('usb_3', lowBound=0, cat='Integer')\n", 275 | "usb_6 = p.LpVariable('usb_6', lowBound=0, cat='Integer')\n", 276 | "\n", 277 | "usbc_1 = p.LpVariable('usbc_1', lowBound=0, cat='Integer')\n", 278 | "usbc_3 = p.LpVariable('usbc_3', lowBound=0, cat='Integer')\n", 279 | "usbc_6 = p.LpVariable('usbc_6', lowBound=0, cat='Integer')" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": 35, 285 | "metadata": { 286 | "collapsed": true 287 | }, 288 | "outputs": [], 289 | "source": [ 290 | "model_b += 10 * (lightning_1 + microusb_1 + usb_1 + usbc_1) \\\n", 291 | " + 15 * (lightning_3 + microusb_3 + usb_3 + usbc_3) \\\n", 292 | " + 20 * (lightning_6 + microusb_6 + usb_6 + usbc_6), \"Cost\"" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": 36, 298 | "metadata": { 299 | "collapsed": true 300 | }, 301 | "outputs": [], 302 | "source": [ 303 | "power_supply_connectors = usbc_6\n", 304 | "ipad_connectors = lightning_1 + lightning_3 + lightning_6\n", 305 | "iphone_connectors = ipad_connectors\n", 306 | "android_connectors = microusb_1 + microusb_3 + microusb_6\n", 307 | "camera_connectors = microusb_3 + microusb_6\n", 308 | "battery_usb_connectors = usb_3 + usb_6\n", 309 | "battery_usbc_connectors = usbc_3 + usbc_6\n", 310 | "battery_connectors = battery_usb_connectors + battery_usbc_connectors\n", 311 | "hd_connectors = usbc_3 + usbc_6" 312 | ] 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "metadata": {}, 317 | "source": [ 318 | "Then our constraints again. The \"model_b +=\" portions are just how we associate the constraint with the model we're building. Our main change here is that we have constrained the problem such that we need to be able to connect our power supply and hard drive at the same time (two separate ports). See Constraint set 2 below. \n", 319 | "\n", 320 | "On top of that, we want to be able to use the other two ports on our laptop so we can maximize the four available ports (see Constraint set 3 below)." 321 | ] 322 | }, 323 | { 324 | "cell_type": "code", 325 | "execution_count": 45, 326 | "metadata": { 327 | "collapsed": false 328 | }, 329 | "outputs": [], 330 | "source": [ 331 | "# Same constraints as before\n", 332 | "model_b += power_supply_connectors >= 1\n", 333 | "model_b += ipad_connectors >= 1\n", 334 | "model_b += iphone_connectors >= 1\n", 335 | "model_b += android_connectors >= 1\n", 336 | "model_b += camera_connectors >= 1\n", 337 | "model_b += battery_connectors >= 1\n", 338 | "model_b += hd_connectors >= 1\n", 339 | "\n", 340 | "# Constraint set 2\n", 341 | "# Plus hard drive and power supply must be able to be plugged\n", 342 | "# in simultaneously\n", 343 | "# Since hard drive cables are a superset of the power supply\n", 344 | "# cables we can formulate this constraint as \n", 345 | "# \"We should have at least one hard drive cable over and above\n", 346 | "# what we have for our power supply\"\n", 347 | "model_b += hd_connectors - power_supply_connectors >= 1\n", 348 | "\n", 349 | "# Constraint set 3\n", 350 | "# Plus must have four cables to plug in and fill all four ports\n", 351 | "model_b += (lightning_1 + lightning_3 + lightning_6 \\\n", 352 | " + microusb_1 + microusb_3 + microusb_6 \\\n", 353 | " + usb_1 + usb_3 + usb_6 \\\n", 354 | " + usbc_1 + usbc_3 + usbc_6) == 4" 355 | ] 356 | }, 357 | { 358 | "cell_type": "markdown", 359 | "metadata": {}, 360 | "source": [ 361 | "Solve the model..." 362 | ] 363 | }, 364 | { 365 | "cell_type": "code", 366 | "execution_count": 46, 367 | "metadata": { 368 | "collapsed": false 369 | }, 370 | "outputs": [ 371 | { 372 | "data": { 373 | "text/plain": [ 374 | "'Optimal'" 375 | ] 376 | }, 377 | "execution_count": 46, 378 | "metadata": {}, 379 | "output_type": "execute_result" 380 | } 381 | ], 382 | "source": [ 383 | "model_b.solve()\n", 384 | "p.LpStatus[model_b.status]" 385 | ] 386 | }, 387 | { 388 | "cell_type": "code", 389 | "execution_count": 47, 390 | "metadata": { 391 | "collapsed": false 392 | }, 393 | "outputs": [ 394 | { 395 | "name": "stdout", 396 | "output_type": "stream", 397 | "text": [ 398 | "lightning_1 = 1.0\n", 399 | "microusb_3 = 1.0\n", 400 | "usbc_3 = 1.0\n", 401 | "usbc_6 = 1.0\n" 402 | ] 403 | } 404 | ], 405 | "source": [ 406 | "variables = [\n", 407 | " lightning_1,\n", 408 | " lightning_3,\n", 409 | " lightning_6,\n", 410 | " microusb_1,\n", 411 | " microusb_3,\n", 412 | " microusb_6,\n", 413 | " usb_1,\n", 414 | " usb_3,\n", 415 | " usb_6,\n", 416 | " usbc_1,\n", 417 | " usbc_3,\n", 418 | " usbc_6\n", 419 | "]\n", 420 | "\n", 421 | "for variable in variables:\n", 422 | " if variable.varValue > 0:\n", 423 | " print(\"{0} = {1}\".format(variable.getName(), variable.varValue))" 424 | ] 425 | }, 426 | { 427 | "cell_type": "markdown", 428 | "metadata": {}, 429 | "source": [ 430 | "And then print out the cost!" 431 | ] 432 | }, 433 | { 434 | "cell_type": "code", 435 | "execution_count": 44, 436 | "metadata": { 437 | "collapsed": false 438 | }, 439 | "outputs": [ 440 | { 441 | "name": "stdout", 442 | "output_type": "stream", 443 | "text": [ 444 | "60.0\n" 445 | ] 446 | } 447 | ], 448 | "source": [ 449 | "print(p.value(model_b.objective))" 450 | ] 451 | }, 452 | { 453 | "cell_type": "markdown", 454 | "metadata": {}, 455 | "source": [ 456 | "Our new constraints required us to increase the cost by $15." 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": null, 462 | "metadata": { 463 | "collapsed": true 464 | }, 465 | "outputs": [], 466 | "source": [] 467 | } 468 | ], 469 | "metadata": { 470 | "kernelspec": { 471 | "display_name": "Python 3", 472 | "language": "python", 473 | "name": "python3" 474 | }, 475 | "language_info": { 476 | "codemirror_mode": { 477 | "name": "ipython", 478 | "version": 3 479 | }, 480 | "file_extension": ".py", 481 | "mimetype": "text/x-python", 482 | "name": "python", 483 | "nbconvert_exporter": "python", 484 | "pygments_lexer": "ipython3", 485 | "version": "3.4.4" 486 | } 487 | }, 488 | "nbformat": 4, 489 | "nbformat_minor": 2 490 | } 491 | -------------------------------------------------------------------------------- /notebooks/Bayesian likelihood of normal distribution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "" 4 | }, 5 | "nbformat": 3, 6 | "nbformat_minor": 0, 7 | "worksheets": [ 8 | { 9 | "cells": [ 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "If you're running an experiment on a population that is normally distributed with a known standard deviation and you observe a sample... what is the likelihood of that sample being the mean of the population you're observing?\n", 15 | "\n", 16 | "That's what I'm solving for today." 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "collapsed": false, 22 | "input": [ 23 | "import numpy, math\n", 24 | "\n", 25 | "foo = [round(numpy.random.normal(20, 3),2) for x in range(0,5)]" 26 | ], 27 | "language": "python", 28 | "metadata": {}, 29 | "outputs": [], 30 | "prompt_number": 40 31 | }, 32 | { 33 | "cell_type": "code", 34 | "collapsed": false, 35 | "input": [ 36 | "foo" 37 | ], 38 | "language": "python", 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "metadata": {}, 43 | "output_type": "pyout", 44 | "prompt_number": 41, 45 | "text": [ 46 | "[14.62, 24.55, 18.23, 21.83, 19.25]" 47 | ] 48 | } 49 | ], 50 | "prompt_number": 41 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "# How to calculate the distribution" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "collapsed": false, 62 | "input": [ 63 | "n = len(foo)\n", 64 | "sigma = 3\n", 65 | "mu = 20\n", 66 | "\n", 67 | "def likelihood_of_normal_parameters(data, mu, sigma):\n", 68 | " return numpy.exp(-n/(2*math.pow(sigma, 2))*math.pow(numpy.mean(data) - mu,2))\n", 69 | "\n", 70 | "hypotheses = numpy.array([1]*10000)/(1.0*10000)\n", 71 | "\n", 72 | "hypotheses" 73 | ], 74 | "language": "python", 75 | "metadata": {}, 76 | "outputs": [ 77 | { 78 | "metadata": {}, 79 | "output_type": "pyout", 80 | "prompt_number": 42, 81 | "text": [ 82 | "array([ 0.0001, 0.0001, 0.0001, ..., 0.0001, 0.0001, 0.0001])" 83 | ] 84 | } 85 | ], 86 | "prompt_number": 42 87 | }, 88 | { 89 | "cell_type": "code", 90 | "collapsed": false, 91 | "input": [ 92 | "for i in range(0,10000):\n", 93 | " hypothesis = i/(1.0*100)\n", 94 | " hypotheses[i] = hypotheses[i]*likelihood_of_normal_parameters(foo, hypothesis, sigma)" 95 | ], 96 | "language": "python", 97 | "metadata": {}, 98 | "outputs": [], 99 | "prompt_number": 43 100 | }, 101 | { 102 | "cell_type": "code", 103 | "collapsed": false, 104 | "input": [ 105 | "new_hypotheses = hypotheses/numpy.sum(hypotheses)" 106 | ], 107 | "language": "python", 108 | "metadata": {}, 109 | "outputs": [], 110 | "prompt_number": 44 111 | }, 112 | { 113 | "cell_type": "code", 114 | "collapsed": false, 115 | "input": [ 116 | "import matplotlib.pyplot as plt\n", 117 | "\n", 118 | "plt.plot(new_hypotheses)" 119 | ], 120 | "language": "python", 121 | "metadata": {}, 122 | "outputs": [ 123 | { 124 | "metadata": {}, 125 | "output_type": "pyout", 126 | "prompt_number": 45, 127 | "text": [ 128 | "[]" 129 | ] 130 | }, 131 | { 132 | "metadata": {}, 133 | "output_type": "display_data", 134 | "png": "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\n", 135 | "text": [ 136 | "" 137 | ] 138 | } 139 | ], 140 | "prompt_number": 45 141 | } 142 | ], 143 | "metadata": {} 144 | } 145 | ] 146 | } -------------------------------------------------------------------------------- /notebooks/Bayesian Non-Parametrics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import numpy as np" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 131, 17 | "metadata": { 18 | "collapsed": false 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "bin_count = 50\n", 23 | "bins = np.array([0]*bin_count)\n", 24 | "\n", 25 | "bins[5] += 10\n", 26 | "bins[6] += 1\n", 27 | "\n", 28 | "\n", 29 | "bins[46] += 1\n", 30 | "\n", 31 | "bin_values = np.array([i for i in range(0,bin_count)])" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 132, 37 | "metadata": { 38 | "collapsed": false 39 | }, 40 | "outputs": [], 41 | "source": [ 42 | "possibility_distribution = [(bin_values*np.random.dirichlet(bins, bins.sum())/bins.sum()).sum() for i in range(5000)]" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 133, 48 | "metadata": { 49 | "collapsed": false 50 | }, 51 | "outputs": [ 52 | { 53 | "data": { 54 | "text/plain": [ 55 | "''" 56 | ] 57 | }, 58 | "execution_count": 133, 59 | "metadata": {}, 60 | "output_type": "execute_result" 61 | }, 62 | { 63 | "data": { 64 | "image/png": "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\nqgAAAABJRU5ErkJggg==\n", 65 | "text/plain": [ 66 | "" 67 | ] 68 | }, 69 | "metadata": {}, 70 | "output_type": "display_data" 71 | } 72 | ], 73 | "source": [ 74 | "%matplotlib inline\n", 75 | "import matplotlib.pyplot as plt\n", 76 | "\n", 77 | "plt.xlim(0,bin_count)\n", 78 | "plt.hist(possibility_distribution, bins=50)\n", 79 | "''" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 13, 85 | "metadata": { 86 | "collapsed": false 87 | }, 88 | "outputs": [ 89 | { 90 | "data": { 91 | "text/plain": [ 92 | "0.11303761322863445" 93 | ] 94 | }, 95 | "execution_count": 13, 96 | "metadata": {}, 97 | "output_type": "execute_result" 98 | } 99 | ], 100 | "source": [ 101 | "(bin_values*np.random.dirichlet(bins, 2)).mean()" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": 54, 107 | "metadata": { 108 | "collapsed": false 109 | }, 110 | "outputs": [ 111 | { 112 | "data": { 113 | "text/plain": [ 114 | "array([[ 0.17338139, 0.03455451, 0.02325679, 0.02124659, 0.02573916,\n", 115 | " 0.14553389, 0.00479338, 0.04005475, 0.04634759, 0.01063245,\n", 116 | " 0.10830318, 0.03707728, 0.03097544, 0.0554245 , 0.00107638,\n", 117 | " 0.11514135, 0.01751338, 0.05830946, 0.02503389, 0.02560464]])" 118 | ] 119 | }, 120 | "execution_count": 54, 121 | "metadata": {}, 122 | "output_type": "execute_result" 123 | } 124 | ], 125 | "source": [ 126 | "bins = np.array([1]*20)\n", 127 | "bins[10] += 1\n", 128 | "bins[15] += 1\n", 129 | "observations = 1\n", 130 | "\n", 131 | "np.random.dirichlet(bins, 1)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 30, 137 | "metadata": { 138 | "collapsed": false 139 | }, 140 | "outputs": [ 141 | { 142 | "data": { 143 | "text/plain": [ 144 | "12.944118435272863" 145 | ] 146 | }, 147 | "execution_count": 30, 148 | "metadata": {}, 149 | "output_type": "execute_result" 150 | } 151 | ], 152 | "source": [ 153 | "(bin_values*np.random.dirichlet(bins, 2)/2.0).sum()" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 20, 159 | "metadata": { 160 | "collapsed": true 161 | }, 162 | "outputs": [], 163 | "source": [ 164 | "np.random.multinomial?" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": 53, 170 | "metadata": { 171 | "collapsed": true 172 | }, 173 | "outputs": [], 174 | "source": [ 175 | "np.random.dirichlet?" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": 157, 181 | "metadata": { 182 | "collapsed": true 183 | }, 184 | "outputs": [], 185 | "source": [ 186 | "def custom_dirichlet(observations):\n", 187 | " total_observations = len(observations) + observations.sum()\n", 188 | " sample = np.array([np.random.beta(1+obs, 1+total_observations-obs) for obs in observations])\n", 189 | " return sample" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 176, 195 | "metadata": { 196 | "collapsed": false 197 | }, 198 | "outputs": [ 199 | { 200 | "data": { 201 | "text/plain": [ 202 | "''" 203 | ] 204 | }, 205 | "execution_count": 176, 206 | "metadata": {}, 207 | "output_type": "execute_result" 208 | }, 209 | { 210 | "data": { 211 | "image/png": "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\n", 212 | "text/plain": [ 213 | "" 214 | ] 215 | }, 216 | "metadata": {}, 217 | "output_type": "display_data" 218 | } 219 | ], 220 | "source": [ 221 | "bins = np.array([0]*40)\n", 222 | "bins[10] += 10\n", 223 | "\n", 224 | "bin_values = np.array([i for i in range(0,len(bins))])\n", 225 | "possibility_distribution = [(bin_values*custom_dirichlet(bins)).sum() for i in range(5000)]\n", 226 | "\n", 227 | "plt.xlim(0,bin_count)\n", 228 | "plt.hist(possibility_distribution, bins=50)\n", 229 | "''" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 151, 235 | "metadata": { 236 | "collapsed": false 237 | }, 238 | "outputs": [ 239 | { 240 | "data": { 241 | "text/plain": [ 242 | "0.0009800877807168468" 243 | ] 244 | }, 245 | "execution_count": 151, 246 | "metadata": {}, 247 | "output_type": "execute_result" 248 | } 249 | ], 250 | "source": [ 251 | "%matplotlib inline\n", 252 | "import matplotlib.pyplot as plt\n", 253 | "\n", 254 | "plt.xlim(0,bin_count)\n", 255 | "plt.hist(possibility_distribution, bins=50)" 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": null, 261 | "metadata": { 262 | "collapsed": true 263 | }, 264 | "outputs": [], 265 | "source": [] 266 | } 267 | ], 268 | "metadata": { 269 | "kernelspec": { 270 | "display_name": "Python 3", 271 | "language": "python", 272 | "name": "python3" 273 | }, 274 | "language_info": { 275 | "codemirror_mode": { 276 | "name": "ipython", 277 | "version": 3 278 | }, 279 | "file_extension": ".py", 280 | "mimetype": "text/x-python", 281 | "name": "python", 282 | "nbconvert_exporter": "python", 283 | "pygments_lexer": "ipython3", 284 | "version": "3.4.3" 285 | } 286 | }, 287 | "nbformat": 4, 288 | "nbformat_minor": 0 289 | } 290 | -------------------------------------------------------------------------------- /notebooks/Predicting Divvy bike availability with MRJob and Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "" 4 | }, 5 | "nbformat": 3, 6 | "nbformat_minor": 0, 7 | "worksheets": [ 8 | { 9 | "cells": [ 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "I'm going to show you how I've been collecting Divvy bike data with the ultimate goal of getting probability distributions for every bike dock in Chicago for every minute of both weekdays and weekends. My ultimate goal is to create a visualization of some sort to see how availability of both docks and bikes changes over time per station. In this article I'll get to the point where I'll be able to make some predictions about how many bikes are available at this exact minute.\n", 15 | "\n", 16 | "What are Divvy bikes? Divvy is a bike sharing system in use throughout Chicago. Every minute they post data regarding the status of every bike dock in the city to an open API. You can learn more about Divvy bikes here: [http://divvybikes.com](http://divvybikes.com)\n", 17 | "\n", 18 | "I've been collecting data for the Divvy bikes throughout Chicago since September with my friend Andrew Slotnick. He showed me that it's a pretty simple API to scrape. Just hit this URL and record the results every minute:\n", 19 | "\n", 20 | "[http://divvybikes.com/stations/json](http://divvybikes.com/stations/json)\n", 21 | "\n", 22 | "I have a Mac Mini in my living that my Fianc\u00e9e watches TV shows on while I'm running processes to scrape data sources. It's all about synergy!\n", 23 | "\n", 24 | "So on this multi-tasking Mac Mini I have the following Python script running:" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "collapsed": false, 30 | "input": [ 31 | "import urllib\n", 32 | "import datetime\n", 33 | "import os, time, socket, datetime, glob\n", 34 | "import subprocess\n", 35 | "\n", 36 | "socket.setdefaulttimeout(20)\n", 37 | "\n", 38 | "def group_previous_days_files():\n", 39 | " print \"Attempting grouping...\"\n", 40 | " print \"bozo...\"\n", 41 | " todays_group = datetime.datetime.now().strftime('%Y%m%d')\n", 42 | " print \"Today's group is \" + todays_group\n", 43 | " log_filenames = [file_path for file_path in glob.glob(\"./DownloadedData/*.json\") if not todays_group in file_path and not 'day_' in file_path]\n", 44 | " print \"Logs found: \" + str(log_filenames)\n", 45 | " for log_filename in log_filenames:\n", 46 | " print \"Grouping \" + log_filename\n", 47 | " base_log_filename = os.path.basename(log_filename)\n", 48 | " the_date = base_log_filename.split('_')[0]\n", 49 | " grouped_file_path = data_folder + 'day_' + the_date + '.json'\n", 50 | " with open(grouped_file_path, 'a') as day_file:\n", 51 | " with open(log_filename, 'r') as minute_file:\n", 52 | " minute_text = minute_file.read()\n", 53 | " day_file.write(minute_text)\n", 54 | " print \"Removing \" + log_filename\n", 55 | " os.remove(log_filename)\n", 56 | " os.system('s3cmd sync ./DownloadedData/day_*.json s3://bozo-divvy/day-log/')\n", 57 | " print \"Done uploading to S3\"\n", 58 | " for day_file in glob.glob('./DownloadedData/day_*.json'):\n", 59 | " os.remove(day_file)\n", 60 | "\n", 61 | "data_folder = './DownloadedData/'\n", 62 | "while True:\n", 63 | " current_file_name = datetime.datetime.now().strftime('%Y%m%d_%H%M') + '.json'\n", 64 | " current_file_path = data_folder + current_file_name\n", 65 | " if not os.path.exists(current_file_path):\n", 66 | " print \"Downloading file... then sleeping.\"\n", 67 | " try:\n", 68 | " urllib.urlretrieve (\"http://divvybikes.com/stations/json\", current_file_path)\n", 69 | " except:\n", 70 | " time.sleep(3)\n", 71 | " continue\n", 72 | " group_previous_days_files()\n", 73 | " else:\n", 74 | " print \"File already exists. Sleeping\"\n", 75 | " time.sleep(30)" 76 | ], 77 | "language": "python", 78 | "metadata": {}, 79 | "outputs": [] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "This script does the following:\n", 86 | "\n", 87 | "* Hits the Divvy bike service every 30 seconds and downloads the json if it hasn't already been downloaded this minute.\n", 88 | "* If an error happens during the download the service just swallows it.\n", 89 | "* After attempting the download I group up the previous days' logs into full day files then upload them to S3.\n", 90 | "* I delete the no longer necessary minute by minute logs (NOTE: any logs from the current day reamain in minute by minute form).\n", 91 | "\n", 92 | "If you watched Hilary Mason in this video you might recognize this as the \"Obtain\" and \"Scrub\" steps of data science:\n", 93 | "\n", 94 | "* Dirty secrets of data science: [http://blog.mortardata.com/post/45192413994/dirty-secrets-of-data-science](http://blog.mortardata.com/post/45192413994/dirty-secrets-of-data-science)\n", 95 | "* Also see dataist here: [http://www.dataists.com/2010/09/a-taxonomy-of-data-science/](http://www.dataists.com/2010/09/a-taxonomy-of-data-science/)\n", 96 | "\n", 97 | "This gives data in this format:" 98 | ] 99 | }, 100 | { 101 | "cell_type": "raw", 102 | "metadata": {}, 103 | "source": [ 104 | "{\"executionTime\":\"2014-01-20 12:07:02 PM\",\"stationBeanList\":[{\"id\":5,\"stationName\":\"State St & Harrison St\",\"availableDocks\":15,\"totalDocks\":19,\"latitude\":41.8739580629,\"longitude\":-87.6277394859,\"statusValue\":\"In Service\",\"statusKey\":1,\"availableBikes\":4,\"stAddress1\":\"State St & Harrison St\",\"stAddress2\":\"\",\"city\":\"\",\"postalCode\":\"\",\"location\":\"620 S. State St.\",\"altitude\":\"\",\"testStation\":false,\"lastCommunicationTime\":null,\"landMark\":\"030\"},{\"id\":13,..." 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "The next step is..." 112 | ] 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "metadata": {}, 117 | "source": [ 118 | "## Exploring the data\n", 119 | "\n", 120 | "My goal from the get go with this data has been to answer the question: \"How likely am I to find an available bike or dock?\"\n", 121 | "\n", 122 | "I'd also love to be able to see this visualized and animated on a map. We'll see about that. First let's answer my burning question. To do that I'll need to design roughly how I want my data to be organized when I get done with it.\n", 123 | "\n", 124 | "What I want is a row of data that lists the bike station, whether or not it's a weekend, the hour, and the minute of the day, as well as a frequency distribution that maps how often X number of bikes and docks were available. Something like this:" 125 | ] 126 | }, 127 | { 128 | "cell_type": "raw", 129 | "metadata": {}, 130 | "source": [ 131 | "{\n", 132 | " \"latitude\": 41.795211999999999, \n", 133 | " \"longitude\": -87.580714999999998, \n", 134 | " \"station_name\": \"Shore Drive & 55th St\", \n", 135 | " \"minute\": 3, \n", 136 | " \"weekday\": true\n", 137 | " \"hour\": 0, \n", 138 | " \"station_id\": 247, \n", 139 | " \"available_bikes\": [0, 1, 7, 10, 9, 10, 8, 16, 2, 3, 6, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \n", 140 | " \"total_docks\": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 141 | " \"available_docks\": [0, 0, 1, 0, 0, 6, 3, 2, 16, 8, 10, 9, 10, 7, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n", 142 | "}" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "That's what I want my final result to look like. Each index in the arrays maps to a hypothesis. So for available bikes, zero times there were zero available bikes, one time there was one available bike, seven times there were two available bikes, etc. To come up with a probability we just add up all of the elements of the array and divide each element by that sum. The same applies for available docks and total docks.\n", 150 | "\n", 151 | "Two things to keep in mind:\n", 152 | "\n", 153 | "1. Just because we've never seen a given number of occurences doesn't mean it's impossible. In other words all the zeroes are a bit extreme. We should add some small amount to each element just to be sure when we do math that zero doesn't count as certainty in impossibility.\n", 154 | "2. You may have noticed \"total_docks\" above. Technically the number of docks available could change. This allows me to account for that. In the case above, this station always had 15 total docks.\n", 155 | "\n", 156 | "So how do we get from the first data format to this ideal one when we've got gigabytes of data being stored on S3? MRJob.\n", 157 | "\n", 158 | "I could download all of the files to my local computer and run a script locally to do my aggregation. What's great about MRJob is I can use the same script I write locally to run on my data on S3 using Amazon's Elastic Map Reduce (EMR). So I'll start by downloading some of the data locally and running my script locally and once it seems to be working I'll decide if it's easier to run it locally or in the cloud.\n", 159 | "\n", 160 | "Let's get started!" 161 | ] 162 | }, 163 | { 164 | "cell_type": "markdown", 165 | "metadata": {}, 166 | "source": [ 167 | "## MRJob step by step\n", 168 | "\n", 169 | "If you need a MapReduce intro or refresher my good friend Tom Hayden has a great introduction to MRJob where he analyzes chess data. Check it out here: [http://tomhayden3.com/2013/12/27/chess-mr-job/](http://tomhayden3.com/2013/12/27/chess-mr-job/)\n", 170 | "\n", 171 | "This example will require us to use some of MRJob's advanced features like multi-step support and protocols.\n", 172 | "\n", 173 | "### Step 0: Decide on input and output formats\n", 174 | "Above I showed that the data I collect from divvy is in the form of JSON. So rather than import lines of text and parse them as JSON into Python dictionaries, I can just have MRJob do that for me with \"protocols\". Below I show what's necessary to define the input and output protocols for this job.\n" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "collapsed": false, 180 | "input": [ 181 | "from mrjob.job import MRJob\n", 182 | "from mrjob import protocol\n", 183 | "\n", 184 | "class AggregateDivvyFrequencies(MRJob):\n", 185 | " INPUT_PROTOCOL = protocol.JSONValueProtocol\n", 186 | " OUTPUT_PROTOCOL = protocol.JSONValueProtocol\n", 187 | "..." 188 | ], 189 | "language": "python", 190 | "metadata": {}, 191 | "outputs": [] 192 | }, 193 | { 194 | "cell_type": "markdown", 195 | "metadata": {}, 196 | "source": [ 197 | "The main ones I use for input and output are \"protocol.RawValueProtocol\" and \"protocol.JSONValueProtocol\". The RawValueProtocol will pass data through just on the value parameter for input and output and won't add any additional formatting to what you explicitly do. This is the default input protocol for MRJob. To read more about protocols check this link out: [http://pythonhosted.org/mrjob/guides/writing-mrjobs.html#job-protocols](http://pythonhosted.org/mrjob/guides/writing-mrjobs.html#job-protocols)\n", 198 | "\n", 199 | "In this job I've decided to make my life easier by reading in the lines of text as JSON objects and I personally prefer saving the data as JSON as well." 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "metadata": {}, 205 | "source": [ 206 | "### Step 1: organize the raw data\n", 207 | "\n", 208 | "First I need to tally for each of my stations how many times they had a given number of bikes, docks, total docks for weekdays and weekends. This essentially means grouping the stations by their pertinent information (lat/long, station id, is_weekday, etc) by setting that info as the key and then setting the value to 1 to act as a tally of how many times I've seen this. Each line of Divvy data includes metadata around the logging (such as the time the data was logged) and ALL the data for each and every station. This means that my mapping step will create many results from the mapper for each line of input.\n", 209 | "\n", 210 | "Here's what this step looks like:" 211 | ] 212 | }, 213 | { 214 | "cell_type": "code", 215 | "collapsed": false, 216 | "input": [ 217 | "def organize_station_data_and_tally(self, _, station_update):\n", 218 | " execution_time_text = station_update['executionTime']\n", 219 | " the_datetime = datetime.strptime(execution_time_text, '%Y-%m-%d %I:%M:%S %p')\n", 220 | " the_hour = the_datetime.hour\n", 221 | " the_minute = the_datetime.minute\n", 222 | " weekday_index = the_datetime.weekday()\n", 223 | " weekday_map = [\n", 224 | " 'Monday',\n", 225 | " 'Tuesday',\n", 226 | " 'Wednesday',\n", 227 | " 'Thursday',\n", 228 | " 'Friday',\n", 229 | " 'Saturday',\n", 230 | " 'Sunday'\n", 231 | " ]\n", 232 | " station_list = station_update['stationBeanList']\n", 233 | " for station_info in station_list:\n", 234 | " station_data = {\n", 235 | " 'station_id': station_info['id'],\n", 236 | " 'station_name': station_info['stationName'],\n", 237 | " 'latitude': station_info['latitude'],\n", 238 | " 'longitude': station_info['longitude'],\n", 239 | " 'weekday': weekday_index < 5,\n", 240 | " 'hour': the_hour,\n", 241 | " 'minute': the_minute,\n", 242 | " 'total_docks': station_info['totalDocks'],\n", 243 | " 'available_docks': station_info['availableDocks'],\n", 244 | " 'available_bikes': station_info['availableBikes']\n", 245 | " }\n", 246 | " yield station_data, 1" 247 | ], 248 | "language": "python", 249 | "metadata": {}, 250 | "outputs": [] 251 | }, 252 | { 253 | "cell_type": "markdown", 254 | "metadata": {}, 255 | "source": [ 256 | "Notice that I'm grouping by station_name as well as id... That's more for convenience than anything. I don't believe that will have changed in the time since I've started collecting data. I want it in the key just to make sure it all gets into my final results.\n", 257 | "\n", 258 | "### Step 2: Sum up the tallies for each unique key\n", 259 | "MRJob ensures that when it calls our reduce function that we get all of the tallies that were associated with a given key all at once." 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "collapsed": false, 265 | "input": [ 266 | "def group_minutes_by_station(self, station_data, tallies):\n", 267 | " tally_total = sum(tallies)\n", 268 | " yield station_data, tally_total" 269 | ], 270 | "language": "python", 271 | "metadata": {}, 272 | "outputs": [] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "metadata": {}, 277 | "source": [ 278 | "When MRJob calls this, all I need to do is sum up all of the tallies for this key and then I know how many times this exact occurence of weekday, hour, minute, etc. happened." 279 | ] 280 | }, 281 | { 282 | "cell_type": "markdown", 283 | "metadata": {}, 284 | "source": [ 285 | "### Step 3: Reshaping the data\n", 286 | "Now I'll have a perfectly valid representation of how often I see these results but it's a really long linear list. Instead I really want it all organized into more of a ferquency distribution one might expect in a histogram.\n", 287 | "\n", 288 | "To do that, I need to move the total_docks, available_docks, and available_bikes out of the key and move it into value. This way in my next reduce step, I'll be able to take all of the different occurences of those values and add their tallies into the appropriate bucket.\n", 289 | "\n", 290 | "This is the mapping step in this transformation:" 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "collapsed": false, 296 | "input": [ 297 | "def organize_into_buckets(self, station_data, tally_total):\n", 298 | " metrics = {\n", 299 | " 'total_docks': station_data['total_docks'],\n", 300 | " 'available_docks': station_data['available_docks'],\n", 301 | " 'available_bikes': station_data['available_bikes'],\n", 302 | " 'occurences': tally_total\n", 303 | " }\n", 304 | " del station_data['total_docks']\n", 305 | " del station_data['available_docks']\n", 306 | " del station_data['available_bikes']\n", 307 | " yield station_data, metrics" 308 | ], 309 | "language": "python", 310 | "metadata": {}, 311 | "outputs": [] 312 | }, 313 | { 314 | "cell_type": "markdown", 315 | "metadata": {}, 316 | "source": [ 317 | "### Step 4: Create the frequency distributions\n", 318 | "Now we'll take this new data and since it will be grouped by everything BUT the dock and bike availability, this function should be called once for each bike station, for each minute in each hour and once on weekdays and once again on weekends.\n" 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "collapsed": false, 324 | "input": [ 325 | "def create_frequency_distributions(self, station_data, all_station_metrics):\n", 326 | " available_bikes_frequencies = [0]*100\n", 327 | " total_docks_frequencies = [0]*100\n", 328 | " available_docks_frequencies = [0]*100\n", 329 | " for station_metric in all_station_metrics:\n", 330 | " available_bikes_frequencies[station_metric['available_bikes']] += station_metric['occurences']\n", 331 | " total_docks_frequencies[station_metric['total_docks']] += station_metric['occurences']\n", 332 | " available_docks_frequencies[station_metric['available_docks']] += station_metric['occurences']\n", 333 | " station_data['available_bikes'] = available_bikes_frequencies\n", 334 | " station_data['available_docks'] = available_docks_frequencies\n", 335 | " station_data['total_docks'] = total_docks_frequencies\n", 336 | " yield None, station_data" 337 | ], 338 | "language": "python", 339 | "metadata": {}, 340 | "outputs": [] 341 | }, 342 | { 343 | "cell_type": "markdown", 344 | "metadata": {}, 345 | "source": [ 346 | "Since this is my last step, note how I'm returning 'None' and also combining all of my data together into a single object. Because I set my output protocol to be a JSONValueProtocol MRJob will automatically serialize my Python dictionary into JSON and out that to my results files.\n", 347 | "\n", 348 | "Here's the whole thing at once:" 349 | ] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "collapsed": false, 354 | "input": [ 355 | "from mrjob.job import MRJob\n", 356 | "from mrjob import protocol\n", 357 | "from datetime import datetime\n", 358 | "\n", 359 | "class AggregateDivvyFrequencies(MRJob):\n", 360 | " INPUT_PROTOCOL = protocol.JSONValueProtocol\n", 361 | " OUTPUT_PROTOCOL = protocol.JSONValueProtocol\n", 362 | "\n", 363 | " def steps(self):\n", 364 | " return [self.mr(mapper=self.organize_station_data_and_tally,\n", 365 | " reducer=self.group_minutes_by_station),\n", 366 | " self.mr(mapper=self.organize_into_buckets,\n", 367 | " reducer=self.create_frequency_distributions)]\n", 368 | "\n", 369 | " def organize_station_data_and_tally(self, _, station_update):\n", 370 | " execution_time_text = station_update['executionTime']\n", 371 | " the_datetime = datetime.strptime(execution_time_text, '%Y-%m-%d %I:%M:%S %p')\n", 372 | " the_hour = the_datetime.hour\n", 373 | " the_minute = the_datetime.minute\n", 374 | " weekday_index = the_datetime.weekday()\n", 375 | " weekday_map = [\n", 376 | " 'Monday',\n", 377 | " 'Tuesday',\n", 378 | " 'Wednesday',\n", 379 | " 'Thursday',\n", 380 | " 'Friday',\n", 381 | " 'Saturday',\n", 382 | " 'Sunday'\n", 383 | " ]\n", 384 | " station_list = station_update['stationBeanList']\n", 385 | " for station_info in station_list:\n", 386 | " station_data = {\n", 387 | " 'station_id': station_info['id'],\n", 388 | " 'station_name': station_info['stationName'],\n", 389 | " 'latitude': station_info['latitude'],\n", 390 | " 'longitude': station_info['longitude'],\n", 391 | " 'weekday': weekday_index < 5,\n", 392 | " 'hour': the_hour,\n", 393 | " 'minute': the_minute,\n", 394 | " 'total_docks': station_info['totalDocks'],\n", 395 | " 'available_docks': station_info['availableDocks'],\n", 396 | " 'available_bikes': station_info['availableBikes']\n", 397 | " }\n", 398 | " yield station_data, 1\n", 399 | "\n", 400 | " def group_minutes_by_station(self, station_data, tallies):\n", 401 | " tally_total = sum(tallies)\n", 402 | " yield station_data, tally_total\n", 403 | "\n", 404 | " def organize_into_buckets(self, station_data, tally_total):\n", 405 | " metrics = {\n", 406 | " 'total_docks': station_data['total_docks'],\n", 407 | " 'available_docks': station_data['available_docks'],\n", 408 | " 'available_bikes': station_data['available_bikes'],\n", 409 | " 'occurences': tally_total\n", 410 | " }\n", 411 | "\n", 412 | " del station_data['total_docks']\n", 413 | " del station_data['available_docks']\n", 414 | " del station_data['available_bikes']\n", 415 | " \n", 416 | " yield station_data, metrics\n", 417 | "\n", 418 | " def create_frequency_distributions(self, station_data, all_station_metrics):\n", 419 | " available_bikes_frequencies = [0]*100\n", 420 | " total_docks_frequencies = [0]*100\n", 421 | " available_docks_frequencies = [0]*100\n", 422 | " for station_metric in all_station_metrics:\n", 423 | " available_bikes_frequencies[station_metric['available_bikes']] += station_metric['occurences']\n", 424 | " total_docks_frequencies[station_metric['total_docks']] += station_metric['occurences']\n", 425 | " available_docks_frequencies[station_metric['available_docks']] += station_metric['occurences']\n", 426 | " station_data['available_bikes'] = available_bikes_frequencies\n", 427 | " station_data['available_docks'] = available_docks_frequencies\n", 428 | " station_data['total_docks'] = total_docks_frequencies\n", 429 | " yield None, station_data\n", 430 | "\n", 431 | "if __name__ == '__main__':\n", 432 | " AggregateDivvyFrequencies.run()" 433 | ], 434 | "language": "python", 435 | "metadata": {}, 436 | "outputs": [] 437 | }, 438 | { 439 | "cell_type": "markdown", 440 | "metadata": {}, 441 | "source": [ 442 | "### Debugging and running it\n", 443 | "\n", 444 | "As I said, I download some data from S3 to run through this and make sure I didn't make any bone headed mistakes. I can invoke a local run with this command in Terminal:" 445 | ] 446 | }, 447 | { 448 | "cell_type": "raw", 449 | "metadata": {}, 450 | "source": [ 451 | "python aggregate_frequencies.py ./test_data/day_201311* --output-dir ./results --no-output" 452 | ] 453 | }, 454 | { 455 | "cell_type": "markdown", 456 | "metadata": {}, 457 | "source": [ 458 | "Then when I'm happy I have it working, I can run the entire thing on EMR like so:" 459 | ] 460 | }, 461 | { 462 | "cell_type": "raw", 463 | "metadata": {}, 464 | "source": [ 465 | "python aggregate_frequencies.py s3://bozo-divvy/day-log/ --output-dir s3://bozo-divvy/day-log-frequency-results --no-output -r emr" 466 | ] 467 | }, 468 | { 469 | "cell_type": "markdown", 470 | "metadata": {}, 471 | "source": [ 472 | "This is a great chance to point out an awesome feature of MRJob. See above how I specify the output directory as '--output-dir'? I love how I don't have to specify that I'm pulling data from S3 for the EMR version. MRJob just infers it from the path I provide to it. I also have a MRJob config file that I keep in one of MRJob's default paths so I don't have to pass it in as a parameter every time I run a job. More about that here: [http://pythonhosted.org/mrjob/configs.html](http://pythonhosted.org/mrjob/configs.html)\n", 473 | "\n", 474 | "Here's an example of what the results look like:" 475 | ] 476 | }, 477 | { 478 | "cell_type": "raw", 479 | "metadata": {}, 480 | "source": [ 481 | "{\"hour\": 0, \"available_bikes\": [0, 1, 7, 11, 8, 9, 10, 16, 5, 2, 7, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \"station_id\": 247, \"total_docks\": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \"longitude\": -87.580714999999998, \"available_docks\": [0, 0, 1, 0, 0, 7, 2, 5, 16, 10, 9, 8, 11, 7, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \"latitude\": 41.795211999999999, \"station_name\": \"Shore Drive & 55th St\", \"minute\": 0, \"weekday\": true}\t\n", 482 | "{\"hour\": 0, \"available_bikes\": [0, 0, 3, 2, 6, 8, 3, 4, 0, 3, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \"station_id\": 247, \"total_docks\": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \"longitude\": -87.580714999999998, \"available_docks\": [0, 0, 1, 1, 0, 1, 3, 0, 4, 3, 8, 6, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \"latitude\": 41.795211999999999, \"station_name\": \"Shore Drive & 55th St\", \"minute\": 10, \"weekday\": false}\t\n", 483 | "{\"hour\": 0, \"available_bikes\": [0, 0, 4, 1, 5, 5, 3, 5, 1, 3, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \"station_id\": 247, \"total_docks\": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \"longitude\": -87.580714999999998, \"available_docks\": [0, 0, 1, 0, 0, 1, 3, 1, 5, 3, 5, 5, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], \"latitude\": 41.795211999999999, \"station_name\": \"Shore Drive & 55th St\", \"minute\": 11, \"weekday\": false}" 484 | ] 485 | }, 486 | { 487 | "cell_type": "markdown", 488 | "metadata": {}, 489 | "source": [ 490 | "## Modeling with our data\n", 491 | "\n", 492 | "Now to get back to our original question. I want to get to being able to point at a given station on the weekday or weekend at a given moment in time and ask how likely I am to find a bike available.\n", 493 | "\n", 494 | "First I'll need to find the ID of the station that's closest to me." 495 | ] 496 | }, 497 | { 498 | "cell_type": "code", 499 | "collapsed": false, 500 | "input": [ 501 | "import json\n", 502 | "results_filepath = '/Users/justin/Documents/Code/divvy_probability/results.txt'\n", 503 | "my_longitude = -87.664362\n", 504 | "my_latitude = 42.007758\n", 505 | "closest_station = None\n", 506 | "closest_station_distance = float('inf')\n", 507 | "\n", 508 | "with open(results_filepath, 'r') as results_file:\n", 509 | " for line in results_file:\n", 510 | " station_data = json.loads(line)\n", 511 | " station_latitude = station_data['latitude']\n", 512 | " station_logitude = station_data['longitude']\n", 513 | " distance = math.sqrt(math.pow(station_latitude-my_latitude,2)+math.pow(station_logitude-my_longitude,2))\n", 514 | " if distance < closest_station_distance:\n", 515 | " closest_station = {'id': station_data['station_id'], 'name': station_data['station_name']}\n", 516 | " closest_station_distance = distance\n", 517 | " \n", 518 | "print closest_station \n", 519 | " " 520 | ], 521 | "language": "python", 522 | "metadata": {}, 523 | "outputs": [ 524 | { 525 | "output_type": "stream", 526 | "stream": "stdout", 527 | "text": [ 528 | "{'id': 294, 'name': u'Broadway & Berwyn Ave'}\n" 529 | ] 530 | } 531 | ], 532 | "prompt_number": 1 533 | }, 534 | { 535 | "cell_type": "markdown", 536 | "metadata": {}, 537 | "source": [ 538 | "Seems legit.\n", 539 | "\n", 540 | "Right now it's 12:58 PM on a weekend... Let's see how likely it is there are bikes available." 541 | ] 542 | }, 543 | { 544 | "cell_type": "code", 545 | "collapsed": false, 546 | "input": [ 547 | "import numpy\n", 548 | "matching_station = None\n", 549 | "current_frequencies = numpy.array([])\n", 550 | "current_probabilities = numpy.array([])\n", 551 | "with open(results_filepath, 'r') as results_file:\n", 552 | " for line in results_file:\n", 553 | " station_data = json.loads(line)\n", 554 | " if station_data['station_id'] == closest_station['id'] and station_data['weekday']==False and station_data['hour'] == 13 and station_data['minute']==5:\n", 555 | " matching_station = station_data\n", 556 | " current_frequencies = numpy.array(station_data['available_bikes'])\n", 557 | " break\n", 558 | "current_probabilities = current_frequencies / (1.0*sum(current_frequencies))\n", 559 | "\n", 560 | "print \"Probability of number of bikes available\"\n", 561 | "for i in range(0,16):\n", 562 | " print str(i) + ': ' + str(100*current_probabilities[i]) + '%'\n" 563 | ], 564 | "language": "python", 565 | "metadata": {}, 566 | "outputs": [ 567 | { 568 | "output_type": "stream", 569 | "stream": "stdout", 570 | "text": [ 571 | "Probability of number of bikes available\n", 572 | "0: 0.0%\n", 573 | "1: 0.0%\n", 574 | "2: 0.0%\n", 575 | "3: 6.25%\n", 576 | "4: 18.75%\n", 577 | "5: 15.625%\n", 578 | "6: 21.875%\n", 579 | "7: 6.25%\n", 580 | "8: 15.625%\n", 581 | "9: 0.0%\n", 582 | "10: 3.125%\n", 583 | "11: 3.125%\n", 584 | "12: 3.125%\n", 585 | "13: 3.125%\n", 586 | "14: 3.125%\n", 587 | "15: 0.0%\n" 588 | ] 589 | } 590 | ], 591 | "prompt_number": 65 592 | }, 593 | { 594 | "cell_type": "markdown", 595 | "metadata": {}, 596 | "source": [ 597 | "If we assume the frequencies are directly interpretable as probabilities then there is practically a 100% chance there will be a bike available. There is also an 84.375% chance that there are between 3 to 8 bikes available.\n", 598 | "\n", 599 | "Looking at this, it says it's IMPOSSIBLE that there will be no bikes available. That seems optimistic to me. Let's see what the probability distribution looks like for there NOT being a bike available." 600 | ] 601 | }, 602 | { 603 | "cell_type": "code", 604 | "collapsed": false, 605 | "input": [ 606 | "import numpy\n", 607 | "\n", 608 | "samples = map(lambda x: math.floor(100*numpy.random.beta(1, 1+sum(current_frequencies))), numpy.arange(0,1000))\n", 609 | "histogram = np.histogram(samples, bins=range(0,100))\n", 610 | "likelihood_no_bikes = histogram[0]/(1.0*sum(histogram[0]))\n", 611 | "print likelihood_no_bikes\n", 612 | "import matplotlib.pyplot as plt\n", 613 | "plt.hist(samples, bins=range(0,100))" 614 | ], 615 | "language": "python", 616 | "metadata": {}, 617 | "outputs": [ 618 | { 619 | "output_type": "stream", 620 | "stream": "stdout", 621 | "text": [ 622 | "[ 0.289 0.205 0.161 0.11 0.068 0.059 0.032 0.025 0.017 0.009\n", 623 | " 0.006 0.007 0.003 0.003 0.001 0.002 0. 0.001 0.002 0. 0.\n", 624 | " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n", 625 | " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n", 626 | " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n", 627 | " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n", 628 | " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n", 629 | " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n", 630 | " 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n", 631 | " 0. ]\n" 632 | ] 633 | }, 634 | { 635 | "metadata": {}, 636 | "output_type": "pyout", 637 | "prompt_number": 100, 638 | "text": [ 639 | "(array([289, 205, 161, 110, 68, 59, 32, 25, 17, 9, 6, 7, 3,\n", 640 | " 3, 1, 2, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0,\n", 641 | " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", 642 | " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", 643 | " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", 644 | " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", 645 | " 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n", 646 | " 0, 0, 0, 0, 0, 0, 0, 0]),\n", 647 | " array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", 648 | " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,\n", 649 | " 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,\n", 650 | " 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,\n", 651 | " 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,\n", 652 | " 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]),\n", 653 | " )" 654 | ] 655 | }, 656 | { 657 | "metadata": {}, 658 | "output_type": "display_data", 659 | "png": "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\n+s9//mNUP+zZs0dOp1M5OTmxbfGef3V1tdLT05WZmamWlpYJrz9nn6BdHHPwpy8pKUnPPfec/vKX\nv+i9997TL3/5S124cEE1NTXy+Xzq6OhQUVGRampq5rvUOVFbWyuv1xt7U9/UfpCk73//+/rGN76h\nCxcu6M9//rMyMzON64+uri698MILOn/+vD788EONjIyovr7eqH7YvXu3mpubx2wb7/kHg0GdPn1a\nwWBQzc3N2r9/v0ZHR+M3YM+Rd955x96yZUtsvbq62q6urp6r5u863/zmN+3XXnvNXrt2rd3X12fb\ntm339vbaa9eunefKZl84HLaLiorsN954w37sscds27aN7Afbtu2BgQF7zZo1t203rT8+/vhjOyMj\nw+7v77cvX75sP/bYY3ZLS4tx/RAKhezs7OzY+njPv6qqyq6pqYkdt2XLFvvdd9+Ne+05u7OPRCJa\nvXp1bN3tdisSicxV83eVrq4utbW1aePGjYpGo3I6nZIkp9OpaDQ6z9XNvh/84Af66U9/qiVLbvzz\nM7EfJCkUCun+++/X7t279eCDD+rJJ5/UJ598Ylx/rFy5UocOHdKXvvQlffGLX1RycrJ8Pp9x/XCr\n8Z5/T0/PmJmOk8nTOQt7Pkx11dDQkEpLS1VbW6tly5aN2Xf9U7eL2a9//WulpKQoLy9v3M9cmNAP\n1125ckXnz5/X/v37df78eX3uc5+7bajChP746KOP9POf/1xdXV3q6enR0NCQTp06NeYYE/ohnome\n/0R9M2dhzxx86fLlyyotLdWuXbtUUlIi6eqrdV9fnySpt7dXKSkp81nirHvnnXfU1NSkNWvWqKKi\nQm+88YZ27dplXD9c53a75Xa79ZWvfEWSVFZWpvPnz2vVqlVG9ccf//hHPfzww/rCF74gh8Ohb33r\nW3r33XeN64dbjff/4tY87e7ulsvlinutOQt70+fg27atvXv3yuv16sCBA7Htfr9fdXV1kqS6urrY\ni8BiVVVVpXA4rFAopPr6en3961/Xr371K+P64bpVq1Zp9erV6ujokCS9/vrrysrK0rZt24zqj8zM\nTL333nv67LPPZNu2Xn/9dXm9XuP64Vbj/b/w+/2qr6/X8PCwQqGQOjs7lZ+fH/9iiX6DIZ7f/OY3\ndkZGhv3AAw/YVVVVc9n0vPv9739vW5Zlf/nLX7Zzc3Pt3Nxc+9y5c/bHH39sFxUV2enp6bbP57Mv\nXbo036XOmUAgYG/bts22bdvofmhvb7c3bNhgr1+/3t6+fbs9MDBgZH8cPXrU9nq9dnZ2tv3d737X\nHh4eNqofHn/8cTs1NdVOSkqy3W63/eKLL8Z9/j/5yU/sBx54wF67dq3d3Nw84fUT+kVoAIC7k6G/\nVAUAZiHsAcAAhD0AGICwBwADEPYAYADCHgAM8P9e9lpuP3MzJQAAAABJRU5ErkJggg==\n", 660 | "text": [ 661 | "" 662 | ] 663 | } 664 | ], 665 | "prompt_number": 100 666 | }, 667 | { 668 | "cell_type": "markdown", 669 | "metadata": {}, 670 | "source": [ 671 | "From this we can count up the frequencies of our hypotheses and conclude that a credible likelihood interval is a 0-7% chance that there will be no bikes." 672 | ] 673 | }, 674 | { 675 | "cell_type": "markdown", 676 | "metadata": {}, 677 | "source": [ 678 | "## Next Steps" 679 | ] 680 | }, 681 | { 682 | "cell_type": "markdown", 683 | "metadata": {}, 684 | "source": [ 685 | "Now what? Well the next step will be to enable people to explore this data. The current form of this data doesn't do much to help us with this task. Instead I'd like to make this something that my Fianc\u00e9e would be interested to see. If I can make a solid visualization out of this, I think that might do the trick. \n", 686 | "\n", 687 | "Sounds like a great topic for my next blog post. Thanks for reading!" 688 | ] 689 | } 690 | ], 691 | "metadata": {} 692 | } 693 | ] 694 | } -------------------------------------------------------------------------------- /notebooks/Slow testing with no control.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import numpy as np\n", 12 | "import matplotlib.pyplot as plt\n", 13 | "import seaborn as sns\n", 14 | "%matplotlib inline" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 117, 20 | "metadata": { 21 | "collapsed": true 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "# Let's simulate 14 days of observations\n", 26 | "n = 14\n", 27 | "\n", 28 | "# Generate a time series of fake order data\n", 29 | "# It must have a 7 day cycle\n", 30 | "# A steadily increasing slope\n", 31 | "# And noise. Plenty of random noise.\n", 32 | "time_index = np.array([i for i in range(n)])\n", 33 | "sample_data = 20/52*time_index + 20*np.sin(time_index*np.pi/3.5) + 200 + np.random.normal(loc=0, scale=5, size=n)" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 118, 39 | "metadata": { 40 | "collapsed": false 41 | }, 42 | "outputs": [ 43 | { 44 | "data": { 45 | "image/png": "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\nQRMRERFJMkrQRERERJKMEjQRERGRJKMETURERCTJKEETERERSTJK0ERERESSjBI0ERERkSSjBE1E\nREQkyShBExEREUkyStBEREREkowSNBEREZEkowRNREREJMkoQRMRERFJMkrQRERERJKMEjQRERGR\nJKMETURERCTJKEETERERSTJK0ERERESSjBI0ERERkSSjBE1EREQkyShBExEREUkyStBEREREkowS\nNBEREZEkowRNRETi4s1Lg/SPzjodhkhaUIImIiKb1js8w599+yJf+uZFbNt2OhyRlKcETURENu10\n5wgAPcMzXLwx5nA0IqlPCZqIiGzama4RXJE/P/PmTUdjEUkHXqcDEBGR1DYxs8i1vil2bSsB4OKN\ncW4OTrOtusjhyERSl1bQRERkU852LW9vHmqr5KkHtgHwL29pFU1kM5SgiYjIppyJ1J8daKtgX0s5\ndRUFvHVpiNHJBYcjE0ldStBERGTDFpdCXOoep76ygKqSPFwuF++/v5GwbXP8pN/p8ERSlhI0ERHZ\nsIs3xggEwxxsrbj1uXe11+ArzOb7Z/uYWwg4GJ1I6lKCJiIiGxbd3jzUVnnrc1leN8eONLK4FOKl\nM31OhSaS0pSgiYjIhoTDNmevjuAryKa59p0nNt93sI6cbA/HT/oJBMMORSiSupSgiYjIhlztm2R6\nLsCB1grcLtc7vpafm8V7D9QxObPEG5cGHIpQJHUpQRMRkQ25vb1ZserXnzzaiMft4pm3/IQ1/klk\nXZSgiYjIhpzpGiE7y83uptJVv15WnMvR3VX0jcxy4droFkcnktqUoImIyLoNjM3RPzrHnuYysrM8\nd33cU/dHGtdq/JPIuihBExGRdVvt9OZqtlUXsae5lCs3J7jeP7UVoYmkBSVoIiKybmc6h3EB+1vL\n13zsUw80AVpFE1kPJWgiIrIu03NLdPZOsqPBR3F+9pqPb28upbGqkJPWEEMT81sQoUjqU4KWgUYn\nF1hYDDodhoikqHNXR7Htu5/evJPL5eKp+7dh23D8LY1/EomFErQMc6V7nP/8Zz/gS18/53QoIpKi\novVnK8c7reXo7irKinN45XwfM/Ma/ySyFiVoGWRsaoEvfesCobDNiUuD6kskIusWCIa4cH2M6rJ8\nassLYv4+r2d5/NNSIMyLp3oSGKFIelCCliECwTBf/MYFpucClBblMD23hH9wxumwRCTFXO4eZzEQ\n4tA6Vs+iHjlQR16Ol+ff7iEQDCUgOpH0oQQtQ3z1eAfX+6d4954afuK9LcDyjVZEZD1ubW/GWH+2\nUl6Ol/cdqmNqLsBrFzT+SeRelKBlgO+f6eXls31sqyrk554ytDeXAXDpxpjDkYlIKgnbNme6RijM\ny6K13reh53jisMY/icRCCVqau9o3yVePd1CQ6+UTH91HTpaHksIcttUU0eGfIBAMOx2iiKSI7oFp\nJmaWOLCjHLfbtfY3rKK0KId37almcGyOs5HVOBH5YUrQ0tjk7BJ/8o3lQwG//GN7qCzJu/W1g22V\nLAXDXO2ddDBCEUklp29tb957esBaouOfvveWGteK3I0StDQVDIX5029eYHx6kY8+0sLe7e/s9n0g\ncoO9pDo0EYnRmc4RvB43e7avPhw9VvWVhezfUU5XzyRdepMosiolaGnqH166iuWf4PDOSj74rqYf\n+vreHeW4XS4uqw5NRGIwPDFPz/AM7c2l5GZ7N/18GqIucm9K0NLQGxcHePaEn9ryfH7xQ7txuX64\nViQ/N4uWumKu9U8xt6CpAiJyb2e6Nn56czVmWwnNNUWc7hhmYGwuLs8pkk6UoKUZ/9AMf/m9K+Rm\ne/jkR/eRl3P3d7rtzaXYNlg3tc0pIvcWba9xYEd8EjSXy8VTD2zDBp49ofFPIndSgpZGZhcCPP31\ncywFw3zsw+1rdvm+3W5DCZqI3N3cQoAO/wTba4soLcqJ2/MeNpVU+HJ57Xw/U7NLcXtekXSgBC1N\nhMM2f/7tSwxPLPDhB5u4b+fap6xa6orJznJzqVt1aCJyd+eujRIK25s+vXknj9vNk0cbCQTDvKDx\nTyLvoAQtTXzr1eucvzbK3u1lfOShlpi+x+txYxpL6R+dY3x6McERikiqim5vbmS801oe3l9HQa6X\nF071shjQ+CeRKCVoaeB0xzD/9PoNKny5fPxH96yrgWR78/JxeU0VEJHVBENhzl8bpcKXS31l7MPR\nY5WT7eHR+xqYmQ/w6rn+uD+/SKpSgpbi+kdn+X++c4lsr5tPfnQfhXlZ6/p+1aGJyL1Y/gnmF0Mc\nbK1Y9UR4PDx+uAGvx82zJ24SDmv8kwgoQUtp84tBnv76eRaWQvz8B3axrbpo3c9RX1lAUX4Wl7rH\nsDUXT0TucGt7M07tNVbjK8jmwb01DE8scKpjOGGvI5JKlKClKNu2+co/X6Z/dI4njjTw7j01G3oe\nt8vF7qZSJmeW6B9VLyIRuc22bc50DpOX46WtsSShr/X++xtxAd9786beLIqgBC1lfe/Nm7xtDbOz\nsYT/7dHWTT3X7W1O1aGJyG3+oRlGpxbZv6Mcryexvy5qyws42FbB9f4pOvwTCX0tkVSgBC0FXbw+\nxj9+/yolhdn86kf2bvrGefuggOrQROS26PSARG5vrvTUAxr/JBKlBC3FjEzM82ffvojb5eITP74P\nX0H2pp+zwpdHVWkeln+cUDgchyhFJB2c7hzB43axd3v5lrxeW0MJO+qLOXt1lN6R2S15TZFkpQQt\nhSwFQjz9jfPMzAf4V0/uZEe9L27P3d5UyvxiiBv903F7ThFJXWNTC3QPTGO2lZCfu/nh6LGKDlF/\n9i2toklmU4KWImzb5n89Y3FzcIaH99fy3gN1cX1+1aGJyEpnr44CcCjO0wPWcqitkqrSPH5wcYCJ\nGTXQlsylBC1FvHCql9cvDLC9toiffXJn3PsR7WoqxYXq0ERk2a3h6K1bs70Z5Xa7eP/92wiGbJ5/\nW+OfJHMpQUsBnT0T/O3znRTlZ/GJH99HltcT99cozMtiW00RXb2TLC5p3IpIJptfDHK5e4zGqkIq\nfHlb/vrv2VtDUX4WL57qZX4xuOWvL5IMlKAlufHpRf7kGxewbfiVH9tLWXFuwl6rvbmUUNimo0dH\n3EUy2cXrYwRD9pad3rxTdpaHx+9rYG4xyCsa/yQZKmGVn8YYL/AVoBnIBj4P3AT+GAgCi8DPWZY1\nbIz5JeDjQAD4vGVZ301UXKkkGArzpW9eYHJ2iZ96rJXdTaUJfb32pjK+98ZNLt8YZ1/L1m5riEjy\niLbXOOhQggbw6H31/PMb3Rw/cZPHD9fjcWs9QTJLIn/ifxYYsSzrEeAp4Gngj4BPWJb1GPAN4NeM\nMdXAp4B3Rx73u8aY9Q2UTFN/83wnXb2T3L+7iiePNib89doafHg9bh0UEMlgoXCYs10jlBRm07SB\n8XHxUpSfzXv21zI6tciJK0OOxSHilEQmaF8DPhf5s4fl1bGfsizrfORzXmABuB941bKsoGVZU0An\nsD+BcaWEV8/18+KpXuorC/iFD+xO2JDilbKzPLQ1+Lg5NMPU3FLCX09Ekk9XzySzC0EOtlVuyX3n\nXt5/tBGXa7lxrcY/SaZJWIJmWdacZVmzxpgi4O+Bz1qWNQRgjHkQ+ATwh0AxMLniW2eA+DX4SkE3\nBqb4X89Y5Od4+eRH95GTHf9DAXcTnSpwpVunOUUy0VZPD7iXqtJ8Du+s5ObgDJd1T5IMk9Dug8aY\nRuDrwNOWZf1d5HM/Bfw68EHLskaNMVMsJ2lRRUBMVeqVlc4tvyfK5MwiX/rWRULhMP/xX9/P3p3V\nCXut1a7fgwcb+MfvX+PawAwfemRzMz7TXTr+/G0VXbvNSdT1s22bc9fGyMvx8PDhxoScGF+vn3lq\nNyetYV443cd7jzbF5Tn187dxunZbJ5GHBKqBZ1iuOXsx8rmfZfkwwPssy4omYW8Bv2OMyQbygF3A\nhVheY3g4vbreh8Jh/uDvzjI8Ps9HHtpOU0V+wv6OlZVFqz63L8dDXo6XU1cG0+76xtPdrp+sTddu\ncxJ5/fpGZukfmeWwqWRifC4hr7FepXledjaWcMoa4tTFfhqrCjf1fPr52zhdu81Zb3KbyBq0XwdK\ngM8ZY140xrwM/A+gEPiGMeYFY8xvWZY1CHwBeBV4DviMZVkZWQD19e9f43L3OAdbK/jwe5odicHt\ndrG7qZSRyQWGJuYdiUFEnJFM25srRcc/PaPxT5JBEraCZlnWp4FPx/jYLwNfTlQsqeDElSG+9+ZN\nqkvz+NiH23E7WJzb3lzKqY5hLt0Yo+pgvWNxiMjWOt05jMsF+3ckV4K2v7Wc2vJ83rw0yEcfaUlo\nP0iRZKHGMkmgd3iGr3z3MjlZHj750X1bOph4NbfncqooVyRTTM4uca13iraGEgrzkqvTkdu1PP4p\nFLZ57qTGP0lmUILmsLmFIE9//TyLgRC/+KHd1Fdurr4iHqpL8ygtyuFK9zhhHW0XyQjnukawSb7t\nzah376nBV5DNS2d6mVvQ+CdJf0rQHBS2bf7iO5cYHJ/nAw9s4+iuKqdDAsDlctHeXMrMfAD/4IzT\n4YjIFjgdGY5+sDU5E7Qsr5snjjSwsBTi5bN9TocjknBK0Bz0nddvcKZrhN1NpXz0vS1Oh/MOt7Y5\nuzVVQCTdLQZCXLoxRm15PtVl+U6Hc1fvO1RPTpaH4yf9BENhp8MRSSglaA45d3WEb71ynfLiHH7l\nx/Yk3Zy59sjcT9WhiaS/yzfGWQqGOdRW6XQo91SQm8XDB2oZn17kzUuDTocjklDJlRVkiMHxOf78\n25fweNx84qP7KMrPdjqkH+IrzKG+ooBO/wSBoN6piqSz053DgLPD0WP15NFG3C4Xz7yl8U+S3pSg\nbbHFpRBf/Pp55haD/Nz7Dc01xWt/k0N2N5eyFAxztXdy7QeLSEoK2zZnu0Yozs+ipTZ570dRFb48\nju6uomd4lgvXVYIh6UsJ2haybZv/+b3L9AzP8uh99Ty0v9bpkO5JdWgi6e963xRTcwH2t1bgdjs7\nHD1W0ca1//KmGtdK+lKCtoWOn/Dz1uUhWut9/MzjbU6HsybTWILb5VIdmkgaS9bpAffSVFPE7qZS\nLneP0z2g0UOSnpSgbZHL3eN87cWr+Aqy+dWP7MXrSf5Ln5fjpaW+mOv9U8wtBJwOR0QS4HTnCFle\n960V81Tx1AORVTSNf5I0lfxZQhoYm1rgT791AZcLfvUjeyktynE6pJi1N5Vi23Dl5sTaDxaRlDI4\nPkffyCx7msvIyfI4Hc667N1eRkNlAScuDzEyqbnBkn6UoCVYIBjii984z/RcgJ9+vI2djSVOh7Qu\n0XfVl7XNKZJ2zkab06bQ9maUKzL+KWzbPHvC73Q4InGnBC3Bvnq8g+v90zy4t4bH7ku9weMtdcXk\nZHl0UEAkDZ3uHMEFHEjS6QFreaC9mtKiHF4528+syjAkzShBS6CXzvTy8tl+tlUX8nPvN7hcqXFC\naiWvx43ZVkL/6BxjUwtOhyMicTIzH6CzZ5KWumJ8BcnXizEWXo+bY0caWQyEeOl0r9PhiMSVErQE\nudo7yVef7aAg18snf3wf2SlW37FSdKrA5W5tc4qki/NXRwnbdkpub670yIE6crM9PHeyR021Ja0o\nQUuAydkl/uSbFwjbNr/yY3upKMlzOqRN2R3th6Y6NJG0cXt6QHKPd1pLfq6X9x2sZ3J2iR9cHHA6\nHJG4UYKWAH/xnUuMTy/yE+/dwZ7tqXV0fTX1lQUU52dxqXtMo1VE0kAgGOb89TGqSvKoK0/e4eix\neuJIAx738vinsO5RkiaUoMXZ1OwSF6+P0drg4wORPj2pzu1ysbu5jMmZJfpG55wOR0Q2ybo5zuJS\niINtFSlZG3unsuJcHmivpn90jnNdo06HIxIXStDirMO/3C9sf0t5Wtz4oqJ1aJdu6DSnSKo73Zl6\n0wPWcmv8kxrXSppQghZn0QQt1fqdrWV3c+SggOrQRFKabduc6RqhINdLa4PP6XDipqGqkL0tZXT4\nJ7jWN+V0OCKbpgQtzjr8E3g9brbXFjsdSlxV+PKoKs3jys1xQmGdlBJJVTcHZxifXmT/jnI87vT6\nFXB7iHq3w5GIbF56/et02NxCAP/QDDvqisnypt+lbW8uY2EpxPV+DScWSVXR05uHUvz05mp2N5Wy\nrbqQtzuGGRpXvayktvTLIhzU1TuJDbSl2fZmlOrQRFLfmc4RvB5XWpwwv5PL5eKpB7Zh2/CMxj9J\nilOCFkdWpP7MpGmCtqupFBfqhyaSqkYnF7g5NMOubaXk5XidDichju6qorw4l9fO9TM9t+R0OCIb\npgQtjjr8E7hdLnbUp1f9WVRhXhbbaoq42jvJ4lLI6XBEZJ3OdKXf6c07edxunjzayFIwzIunNP5J\nUpcStDhZDIS40T9NU00Rudnp+c4UoL25lFDYpqNnwulQRGSdzkTqz1J1OHqsHj5QS36Ol+dP9bAU\n0JtJSU1K0OLkWu8kobCdttubUe23xj6pDk0klcwtBLlyc4Km6iLKinOdDiehcrO9PHpfPdNzAV67\noPFPkpqUoMVJtP6srTF9+gqtpq3eh9fjVh2aSIq5cH2UUNhO6+3NlR4/3IDXExn/FNb4J0k9StDi\npLNnEoC2hvReQcvO8tDW4MM/NMPUrApwRVLFmcj0gIMZkqCVFObw7j01DI3P35qcIJJKlKDFQTAU\n5mrvJA2VBRTmZTkdTsK1R6YKXLmpVTSRVBAMhTl3dZTy4hwaqwqdDmfLvP/W+Cc1rpXUowQtDm4M\nTLMUDKfdeKe7UR2aSGrp7JlkbjHIwdbKtJoRvJa6igIOtlZwtXeKTh1skhSjBC0O0nX+5t00VReR\nn+Pl4vVxbFu1HSLJLjo9IFO2N1d6//2NAPzLmxqiLqlFCVocZFqC5na72N1UyujUAsMT806HIyL3\nYNs2ZzpHyM32YLZlxj1qpZ2NJWyvLeZM5wj9o7NOhyMSMyVomxQO23T2TFBVmkdJYY7T4WyZ3c3R\nsU+qQxNJZr0js4xMLrCvpRyvJ/Nu+S6Xiw88sA0beOYtjX+S1JF5/1rjrGd4hvnFUMasnkXdqkPr\nVoImksyiJxgzpb3Gau7bWUlVSR6vXxhgfHrB6XBEYqIEbZPSff7m3VSX5lFWnMPlG2OEVYcmkrTO\ndI7gdrnYt6Pc6VAc43a7ePL+RoKhMMdViyYpQgnaJmVa/VmUy+WivamM2YUg/sEZp8MRkVVMzCxy\nvX+KnY0+CnLTvwXQvRzdVQXA+S71RJPUoARtE2zbpsM/QWlRDhW+9B6dspr2W3VoarchkoxuD0ev\ndDgS5xXlZ1Nbns+V7jFC4bDT4YisSQnaJgyMzTE9F8A0lmRUb6Go3U2RBE11aCJJKTo94EAG15+t\n1NZQwsJSCP+QVv0l+SlB24Tb8zcza3szyleYQ31lAZ3+CQLBkNPhiMgKi0shLt0Yp76ygKqSPKfD\nSQptDcuzkqOj+USSmRK0TejM0PqzldqbylgKhunqnXI6FBFZ4cL1MYKhcEaf3ryTEjRJJUrQNqHD\nP0FhXhZ15flOh+IY1aGJJKczXZHpAa2qP4uqLMmjtCiHzp4JTUGRpKcEbYNGJucZnVpkZ4bWn0Xt\nbCzB43apYa1IEgmHbc52jeIryKa5tsjpcJKGy+WifXs5kzNLDE+qH5okNyVoG5Sp7TXulJfjZXtd\nMTcGpphbCDgdjogAXb2TzMwHONBagTuD30Cupn37cpPtaImKSLJSgrZBHRnaoHY17U2l2DZcuakb\nnkgyuN1eQ/Vnd2rfvtywV3VokuyUoG2Q5Z8kN9tDY1Wh06E47tbYJ9WhiSSFM50jZGe5b7XCkdu2\n1xWTk+Whs0dvKCW5KUHbgMnZJQbH5mht8OF2a/ugpa6YnGyP6tBEkkD/6CwDY3PsaS4jO8vjdDhJ\nx+Nx01JXTP/oHDPzKsuQ5KUEbQM6tb35Dl6PG9NYwsDYHGNTKrwVcZKmB6wt2m6jS9ucksSUoG2A\npQMCP6Q9spVyWVMFRBx1pnMEF7C/NXOHo68l2lxc25ySzJSgbUCHf4Isr5vmmmKnQ0kaqkMTcd7U\n3BJdvZPsaPBRnJ/tdDhJq6W2GLfLpYMCktSUoK3T3EKAnqEZWmqLyfLq8kXVVxZQnJ/FpRvjagAp\n4pBzXaPYtk5vriUvx0tjdSHX+6dYCmhMnSQnZRjr1NkziY22N+/kcrloby5jcnaJvpFZp8MRyUjR\n+rODrUrQ1tLW4CMUtrkxMO10KCKrUoK2Trca1G5Tgnan6JH+S6pDE9lygWCIC9dHqS7Lp7a8wOlw\nkl5bg+rQJLkpQVunDv8EHreL1jqf06EknWgd2mW12xDZcpdujLMU0HD0WLXWa3C6JDclaOuwuBTi\nxsA0TTVF5GSrv9Cdyn25VJfmceXmOMFQ2OlwRDKKtjfXp7Qoh8qSXLp6JgmrblaSkBK0dbjaN0ko\nbKv+7B7am8tYWApxo191HSJbJWzbnOkaoTAv69bKkKytraGEucWg6mYlKSlBW4db9WcNStDu5lYd\nmtptiGyZG/3TTM4scWBHuaabrEO0Ya22OSUZKUFbhw7/BC6grVHvUO9mV1MpLnRQQGQrnekaBuCg\npgesiw6dSMvtAAAgAElEQVQKSDJTghajYCjM1b4p6isLKcjNcjqcpFWYl0VTTRFXeydZWAo6HY5I\nRjjTOYLX42bPdg1HX4/a8nwK87Lo9GsFTZKPErQY3eifJhAMa/5mDNqbywiFbTp00xNJuOGJeXqG\nZ2lvLiU32+t0OCnF5XLRWu9jdGpBc4Ql6ShBi5HlX96yU/+ztbU3qw5NZKuc6Yyc3lR7jQ25NTi9\nV28oJbkoQYtRdDVoZ4Pqz9bSWu/D63FzSf3QRBIu2l7jwA4laBtxqw5NK/6SZJSgxSActunqnaC6\nNA9fYY7T4SS97CwPbQ0+eoZnmJpdcjockbQ1uxDAujnB9toiSot0b9qIppoivB63DgpI0lGCFgP/\n0AzziyH1P1uH6DbnZZ3mFEmY81dHCdu2Tm9uQpbXTUttEf7hGeYWdLBJkocStBjc6n+mBC1m0bFP\nqkMTSZzo9uYhTQ/YlLbGEmwbrvVpm1OShxK0GEQTNJ3gjF1TdREFuV4u3RjH1hgVkbgLhsKcvzZK\nhS+X+koNR98MzeWUZKQEbQ22bWP5JygrzqHcl+t0OCnD7Xaxa1spo1MLDE/MOx2OSNqxbk4wvxji\nYFsFLpemB2xG662JAqpDk+ShBG0N/aNzzMwH2NlYopvgOt1ut6E6NJF4i7bX0Pbm5hXkZlFfWcC1\nvimCobDT4YgAStDWpPmbG6c6NJHEsG2bM13D5OV4aVPpRVy0NZSwFAxzc3DG6VBEACVoa+ro0QGB\njaoqzaO8OIfL3eOEw6pDE4kX/9AMo1OL7N9Rjtej23g8tGmbU5KM/mXfg23bWDcnKMzLorY83+lw\nUo7L5WJ3UxmzC0FuDk07HY5I2ri1vanpAXFzO0HTQQFJDkrQ7mF0coHx6UWM6s827FY/NNWhicTN\n6a4RPG4Xe7eXOx1K2igvzqW0KIfOngmdPJekoATtHiz1P9u03apDE4mrkYl5ugemMdtKyM/VcPR4\ncblctDX4mJ4LMDSuk+fiPCVo96AGtZvnK8imobKAjp5JAsGQ0+GIpLy3Lg0AcEjTA+IuOpezQ3Vo\nkgSUoN1DR88kudkeGqsKnQ4lpbU3lxEIhulSbYfIpr15cTlBO9Cq7c14Ux2aJBMlaHcxObPI4Ngc\nbQ0luN2qP9uM3U2RfmiayymyKfOLQc51jtBYVUiFL8/pcNJOQ2UhudkeJWiSFJSg3UVH5B/ozkaf\nw5Gkvp2NJXjcLjWsFdmki9fHCIbCOr2ZIG63i9Z6H4Njc0zNLjkdjmQ4JWh30XEzOn+z1OFIUl9e\njpeWumJuDEwxuxBwOhyRlHU60l7joBK0hNE2pyQLJWh3YfknyPK6aa4tcjqUtNDeXIZtw5VuFd+K\nbMRiIMTZrhHKfbk0Veu+lCjRgwJdvbpXibOUoK1idiFA7/AMO+qK1aU7Tm7N5exWuw2RjXjj4gBz\ni0EeO9KovowJtL2uGI/bpRU0cZyyj1V09kxio/Ya8bS9tpicbI8a1opsgG3bPHeyB4/bxYfes93p\ncNJaTpaHbdVFdA9MsxhQayBxTsK6HBpjvMBXgGYgG/i8ZVn/FPnaHwBXLMv688jHvwR8HAhEHvfd\nRMUVC/U/iz+vx41pLOHc1VHGphYoK851OiSRlHGpe5zekVne1V5NuS+P4WGNTkuktgYf1/unuN43\nxa4m1SGLMxK5gvazwIhlWY8AHwCeNsaUG2P+GfiR6IOMMdXAp4B3A08Bv2uMyUpgXGvq8E/gcbvY\nUacTnPHUfmuqgFbRRNbj+Ak/AE8caXQ4kswQrUPT4HRxUiITtK8Bn1vxOgGgEPgt4K9XPO5+4FXL\nsoKWZU0BncD+BMZ1TwtLQboHpmmuKSIn2+NUGGlJdWgi6zcwNse5q6PsqC+mpa7Y6XAygk5ySjJI\n2BanZVlzAMaYIuDvgc9altUNdBtjPrjiocXAyn8FM4BjS1dX+6YIhW1tbyZAfUUBxQXZXL4xjm3b\nKnQWicHzJ3sAOKbVsy1TXJBNdVk+Xb2ThMO2mpWLIxI6adcY0wh8HXjasqy/u8vDplhO0qKKgJjW\nlSsr43/UvOft5Zvhkb21CXn+ZOLE3+/Qziq+f7qH+TA01aT29U33n49E0rWLzcx8gNcu9FPhy+X9\n72m5dapc129zYrl++3ZU8NyJm8yFbLZXa+UySj97WyeRhwSqgWeAT1iW9eI9HvoW8DvGmGwgD9gF\nXIjlNRJRKHvWGsIFVBVlp3UhbmVlkSN/v5baQr5/Gl471UP+0dRdEXDq+qUDXbvYPfPWTRaWQnz4\nwWbGx2YBXb/NivX6NVbkA/DmuT4Ks9TwAPSzt1nrTW4T+VP360AJ8DljzIvGmBeMMTmRr9nRB1mW\nNQh8AXgVeA74jGVZjszYCATDXO2boqGqkIJcR88ppK32puhBAdWhidxLKBzmuZM9ZHvdPHKgzulw\nMk5bow4KiLMSWYP2aeDTd/nab9/x8ZeBLycqlljdGJgiEAyr/iyByn25VJflc8U/QTAUViNgkbs4\n0znC6NQC7ztYR2Ge3jButerSPIrys5b7YqpmVhyg344rRPufGSVoCdXeVMriUojr/VNOhyKStI5H\nDgc8rsMBjnC5XLQ1lDA+vcjo1ILT4UgGUoK2ghVJ0NqUoCVUtN2GpgqIrK57YJoO/wR7tpdRX1Hg\ndDgZS+02xElK0CLCYZuunkmqy/LxFWQ7HU5a29VUigvVoYnczXMnlxvTqrWGs24NTleCJg5Qghbh\nH5phYSmEadT0gEQryM2iubaIq31TLCwFnQ5HJKlMzizy5uVBqsvy2dtS5nQ4GW1bdSHZXrcOCogj\nlKBFWJq/uaXam8sIhe1bdX8isuzF070EQzbHjjTgVmG6o7weNy11xfQOzzK7EHA6HMkwStAiNCB9\na+2ODCDWXE6R2wLBMC+d7iU/x8uDe2ucDkeA1oYSbOBqr7Y5ZWspQQNse3klp7w4hwpfntPhZIS2\nBh9ZXrcSNJEV3ro8yNRcgEcO1JGbndBBLxKjnTooIA5Rggb0jc4xMx/Q6c0tlOX10Nbgo2d4hslZ\nR/oSiyQV27Y5ftKPywWPHa53OhyJ2FHvw+WCTpVjyBZTgsbtf3ja3txa7c3LBdCXu3WaU6TDP8HN\nwRnu21mplfwkkpfjpbGykGv90wSCYafDkQyiBA01qHWK6tBEbos2plVrjeTT2uAjGArTPag5lLJ1\nMj5Bs20byz9BUX4WNWX5ToeTUZqqiyjI9XL5xhi2ba/9DSJpanhintOdwzTVFN1qjirJI9oPTe02\nZCtlfII2MrnA+PQiOxtLNGtti7ndLnY1lTI6tcjQxLzT4Yg45vm3e7BtOHakQfehJHRrooBfBwVk\n62R8gqb2Gs6K1qFpm1My1fxikFfO9eMryObormqnw5FVlBXnUl6cS1fvJGGt9ssWyfgE7VaD2gYl\naE6IzuXU2CfJVK9fGGB+Mcijh+rJ8mb8LTlptTX6mJkPMDA653QokiEy/m7Q6Z8gL8dDY1Wh06Fk\npKqSPMqLc7jSPU44rHemklnCts1zJ/14PS7ed0itNZKZ6tBkq2V0gjYxs8jg+DxtDSW43ar7cILL\n5WJ3cxmzC0FuDumElGSW81dHGRyf513tNRQXZDsdjtxDtA5Ng9Nlq2R0gqb6s+Rwe5tTdWiSWY6f\n9APwxJEGhyORtdRVFJCf49VEAdkyStBQgua03U3RgwKqQ5PM0Ts8w6Ub4+zaVsK26iKnw5E1uF0u\nWht8DE3MMzGz6HQ4kgHWnaAZY4qNMXsSEcxW6/BPkO1101yjm6OTfAXZNFQW0NkzSSAYcjockS0R\nbUz7hBrTpgxtc8pWiilBM8Z8zBjzFWNMJXAJ+AdjzO8kNrTEmpkP0DM8S0tdMV5PRi8kJoX25jIC\nwbBufJIRpueW+MHFASp8uRxsrXA6HIlR9KBAhw4KyBaINTP5VeA/AD8DfAvYBzyVqKC2QjQR0PZm\ncrhVh9atOjRJfy+f7SMQDPPEkUYdUEoh22uL8HpcqkOTLRHz0pFlWWPAB4HvWpYVBFJ6mq/mbyaX\nnY0leNwu1aFJ2guGwrxwqpecbA8P7at1OhxZhyyvh+aaYvyDMywsBZ0OR9JcrAnaRWPMd4AW4Dlj\nzNeAE4kLK/Es/wQet4uWes29Swa52V521BVzo3+a2YWA0+GIJMzb1jDj04s8vK+W/Fyv0+HIOrU1\n+AjbNtf6ppwORdJcrAnaLwK/BzxgWdYS8NfAxxIWVYItLAXpHpimubaInCyP0+FIxO7mMmzgirY5\nJY0dP+nHBTyu1hopqTU6l1PbnJJgsSZobuBh4I+MMcXAoXV8b9K52jtF2LY13inJqA5N0t3V3kmu\n9U1xoLWC6tJ8p8ORDWitjyZoOiggiRVrkvVFoAA4DASBVuDLiQoq0Sz1P0tK22uLycn2qGGtpK1o\nY9pjWj1LWUX52dSW53O1d4pQOOx0OJLGYk3QDluW9RkgYFnWHPDzLK+ipaRO/wQubve0keTg9bjZ\n1VjC4Ngco5MLTocjEldjUwucvDJMfWUBu5pKnQ5HNqGtoYTFQAj/0IzToUgaizVBs40x2UB0mnXF\nij+nlEAwzNW+KRqrCsnPzXI6HLlDe7OmCkh6evF0L2Hb5tiRRlwutdZIZdE3951+1aFJ4sSaoP0R\n8BxQY4z5I+Ak8IcJiyqBrvdPEQyFtb2ZpPbtKAfgtQsDDkciEj+LgRAvne6lMC+Ld7VXOx2ObFJb\n5PdHZ68SNEmcmBI0y7L+GvgV4PPANeDDlmV9JZGBJYrmbya3mrJ89mwvo8M/QffAtNPhiMTFGxcH\nmF0I8r5DdWTr5HjKq/Tl4ivIprNnAttOyc0kSQGxjnoqA+osy/oiUAj8pjGmPaGRJUg0QWtTgpa0\njkVmE0YLqkVSmW3bHD/Zg8ft4tFDOhyQDlwuF20NPiZnlhhWvawkSKxbnH8D7DLGPA78BPBt4E8T\nFlWChMJhunonqSnLx1eQ7XQ4chd7W8qoKcvnzUuDTM4sOh2OyKZcujFO38gsR3dVUVqU43Q4EifR\nuZydfrXbkMSINUErtSzraeAjwF9FtjxTromPf2iGhaWQtjeTnNvl4tiRBkJhmxdP9zodjsimRFeC\nn4isDEt6aGtUw1pJrJgb1RpjDrOcoH3HGHMQSLkZJR03NX8zVTy4t5aCXC8vnu4lEAw5HY7IhgyM\nzXHu6ig76otpqSt2OhyJo8aqQnKyPGpYKwkTa4L2a8B/B/5vy7Kusby9+e8TFlWCqEFt6sjJ9vDI\nwTqm5wK8cXHQ6XBENuT5kz3A7bpKSR8et5sd9cX0j84xM6/5wRJ/sZ7ifN6yrMcsy/qjyKceBLoT\nF1b8hW2bzp5JyotzKfflOh2OxODx+xpwu1wcP+nXSSlJOXMLAV49309pUQ737ax0OhxJgGgdWpe2\nOSUBYj3F+UljzJQxJmSMCQEB4HhiQ4uv/pFZZuYD7GzU9IBUUVacy5FdlfQMz2qAuqScl8/2sxgI\n8fjhBryelB1dLPdwe3C6tjkl/mK9a/yfwAHg74AdwL8F3khUUInQEXmHo+3N1HLs6PLW0LMn1HJD\nUkcoHOb5t3vI9rp55ECd0+FIgrTUFuN2uXRQQBIi1gRtyLKs68A5YJ9lWX8JmIRFlQBqUJuadtT5\n2FFXzNmrowyOzTkdjkhMznSOMDq1wIN7ayjM00i5dJWX46WxupDr/VMsBXSYSeIr1gRt1hjzKMsJ\n2o8YY2qAlJn2a9s2Hf4JivOzqClLue4gGS+6ivZcpOBaJNkdj/ysPq7DAWmvrcFHKGxzQ5NPJM5i\nTdA+Bfwo8C9AOWABTycqqHgbnlxgfHqRnY0lGlKcgu7bWUlpUQ6vnu9nbkGnpSS5dQ9M0+GfYM/2\nMuorCpwORxJsZ7RhrerQJM5i6mVmWdZFbrfV+InEhZMY0f5n2t5MTV6Pm8cPN/APL13l5bP9PPXA\nNqdDErmraGNatdbIDLcPCqgOTeIrpgTNGPMh4DeBCuDWEpRlWS0JiiuuVH+W+h45UMe3X7vO82/7\nOXa0AY9bp+Ik+UzOLPLW5UFqyvLZ21LmdDiyBUoKc6gqyaOrZ5KwbePWLo3ESay/5f4H8NvA48Cj\nK/5LCR09E+TleGmoLHQ6FNmgwrws3rO3ltGpRU53jDgdjsiqXjzdSzBk88SRBv2iziCtDT7mFoP0\njcw6HYqkkVjHNU1YlvXdhEaSIOPTiwyNz7N/Rzlut26YqeyJIw28eLqXZ0/6ObKryulwRN4hEAzz\n0ule8nO8PLi3xulwZAu1Nfh4/cIAnT2TWgiQuLlngmaMeSTyx8vGmC8A3wSC0a9blvVyAmOLi2jh\npuZvpr7a8gL2tZRz/too1/un2F6r2YaSPN66PMjUXICn7t9GbnbKjSqWTWhbcVDg0UP1Dkcj6WKt\nu8h/WfHnBmDfio9t4LG4RxRnmr+ZXp482sj5a6McP+Hn4z+6x+lwRIDlVj7HT/hxueCxw/oFnWlq\ny/MpzMui06+DAhI/90zQLMu6VWdmjKmyLGvIGJMP1FmW1ZXw6OKgwz9BttdNU02R06FIHLQ3l1JX\nUcCJK0P85KOtlBblOB2SCB3+CW4OzXDEVFLhy3M6HNliLpeL1nofZ7pGGJtaoKxY855l82Kdxfkp\nlnugAVQC/2SM+XjCooqTmfkAvcOz7Kj3aRZemnC5XBw70kAobPPCKTWuleQQbUz7hFprZKy2yJzn\nrl6tokl8xJq1/DLwMIBlWd3AYZab1ya1aP2ZtjfTy7v3LI/P+f6ZPhY1XkUcNjwxz+nOYZpqimiL\n9MSSzNNWH6lD0zanxEmsCVoWsLji4yWWa9CSmvqfpafsLA/vPVjHzHyANy4OOB2OZLjn3+7BtuHY\nkQZNKslgTTVFeD1uTRSQuIk1Qfsm8IIx5pPGmE8CzwLfSlxY8dHhn8DjdtFSp9N+6eax+xrwuF0c\nP9mDbSf9ewVJU/OLQV4514evIJuju6qdDkcclOV101JbhH94hrmF4NrfILKGWBO0zwBfAAzQAnzB\nsqzPJSyqOJhfDNI9MMP22mJysjxOhyNxVlqUw9HdVfSNzHLxxpjT4UiGev3CAPOLIR69r54sr+pc\nM11bYwm2Ddf6tM0pmxdrs54TlmXdB/xDIoOJp6t9y2M3ooWbkn6OHWnkjYuDHD/Rw97t5U6HIxkm\nbNs8d9KP1+PifQfVWkO4VYPY0TPJ3hbdk2RzYn3LN2iMedgYkzI9DToihZpqUJu+ttcW09rg4/y1\nUfpHNWJFttb5q6MMjs/zrvYaiguynQ5HksCOeh8uoEt1aBIHsSZoR4DvA/PGmHDkv6Q+Ptfhn8AF\ntNYrQUtnT0baGjx3Ui03ZGsdP+kHlkeQiQAU5GZRX1nAtb4pgqGw0+FIiotpi9OyrMpEBxJPgWCI\na31TNFYXkp+rkSvp7NDOCsqLc3ntQj8//kgLhXlZTockGaBneIZLN8bZta2EbdVqgi23tTaU0DM8\ny83BGR1Qk02JKXsxxvzmap+3LOu34xtOfFzvnyYYCqu9RgbwuN08friBr73Yxctn+/jgu5qcDkky\nQHTF9pga08od2hp8vHS6l86eCSVosimxbnG6VvyXDfwokLRnyqPzN1V/lhkeOVBLTpaH59/u0baC\nJNz03BI/uDhAhS+XA60VTocjSSZ6UKCzRyc5ZXNi3eJcOTQdY8x/ZbkXWlKKNqhta1CClgnyc7N4\naF8tz5/q4VTHMPfvTtr3DpIGXj7bRyAY5okjjbjdakwr71RenEtpUQ6dPRPYtq3mxbJhG23cUwhs\ni2cg8RIKh+nqnaS2PF8nqzLIE0cacAHPnvA7HYqksWAozPNv95CT7eGhfbVOhyNJyOVy0dbgY3ou\nwND4vNPhSAqLtQbtOrdHO7mAUuD3ExXUZtwcnGFxKaT6swxTXZbPgdYKznSNcLV3kh316n8n8XfS\nGmJiZoknDjfoAJLcVVtDCW9dHqKjZ4Lqsnynw5EUFesK2vuBPwYuAteBzwGfT1RQm6H5m5nrWKTd\nQbT9gUi8PXeyBxfwuFpryD2oDk3iIda3gJ8FcoE/Zzmp+zlgB/DpBMW1YR06IJCxdjWV0lBZyMkr\nw4w9ukBZca7TIUkaudo7ybW+KQ62VlBdqlURubuGykLycjxK0GRTYl1Be8CyrJ+yLOufLMv6FvCT\nwJMJjGtDwrZNh3+C8uJc/XLOQC6Xi2NHGwjbNs+fUuNaia/oyuwxrZ7JGtxuFzvqfAyOzTE1u+R0\nOJKiYk3Q/MaY1hUfVwO9CYhnU/pGZpldCGp7M4O9q72aovwsXj7Tx+JSUg+7kBQyNrXAySvD1FcW\nsKup1OlwJAVom1M2K9YELQs4a4z5njHmn4BLQL0x5gVjzAuJC299OqPbm9uUoGWqLK+HRw/VM7sQ\n5PUL/U6HI2nixdO9hG2bY0ca1TZBYhJt89SpuZyyQbHWoP3WHR8n5QlOSwcEBHj0UD3//EY3x0/2\n8N5D9bj1C1U2YTEQ4qXTvRTmZfGudvXYk9hsryvG43bR1asVNNmYWBvVfj/RgWyWHak/Ky7Ipro0\nz+lwxEG+whwe2F3NaxcGuHBtjP07yp0OSVLYDy4OMLsQ5MMPNpGd5XE6HEkROVkemmqK6B6YZjEQ\nIkc/O7JOG21Um3SGJ+aZmFliZ2OJtiCEJyIzEo+fuOlwJJLKbNvmuZM9eNwuHj2kwwGyPq31PkJh\nm+t9U06HIikobRK0W9ubDWpQKtBUU4RpLOHijXF6h2ecDkdS1KUb4/SNzHJ0VxWlRTlOhyMpRnVo\nshlpk6CpQa3c6djRyCraSbXckI2JttaIrsiKrIdOcspmpE2C1umfJD/HS0NlodOhSJI42FpBZUku\nP7g4wPScehHJ+gyMzXHu6iit9T5a6oqdDkdSUHFBNtVl+XT1ThIO22t/g8gKaZGgjU8vMjQxT1uD\nD7db9WeyzO128cThRgLBMN8/0+d0OJJinru1eqbaM9m4tgYfC0shelRqIeuUFgnare1N9T+TOzy0\nv5bcbA/Pn+ohGAo7HY6kiLmFAK+dH6CsOIfDptLpcCSFaZtTNiq9EjTVn8kd8nK8PLy/jsmZJU5c\nGXI6HEkRL5/tZzEQ4rH7GvC40+I2KQ7ZqYMCskFpcefp8E+QneWmqbrI6VAkCT1+pAEXcPyEH9tW\nHYjcWygc5vm3e8j2unnkQJ3T4UiKqyrNoyg/i86eSd1/ZF1SPkGbmQ/QOzLLjjofXk/K/3UkAapK\n8ji0s5IbA9Pq6i1rOtM5wujUAg/uq6UwL8vpcCTFuVwu2hpKGJ9eZHRqwelwJIWkfEZza/6mtjfl\nHo5FCr2Pn/A7HIkku+jPyBOHdThA4kN1aLIRKZ+gaf6mxGJnYwnbqgt5u2OYkYl5p8ORJNU9ME1H\nzyR7t5dRV1HgdDiSJm43rFWCJrFL+QStwz+Bx+1SnyK5J5fLxbEjjdg2PH9KjWtldc+pMa0kwLbq\nQrK9brp0UEDWIaUTtPnFIN2D02yvK9YQY1nT/burKS7I5uWz/cwvBp0OR5LM3EKAt64MUVWax96W\nMqfDkTTi9bhpqSumd3iW2YWA0+FIivAm6omNMV7gK0AzkA18HrgE/CUQBi5YlvWJyGN/Cfg4EAA+\nb1nWd2N5jau9k9j27WPMIveS5XXz2H31fPOV67x+YYDHVWMkK7x5aZBAMMzD+2txu9TwWuKrraGE\nKzcnuNo7yf4dFU6HIykgkStoPwuMWJb1CPAU8DTwB8BnLMt6L+A2xvyYMaYa+BTw7sjjftcYE9PR\nqY4e1Z/J+rzvYD1ej5vjJ/2EdeRdVnjlXD8uFzy4t9bpUCQN6aCArFciE7SvAZ+L/NkDBIH7LMt6\nJfK57wHHgPuBVy3LClqWNQV0AvtjeYGOmxO4XNBa74tv5JK2iguyedeeaobG5znXNep0OJIk/EMz\n3BiYZn9LOaVFOU6HI2loR70Pl+t25wGRtSRsi9OyrDkAY0wR8PfAZ4HfX/GQaaAYKAJWvqWYAdbM\nuJYCIa71T9NS76OpsTRucWeSysrMbOz7U0/u4tVz/bx0to9jD27f8PNk6vWLh2S7dt98/QYAH3q4\nJeliW00qxJjMnLp+22t9XB+YpqQ0nyxvatZN62dv6yQsQQMwxjQCXweetizrb40xv7fiy0XABDDF\ncqJ25+fvqePmOMFQmJaaYoaHp+MZdkaorCzK2OtW4HWxu6mUc10jnLrYT2NV4bqfI5Ov32Yl27UL\nhsK8cMJPUX4WzZUFSRXbapLt+qUaJ6/f9poirvVNcvJCf0ru/Ohnb3PWm9wmbIszUlv2DPCfLMv6\nq8inTxtjHon8+QPAK8AJ4CFjTLYxxgfsAi6s9fwXry1vT+1sTL0fcnHesaPLbRSOn1Tj2kx3pnOE\nmfkA795To2kkklBtjdE6NG1zytoSeTf6daAE+Jwx5kVjzAvAbwC/bYx5DcgC/sGyrEHgC8CrwHMs\nHyJYWuvJL0QStDYdEJAN2L+jnKrSPN64OMjU7Jo/bpLGXjnXD8DD+3U4QBIrumrW6ddBAVlbImvQ\nPg18epUvvW+Vx34Z+PJ6nv/KjTFqy/Mpzs/eWICS0dyRxrVfPd7BS6d7+dGHNl6LJqlrbGqBC9dH\naakrpr5y/VvdIutRVpxLeXEuXb2ThG1b7VzknlJ2PX9hKaT5m7Ip79lXQ16OlxdO9xIIhp0ORxzw\n+oUBbBse0uqZbJG2Rh8z8wEGRuecDkWSXMomaKD+Z7I5udle3nugjqnZJd66POh0OLLFwrbNq+f6\nyfa6uX9XtdPhSIa4PZdTdWhyb0rQJKM9drgelwuOn/Bjq3FtRun0TzA0Mc9hU0V+bkIPtIvcEm1Y\n26WGtbKGlE3Q3r2vlrLiXKfDkBRX4cvj8M5Kbg7N0KEGkhklejjgkQPa3pStU1dRQH6OVxMFZE0p\nm6B95t/c73QIkiaiLTeePaGWG5lifjHIyStDVJXkaSVetpTb5aK1wcfQxDwTM4tOhyNJLGUTNJF4\naaZ4vFcAACAASURBVK33sb22iDOdIwxNzDsdjmyBty4PshQM8579tbh0kk62mLY5JRZK0CTjuSIt\nN2zg+ZM9TocjWyA6GP09e2ucDkUyUPSgQIcOCsg9KEETAY7sqqKkMJtXzvUxvxh0OhxJoN7hGa71\nTbF3e7nqWMUR22uL8HpcqkOTe1KCJgJ4PW4eu6+BhaXQreJxSU+vntfkAHFWltdDc00x/sEZFpb0\nhlBWpwRNJOK9B+vI8rp57qSfcFgtN9JRMBTm9QsDFOZlcaC1wulwJIO1NfgI2zbX+qacDkWSlBI0\nkYii/Gwe3FvDyOQCZ7pGnA5HEuBs1yjTcwHetaeaLK9uf+Kc2w1rtc0pq9MdSmSFJ44st9w4rpYb\naenVc30APLy/zuFIJNO1Rk5yaqKA3I0SNJEV6isK2LO9DMs/QffAtNPhSByNTy9y7toozTVFNFZp\nMLo4qzAvi7qKAq72ThEKaxaw/DAlaCJ3OBZdRTupVbR08oOLy4PRdThAkkVrvY/FQAj/0IzToUgS\nUoImcoe9LWXUlufz5qVBJtXpOy3Yts0r5/rJ8rp5oF2D0SU5RBvWdvpVhyY/TAmayB3cLhdPHGkk\nFLZ58XSv0+FIHHT2TDI4NsfhnZXk52Y5HY4IAG2RMWOdvUrQ5IcpQRNZxYN7aijI9fLi6V4CwZDT\n4cgmvXpOvc8k+VT6cvEVZtPZM4Ftq7WPvJMSNJFV5GR7eORgHdNzAd64OOh0OLIJ84tBTlwZosKX\ni2kqdTockVtcLhdtDSVMziwxPLngdDiSZJSgidzF4/c14Ha5OH7Sr3e3KezklSEWAyEe2leLW4PR\nJcncrkNTuw15JyVoIndRVpzLkV2V9AzPcqV73Olw5P9v786j66zvO4+/76J93yVbtlb7Z8uyDDYG\nbGMMGAjEkGYhkIWTaVKSnuk20572nLYz7bSdJp1zptM26Uw3SEjSNoRAShKWsBuwbDA4YEuy7J8s\nWZYXLbb2fb13/rjXYAjY2q6e+1x9XufkIMnSvR89sa4/en7P8/vO0766DjzAjo1a3pTo825B04a1\n8gEqaCKXcdvWi1tunHU4icxHR88IzecGqCrLJidDg9El+qzKTyUhzqcNa+WXqKCJXEbFigwqVqRz\npLmbrt5Rp+PIHOnmAIl2Pq+XipXpdPSMMjw25XQciSIqaCJXcNvWVQSBF3UWzVWmZwLsb+gkJdHP\n1Ws0GF2i18W5nM1a5pRLqKCJXMEWk0d2egK19R2Mjus3XLeoP9nD4Mgk11cVEuf3OR1H5COt0VxO\n+RAqaCJX4PN62b25mImpGV470uF0HJmld5c3N2l5U6Jb+Yp0vB6PbhSQ91FBE5mFnZtWEB/n5aVf\nnNFgYxcYGJ7gSHMPqwtSWV2Q5nQckctKjPezuiCV1o5BJqe0MbaEqKCJzEJqUhw7qovoGZzgnaZu\np+PIFbx+tItAMMjOmhVORxGZlcriDGYCQV5++xwTkyppooImMmu3XlMMwPOHzjicRC4nNBi9Hb/P\no8Ho4hpb1+Xj9Xj40d5m/uvf1/Ltpxo51tZHQJtkL1t+pwOIuEVRTgo1FTnUtfRw4kwfmYn68YlG\nLe2DdPSMcu36fFKTNBhd3GFNcSbf+Np1HGjo5EBDJ/vD/8tJT2BbdSHbq4sozE52OqYsIf0LIzIH\nN129krqWHmoPt3PX9audjiMforauHUDLm+I6+VnJfHJnOZ+4oYwTZ/rZ39DJoePneepAG08daKNi\nRTrbNxZx7fp8UhL1y0esU0ETmYOqkizi/V7eOtalghaFJiZnOHjsPDnpCazXYHRxKa/Hg1mdhVmd\nxRdvW8s7TRc40NDJ0VO9tLQP8siLTVxVmcv26iKqy7Px+3S1UixSQROZg/g4H+tKsqhr6aG7f4zc\nzCSnI8klDtnzTEzO8LGtq/B6NRhd3C8hzsf1Gwq5fkMhfUMTvHE0tPR5yF7gkL1AenIc11UVsr26\nkNUFqXg8+nsfK1TQROZoU/g6tCMtPezeUux0HLnEviOh5U0NRpdYlJWWwJ3Xl3DHdatp6xpif30n\nBxu7eOHQGV44dIbivBS2Vxdx/YYCMlMTnI4rC6SCJjJHNRW5QBN1KmhRpbN3lKazA6wvySJPZzYl\nhnk8HkoL0yktTOe+Wyqpb+nhQEMnh5u7+dHeZh57pZkNZdnsqC7i6jW5xMdpkoYbqaCJzFFORiIl\nhWkcP93HxNQMCXrxiwr76zUYXZYfv8/L1WvzuHptHsNjUxxs7OJAQycNJ3tpONlLUoKPrevy2V5d\nxJriDC2BuogKmsg8XLO+gB/vbeZYWx9XVWoQt9NmAgH213eQlOBn89o8p+OIOCI1KY7dW4rZvaWY\n9u4RDjR08vrRTl470sFrRzrIy0xke3UR26sLdZbZBVTQROZha1UhP97bTF1LjwpaFDja2kv/8CQ3\nX71SyzkiwIrcFO65qYJP31jOsdN9HKjv5BdN5/lpbSs/rW1lbXEG2zcWsXVdPkkJqgLRSP+viMzD\nupIsUhL91LV0Ewyu1bKBw/Yd0WB0kQ/j9XrYUJrNhtJs7p9Yy9tNF9hf38Hx0/00nR3g319oYvPa\nPHZUF1JVmq27n6OICprIPPh8XjaUZfPmsfOcuzBCcX6q05GWrcHRSQ43d1Ocl0qJBqOLfKSkBD87\nNhaxY2MR3QNjvH60iwP1HRxs7OJgYxcZqfFs2xDasqM4T69pTlNBE5mnTRW5vHnsPEdaulXQHPRG\nQyczgSA7a4p0JlNklnIzkrh7eyl3bSvhZPsg+xs6ebOxi2cPnubZg6cpKUhj+8ZCrqsqID053um4\ny5IKmsg8VZdn4wHqWnrYs63U6TjLUmgwegc+r4frN2gwushceTweKlZmULEyg8/vruRIcw/76zuo\nP9nLIy+e4EcvN7OxPIft1YXcmqVZoEtJBU1kntKS4ylfmU7zuQGGx6Y0mNsBrR1DnOse4RqTR5p+\nyxdZkDi/j2vW5XPNunwGRiZDW3bUd3C4uZvDzd384KUT/P59V7EiN8XpqMuCBniJLEBNRS7BIDS0\n9jgdZVl6dzD6Jg1GF1lMGSnx3L51FX/2lWv5869cy+4txfQPTfDgk41MzwScjrcsqKCJLMCmihwg\ntMwpS2tiaoaDx7rISktgQ2m203FEYtaq/FS+eNtabrlmFW1dQzy5/5TTkZYFFTSRBViVn0pWWgIN\nJ3sJBIJOx1lW3rYXGJuYYcfGQm0NILIEvvbJjeSkJ/D06220tA84HSfmqaCJLIDH42FjeQ7DY1Oc\nbB90Os6ysi+8vHmDBqOLLImUpDh+bU8VgWCQh55sZGJqxulIMU0FTWSBLi5zHmnpdjjJ8nG+b5Tj\np/tZtzqTfN1ZJrJk1pVkcfvWVXT1jfHY3man48Q0FTSRBVpfmoXf59F1aEuotr4TgBs0GF1kyX1m\nVzkrclN4+e1zukEqglTQRBYoMd6PWZ3FmfPD9A6OOx0n5gUCQfbXd5AY72OLyXc6jsiyE+f38dW7\nqvB5PXzn6WOMjE85HSkmqaCJLIKai3dzntRvk5HWeKqXvqEJrqsqIEGD0UUcUVKYxid2lNI/PMm/\nPd/kdJyYpIImsgje3W6jWQUt0l6rCw9Gr9HeZyJO+vi2EipWpHOwsYs3j3U5HSfmqKCJLIL8rGQK\ns5NpbOtlalp3NkXK0Ogk7zRdYGVuCmVFGowu4iSf18sDd1URH+flX5+z9A1NOB0ppqigiSySmooc\nJqcC2NP9TkeJWW80djETCHKDBqOLRIWC7GTuvbmSkfFpHn7mGMGg9oNcLCpoIoukRlMFIioYDLLv\nSGgw+rYNhU7HEZGwm69eSXVZNg2tvbzyzjmn48QMFTSRRbJ2VSaJ8T7qWnr0W2QEtHUNcfbCMJsq\nc0lP0WB0kWjh8Xj48sfXk5Lo59G9zXT1jjodKSaooIksEr/Py4bSbM73j9GpF6hFt+/dmwO095lI\ntMlKS+D+2w2TUwEeeqqRmYAGqi+UCprIItIyZ2RMTs1w8GgXGanxVJdrMLpINLquqoBr1+fT0j7I\nM2+cdjqO66mgiSwiFbTIePvEBUYnptlRXYTPq5ctkWh1/+2GzNR4flbbSlvnkNNxXE2vdCKLKCM1\ngZLCNJrO9DM2Me10nJhRG17e1GgnkeiWmhTHV/asZyYQ5MGnGrXt0AKooIkssk0VOcwEghxt7XU6\nSkzo7h+j8VQfa4szKMzWYHSRaFddlsPNm1fS3j3Cj1896XQc11JBE1lkNRW5gJY5F0tt/cWzZ5oc\nIOIW995USUFWEi+8dYbjbX1Ox3ElFTSRRVZalEZ6chx1J3sIaLuNBQkEQ4PRE+J9XLMuz+k4IjJL\nCfE+Hri7Cjzw7aeP6ZKPeVBBE1lkXo+HjeU5DI5M6iLZBTrW1kfP4ATXrssnMd7vdBwRmYOKFRns\n2VZKz+A4P3hRA9XnSgVNJAJqKrXMuRj2HWkHYOcmLW+KuNEndpRSUpDG/vpO3m664HQcV1FBE4mA\nDaXZ+Lwe6lq6nY7iWsNjU7zd1E1RTjIVK9KdjiMi8+D3eXng7ir8Pi/fe/Y4gyOTTkdyDRU0kQhI\nTvSzpjiD1o4hBvSCNC8HG7uYngloMLqIy63MTeGeXeUMjU7x3Z8f1yi8WVJBE4mQi3dz1muZc15q\n6zrwejxs12B0Ede7desq1q3O5HBz97t3ZsvlqaCJRMh7UwW0zDlXp7uGaOsaYlNlDhmpCU7HEZEF\n8no8/NqeKpISfDzy4gm6+8ecjhT1VNBEIqQoJ5ncjESOnuplekaDg+dinyYHiMScnIxEvnDrWsYn\nZ3jo6WMEAlrqvBwVNJEI8Xg8bKrIZWxihhNnB5yO4xpT0zO8cbST9JR4NpbnOB1HRBbR9upCNq/N\no+lMP8+/dcbpOFFNBU0kgmoqtcw5V++c6GZkfJrt1YX4fXqJEoklHo+HL91hSE+O4z9ea+HshWGn\nI0UtvfqJRNC61ZnEx3m1H9ocXByMvlPLmyIxKT05nl+9cz3TM0EefLJRl4B8BBU0kQiK8/uoKsmm\no2eU87oo9op6BsY52tpL5coMinJSnI4jIhFy1ZpcdtYUceb8MD+tbXU6TlRSQROJsHfv5mzWMueV\n7G/oIIhuDhBZDj63ew25GYk880YbzbpO95eooIlE2HvbbWiZ83ICwSC1dR3Ex3nZui7f6TgiEmFJ\nCX4euKsKgvDQU42MT2qg+qVU0EQiLDs9keK8VI6f7mdicsbpOFHLnu6ne2CcrevySUrQYHSR5WDt\nqkw+dt1qzveP8aOXm52OE1VU0ESWwKbKHKZnAjS29TodJWrV1oUHo9doMLrIcvKpneUU56XwyuF2\nrTRcQgVNZAlomfPyRsenOGQvUJCVxJriDKfjiMgSivN7eeCuKnxeDw8/c4zhsSmnI0UFFTSRJVCx\nIoOURD91LT0aFPwhDh47z9S0BqOLLFerC9L45M4yBkYm+f5zVq+TqKCJLAmv18PG8hz6hiY4c14b\nM35QbV07Hg9sr9bdmyLL1Z3XlVBZnMGh4+c52NjldBzHqaCJLBEtc364s+eHae0YoqY8h6w0DUYX\nWa68Xg8P7FlPQpyPf3u+id7BcacjOSriBc0Yc50xZm/47c3GmIPGmFeNMd+85HO+aox5yxhzwBiz\nJ9KZRJxQXZ6Dx6OC9kHvDUbXzQEiy11+VjL37a5kdGKa7zxzjMAyXuqMaEEzxvwB8CBw8dfifwZ+\nx1q7Cxg0xnzBGFMA/DawDbgD+CtjTFwkc4k4ITUpjoqVGbS0D+gi2LDpmQCvH+0kLTmOTZUajC4i\nsGvTCmoqcmg81cfet885HccxkT6D1gx86pL3i621B8Nv7wd2AtcCtdbaaWvtIHACqIlwLhFHbKrI\nIRiE+pM6iwZw+EQ3w2NTbNugwegiEuLxePjynetITYrjsb3NdPSMOB3JERF9RbTWPgFcujVwizFm\nZ/jtu4FkIB24dMbDMKD77CUm1VTkAlrmvKi2XoPRReSXZaQm8KWPGSanAzz01PIcqL7U23V/Bfim\nMcYP7APGCZWz9Es+Jw3on82D5eWlLXrA5UTHb2Hmc/xyc1PJzUjkaGsv2dkp+JbpWaO8vDR6BsZo\nONmDWZ3FVVUqaHOhn92F0fGbv6U8dnfmpdF4pp9XfnGWV+o6+fztZsmeOxosdUHbA3zBWttnjPkW\n8AzwDvB1Y0w8kASsAxpm82AXLgxFLGisy8tL0/FbgIUcv+qybF453M4bR86xdlXmIieLfheP3VMH\nThEIwnVV+fq7OAf62V0YHb/5c+LY3bOzjCNNF/jh85aKwlTKitKv/EVRaq7ldql/fT8BvGyMqQUG\nrLXPWmu7gG8BtcCLwB9bayeXOJfIktEyJwQvDkb3e7l2XYHTcUQkSiUnxvFre9YTCAZ56KlGJqeW\nzzzjiJ9Bs9a2AdvDbz8FPPUhn/Nt4NuRziISDdaXZOH3ealr6eaemyqcjuOIpjP9nO8fY9uGQpIT\nNRhdRD5aVWk2t24p5sVfnOXxV1v4wq1rnY60JJbnBTAiDkqI97FudSZnL4zQM7A8N2KsDe99duMm\nXXsmIld2z00VFOUk8+KhszSe6nU6zpJQQRNxwLtTBZbhdhuj41O8Zc+Tn5m0LK/BE5G5i4/z8cBd\nVXg9Hr799DFGx2N/L0kVNBEH1FSGr0Nr7nY4ydLbd/gck1MBdmgwuojMQVlROnfvKKVvaIJ/f+GE\n03EiTgVNxAH5mUkU5SRzrK1vWV30CvDCm6fxeGBHdaHTUUTEZfZsK6GsKI3Xj3Zy6Ph5p+NElAqa\niENqKnKYnA5w/PSstv2LCee6R7BtfVSX5ZCdnuh0HBFxGb/PywN3VRHn9/L95yz9wxNOR4oYFTQR\nh7y33cbyWeZ89XBorp4mB4jIfBXlpPDZmyoYHpviuz8/TjBGB6qroIk4ZE1xBkkJPupaemL2BeZS\nF/rHeOWdc+RmJrEpfA2eiMh83LKlmKrSLOpaenjtSLvTcSJCBU3EIX6flw2l2XQPjNPRM+p0nIh7\nbG8z0zNBfnVPaHlCRGS+vB4PX/n4epIS/PzwpWbO98Xea6heJUUctFymCjSd6eeQvUDFinRuvHql\n03FEJAZkpydy/+1rmZia4aGnjxEIxNZKhAqaiIM2XtwPLYavQwsEgzzyUuiW+M/tXqOtNURk0Vxf\nVcA16/JpPjvAs2+edjrOolJBE3FQRko8ZUVpnDg7wOj4tNNxIuL1hk7aOoe4vqqAipUZTscRkRji\n8Xj40scMGSnx/GRfK9MzAacjLRoVNBGH1VTkMhMIcjQGx5dMTM7w41dbiPN7+cyu5Tl3VEQiKzUp\njt+9dxP3374Wvy92ak3sfCciLvXu2KcYnCrw84Nt9A9Pcse1q8nJ0L5nIhIZqwvSuHHTCqdjLCoV\nNBGHlRSmkZ4ST/3JHgIxtN1G7+A4zx48TUZqPHdev9rpOCIirqKCJuIwr8dDTXkOg6NTnOoYcjrO\nonn81RYmpwN85sYKEuP9TscREXEVFTSRKFATY3dznmwf5I2jXZQUpLF9o2ZuiojMlQqaSBTYUJaN\nz+vhSAzshxYMBnnkpSYAPn/rGrzaVkNEZM5U0ESiQFKCn7WrMmnrHHL98N83j52n5dwgW0wea1dl\nOh1HRMSVVNBEosTFZc56F59Fm5ya4fFXmvH7PHz25kqn44iIuJYKmkiUeO86NPcWtOffOkPP4AS3\nXbOK/Mwkp+OIiLiWCppIlCjMTiY/M4mjp3pduRt2//AET7/eRlpyHHdtL3U6joiIq6mgiUQJj8dD\nTUUO45MzNJ3pdzrOnP3HayeZmJrhUzeWk5SgbTVERBZCBU0kitRUunOZs61ziP11HRTnpXBjTWzt\n5i0i4gQVNJEoYlZlkRDnc9V2G8FgkB++dIIgcN/uNXi92lZDRGShVNBEokic30tVaRZdvaN09Y06\nHWdW3m7qxp7pZ1NFDhtKs52OIyISE1TQRKLMe8PTo/8s2tR0gMf2NuPzerj3Fm2rISKyWFTQRKJM\nTUUu4I6xTy/94izn+8e4efNKinJSnI4jIhIzVNBEokxWWgKr81OxZ/oZn5x2Os5HGhyd5MkDraQk\n+vnEjjKn44iIxBQVNJEoVFOZw/RMkMZTfU5H+Ug/3dfK2MQMv3JDGalJcU7HERGJKSpoIlEo2pc5\nz14Y5pXD5yjKSeamq1c6HUdEJOaooIlEofKidFKT4qhr6SEYDDod532CwSCPvtxMMAj33lyJ36eX\nERGRxaZXVpEo5PV62FieTf/wJKe7hp2O8z71J3s42trLhrLsd+84FRGRxaWCJhKlonGZc3omwA9f\nasbjgftuqcTj0aa0IiKRoIImEqWqy7PxejxRNfbplXfO0dk7yq6rVlKcl+p0HBGRmKWCJhKlUhLj\nqFyZzsn2QQZHJ52Ow/DYFD+tbSUpwccnd2pbDRGRSFJBE4liNZW5BIGGk86fRfvZ/lZGxqe5e3sZ\n6cnxTscREYlpKmgiUezdsU8OL3N29Iyw9+1z5GcmsXtLsaNZRESWAxU0kSi2MjeF7PQEGk72MhMI\nOJbjsb0tzASCfPbmSuL8etkQEYk0vdKKRDGPx0NNRS6jE9M0nx1wJMPRU70cbu7GrMpk89pcRzKI\niCw3KmgiUc7JZc5AIMijL53AA3xu9xptqyEiskRU0ESi3PqSLOL8XkcK2mt17Zy9MMKOmiJKCtOW\n/PlFRJYrFTSRKJcQ52Pd6izOdY/QPTC2ZM87Oj7NE6+dJCHOx6dvLF+y5xURERU0EVdwYpnz6ddP\nMTQ6xce3lZCZmrBkzysiIipoIq6waYkL2vn+MV44dIac9AQ+tnXVkjyniIi8RwVNxAVyM5NYkZvC\nsbY+JqZmIv58j+9tZnomyD03VRIf54v484mIyPupoIm4RE1FDlPTAY639UX0eezpPg7ZC1SsTOfa\n9fkRfS4REflwKmgiLrEUy5yBYJAfvtwMaFsNEREnqaCJuETFygySEvzUtXQTDAYj8hyvN3TS1jnE\n9RsKqFiREZHnEBGRK1NBE3EJv89LdVk2PYMTtHePLPrjj09O8/irLcT7vdyzq2LRH19ERGZPBU3E\nRSK53cbP3zjNwPAkd1y3muz0xEV/fBERmT0VNBEX2ViRgwc4ssgFrWdgnGffPE1majx3XleyqI8t\nIiJzp4Im4iLpyfGUrUin+ewAI+NTi/a4P361hanpAJ/ZVUFCvLbVEBFxmgqaiMvUVOQQCAY52tq7\nKI/X0j7AG41dlBSmsa26cFEeU0REFkYFTcRlNlXkAnCkeeHLnMFgkB++eAKAz+9eg1fbaoiIRAUV\nNBGXWV2QSkZqPPUnewgEFrbdxsFjXbS0D3KNyWPtqsxFSigiIgulgibiMh6Ph5ryHIbHpmjtGJz3\n40xOzfD4Ky34fR4+e3PlIiYUEZGFUkETcaGai8ucC7ib87m3ztA7OMFtW1eRl5m0WNFERGQRqKCJ\nuFBVaRY+r4e6lu55fX3f0ATPvN5GenIcd20rXdxwIiKyYCpoIi6UlODHrM7kdNcwfUMTc/76J147\nycTUDJ+6sZykBH8EEoqIyEKooIm41MVlzvqTc1vmbOscYn99B8V5qeysWRGJaCIiskAqaCIutSk8\n9ulI8+yXOYPBII+8dIIg8LndlXi92lZDRCQaqaCJuFRBdjIFWUk0nupjajowq695u+kCTWf6uaoy\nl6rS7AgnFBGR+VJBE3GxmopcJqZmaDrTf8XPnZoO8KO9zfi8Hu69RdtqiIhEMxU0ERerqQwvc87i\nbs6XfnGWC/3j3LK5mMLs5EhHExGRBVBBE3ExsyqThHgfdVfYD21wZJInD7SSkujnEzeULk04ERGZ\nNxU0ERfz+7xsKM3mfN8Ynb2jH/l5P6ltZWxihk/uLCclMW4JE4qIyHyooIm4XE34bs66j7ib8+yF\nYV49fI6inGR2XaVtNURE3EAFTcTlLha0Dxv7FAwGefSlEwSDcN8tlfh9+pEXEXEDvVqLuFxmagIl\nBWk0nelnbGL6fX9W19LD0VN9VJdls7E8x6GEIiIyVypoIjGgpiKHmUCQxlO9735seibAoy834/V4\nuO+WSjwebUorIuIWKmgiMeC97TbeW+bc+845OntH2XXVClbmpToVTURE5kFTkkViQFlROmnJcdS3\n9BAIBhkdn+Znta0kJfj5lZ1lTscTEZE50hk0kRjg9XjYWJ7DwMgkp7uG+Nn+VkbGp7l7eynpyfFO\nxxMRkTlSQROJERfv5nz+rTPsffsc+ZlJ7N5S7HAqERGZDy1xisSI6rJsvB4PbxztAuDeWyqJ8+t3\nMBERN9Krt0iMSE6Mo7I4A4B1qzO5ek2uw4lERGS+VNBEYsjOmiJSEv18bvcabashIuJiWuIUiSE7\nNhaxvbpQ5UxExOV0Bk0kxqiciYi4nwqaiIiISJRRQRMRERGJMhG/Bs0Ycx3wv6y1NxtjrgL+EZgC\nmqy1D4Q/56vA18If/7q19ulI5xIRERGJVhE9g2aM+QPgQSAh/KE/Bf7MWnsjkGiM2WOMKQB+G9gG\n3AH8lTEmLpK5RERERKJZpJc4m4FPXfL+O0CuMcYDpBE6Y3YtUGutnbbWDgIngJoI5xIRERGJWhEt\naNbaJ4DpSz50AvgWcBTIB14B0oGBSz5nGMiIZC4RERGRaLbU+6B9E9hhrT1ujPkN4G+AZwmVtIvS\ngP5ZPJYnLy8tAhGXDx2/hdHxmz8du4XR8VsYHb/507FbOktd0HqAofDb7cB24C3g68aYeCAJWAc0\nLHEuERERkaix1AXtq8CjxpgpYBL4qrW2yxjzLaAW8AB/bK2dXOJcIiIiIlHDEwwGnc4gIiIiIpfQ\nRrUiIiIiUUYFTURERCTKqKCJiIiIRBkVNBEREZEos9R3cS5YeArBPwCbgHHgAWvtSWdTuYMxxg98\nBygF4gnNPX3S0VAuZIzJBw4Bt1prm5zO4ybGmD8EPgHEAf9grX3Y4UiuEP7Z/R6hn91pQnfANWbE\nKAAABfNJREFU6+/eLHxgHnQF8F0gADRYa3/T0XAu8CHztL9F6O/gBPAla+0FRwNGuUuP3yUf+wLw\nW9ba7Zf7WjeeQfskkBD+xv6I0Ga3Mjv3A93hWah3Av/X4TyuE/6H8p+AUaezuI0xZhewLfyzexOw\nytlErvJxwGet3QH8T+AbDudxhQ+ZB/03hLZy2gV4jTG/4lg4F/iQ4/d3wG9aa28BngD+0KlsbvAh\nxw9jzNXAV2bz9W4saDcQmj6AtfYgcI2zcVzlR8CfhN/2EpqFKnPz18A/EtpoWebmY0CDMeYnwM+A\npxzO4yZNgD+8gpBBaB9JubIPzoPeYq3dF37758CtSx/JVT54/O6z1taH3/YDY0sfyVXed/yMMTnA\nXwL/ZTZf7MaC9sHZndPGGDd+H0vOWjtqrR0xxqQBjwH/zelMbmKM+VXgvLX2BUKbKsvc5AJbgHuA\n/wz8wNk4rjIMlAHHgX8mtMwkV/Ah86Av/bkdQnOfL+uDx89a2wVgjNkO/Cbwtw5Fc4VLj1+4pzwE\n/B4wwiz+DXFjsRkkNK/zIq+1NuBUGLcxxqwCXga+Z6191Ok8LvNl4DZjzF7gKuD74evRZHZ6gOes\ntdPh66fGjTG5Todyid8FnrXWGkLX334/PB5P5ubSfytmO/dZLmGMuY/QdeAft9b2OJ3HRTYDlYRW\nYB4B1htjLnuJlutuEgD2A3cBjxtjrgfqr/D5EmaMKQCeI3QNwV6n87hN+LoVAMIl7dettecdjOQ2\ntcDvAH9rjFkBJBMqbXJlvbx3SUI/oddun3NxXOttY8yN1trXCF2H+7LTgdzEGHM/8DXgJmutyu3s\neay1h4CNAMaYEuARa+3vXe6L3FjQniB0FmN/+P0vOxnGZf4IyAT+xBjzp0AQuNNaO+FsLFfSjLQ5\nstY+bYzZaYx5k9Dp/d+w1uo4zs7fAd8xxrxG6A7YP7LW6vqfuft94EFjTBxwDHjc4TyuEV6i+ybQ\nBjxhjAkCr1pr/9zZZK4wr9c5zeIUERERiTJuvAZNREREJKapoImIiIhEGRU0ERERkSijgiYiIiIS\nZVTQRERERKKMCpqIiIhIlFFBExHXM8Y8bIz50gIf49eNMV+bw+fvCm9YLCKy6Ny4Ua2IyKKz1v7z\nPL5MG0mKSESooImIK4Xn2O0B2gmNPdob/vjXgVuALKAb+DSh8XC7rbVfDH/OnwJj1tr/fcnj/Q8g\naK39C2NMO6Fd5m8gNGLpXmttmzHmduBvgDHAXvK1FYRm7GUDo8BvExpD9zrwT9bah40x/wL0Wmv/\nMEKHRERiiJY4RcR1jDGfITQ0fD3wWUJDiC8WpbXW2m3W2nVAC/BF4FFgtzEmOfwQXwT+9TJPUQi8\nYK3dDOwDfis8nPy7wKettVsJlbSLvgf8gbX2GuDXgUettQHgPwF/YYz5HHAN8N8X/M2LyLKggiYi\nbnQT8B/W2oC1tht4BsBa2wL8vjHmq8aYvwauB1KttSPA08BnjDE3AM3W2s4rPMdz4f82EDozthE4\nZ61tCn/8ewDGmBRgK/CwMeYd4AdAsjEmy1p7HPh7QmXwfmvt9GJ88yIS+7TEKSJuFOT9v2BOAxhj\nNgOPAP8HeAyYITSYHeBhQmewThI6E3ZZ1trJS57LE/6v74PPGf7YWPhsG+EcK621feF31xNaat0M\nNM7quxORZU9n0ETEjV4EPmuMiTfGZAF3hD++C9hrrf0X4DhwO+FSZa2tBYoJnX37yTyesw7IM8Zs\nDL//+fDjDgInjDEXr2+7DXg1/PYeQmfetgPfMMYUzuN5RWQZUkETEdex1v6MUAlqIFS2job/6FHg\nKmPMYUIl7ghQdsmXPgG8bK2dusJT/NLdmeHlyS8A/2aMOQQkXfLH9wMPGGOOAF8H7jXGZAD/D3jA\nWtsK/B3w4Jy+URFZtjzBoO4SF5HYZ4xJAF4Afsdae9jpPCIil6MzaCIS88JLix3AfpUzEXEDnUET\nERERiTI6gyYiIiISZVTQRERERKKMCpqIiIhIlFFBExEREYkyKmgiIiIiUeb/AydiFieWtC0RAAAA\nAElFTkSuQmCC\n", 46 | "text/plain": [ 47 | "" 48 | ] 49 | }, 50 | "metadata": {}, 51 | "output_type": "display_data" 52 | } 53 | ], 54 | "source": [ 55 | "# Plot the data so we can see what we've got.\n", 56 | "plt.figure(figsize=(10,8))\n", 57 | "plt.xlabel('day index')\n", 58 | "plt.ylabel('purchases')\n", 59 | "plt.title('Orders over time')\n", 60 | "plt.plot(sample_data);" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 116, 66 | "metadata": { 67 | "collapsed": true 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "# These are the adjustments our simulation will make\n", 72 | "# when pretending to run control (index 0) or variant (index 1)\n", 73 | "# 1 means the control will run at 100% of normal values\n", 74 | "# 1.05 means the variant will run at 105% of normal values\n", 75 | "improvement_adjustments = [1, 1.05]" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 90, 81 | "metadata": { 82 | "collapsed": false 83 | }, 84 | "outputs": [], 85 | "source": [ 86 | "# For n number of days we run our experiment. \n", 87 | "# Here we decide up front which day will show which treatment\n", 88 | "# 0 for control and 1 for the variant\n", 89 | "test_plan = np.random.randint(0, 2, size=n)" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 91, 95 | "metadata": { 96 | "collapsed": false 97 | }, 98 | "outputs": [], 99 | "source": [ 100 | "# This is the same as the test plan but we\n", 101 | "# convert the treatment indicator into the lift\n", 102 | "# we should see on the business when it is used.\n", 103 | "daily_adjustments = [improvement_adjustments[v] for v in test_plan]" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 92, 109 | "metadata": { 110 | "collapsed": false 111 | }, 112 | "outputs": [], 113 | "source": [ 114 | "# Adjust each day of the sample data by using\n", 115 | "# our control/variant adjustments.\n", 116 | "test_results = sample_data * daily_adjustments" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 93, 122 | "metadata": { 123 | "collapsed": false 124 | }, 125 | "outputs": [], 126 | "source": [ 127 | "# Associate the treatment used with the results\n", 128 | "# or number of orders we saw for each day.\n", 129 | "test_plan_and_results = list(zip(test_plan, test_results))" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 94, 135 | "metadata": { 136 | "collapsed": true 137 | }, 138 | "outputs": [], 139 | "source": [ 140 | "# Separate control and variant data from\n", 141 | "# each other\n", 142 | "_, control_data = zip(*np.array(list(filter(lambda x: x[0] == 0, test_plan_and_results))))\n", 143 | "_, variant_data = zip(*np.array(list(filter(lambda x: x[0] == 1, test_plan_and_results))))" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 95, 149 | "metadata": { 150 | "collapsed": true 151 | }, 152 | "outputs": [], 153 | "source": [ 154 | "# Perform a statistical analysis on the psuedo-control and variant.\n", 155 | "# Find the probability distribution for the true mean difference\n", 156 | "# between the control and variant.\n", 157 | "\n", 158 | "control_means = []\n", 159 | "variant_means = []\n", 160 | "improvement_diffs = []\n", 161 | "\n", 162 | "for _ in range(20000):\n", 163 | " # We can't assume that the control and variant have \n", 164 | " # the same number of data points because we used \n", 165 | " # random assignment. We must specify the number of \n", 166 | " # days of data we saw for both control and variant.\n", 167 | " # This gives us the correct amount of uncertainty.\n", 168 | " control_mean = np.random.choice(control_data, size=len(control_data), replace=True).mean()\n", 169 | " variant_mean = np.random.choice(variant_data, size=len(variant_data), replace=True).mean()\n", 170 | "\n", 171 | " control_means.append(control_mean)\n", 172 | " variant_means.append(variant_mean)\n", 173 | " improvement_diffs.append(variant_mean/control_mean-1)" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 109, 179 | "metadata": { 180 | "collapsed": false 181 | }, 182 | "outputs": [ 183 | { 184 | "data": { 185 | "image/png": "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\n", 186 | "text/plain": [ 187 | "" 188 | ] 189 | }, 190 | "metadata": {}, 191 | "output_type": "display_data" 192 | } 193 | ], 194 | "source": [ 195 | "# Plot the results of the statistical analysis.\n", 196 | "\n", 197 | "weights = np.ones_like(improvement_diffs)/len(improvement_diffs)\n", 198 | "plt.figure(figsize=(10,8))\n", 199 | "plt.title('Probability distribution of lift in orders')\n", 200 | "plt.hist(improvement_diffs, bins=50, weights=weights);" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 115, 206 | "metadata": { 207 | "collapsed": false 208 | }, 209 | "outputs": [ 210 | { 211 | "data": { 212 | "text/plain": [ 213 | "0.92900000000000005" 214 | ] 215 | }, 216 | "execution_count": 115, 217 | "metadata": {}, 218 | "output_type": "execute_result" 219 | } 220 | ], 221 | "source": [ 222 | "# Probability of positive lift\n", 223 | "\n", 224 | "(np.array(improvement_diffs) > 0).sum()/len(improvement_diffs)" 225 | ] 226 | } 227 | ], 228 | "metadata": { 229 | "kernelspec": { 230 | "display_name": "Python 3", 231 | "language": "python", 232 | "name": "python3" 233 | }, 234 | "language_info": { 235 | "codemirror_mode": { 236 | "name": "ipython", 237 | "version": 3 238 | }, 239 | "file_extension": ".py", 240 | "mimetype": "text/x-python", 241 | "name": "python", 242 | "nbconvert_exporter": "python", 243 | "pygments_lexer": "ipython3", 244 | "version": "3.4.4" 245 | } 246 | }, 247 | "nbformat": 4, 248 | "nbformat_minor": 0 249 | } 250 | --------------------------------------------------------------------------------