diff --git a/Apps/Recommendations/Python/checkColdRecomFeatureMatch.py b/Apps/Recommendations/Python/checkColdRecomFeatureMatch.py new file mode 100644 index 00000000..dba0bc1b --- /dev/null +++ b/Apps/Recommendations/Python/checkColdRecomFeatureMatch.py @@ -0,0 +1,230 @@ +# +# In this script we check the returned scoring items when the seed item is cold +# In terms of checking, we check if there is any features with the same value. +# In this version, only one seed item is supported. + +# list of input files: +# 1. catalog file +# 2. trainining file +# 3. seed file +# 4. the scoring file using cold item support +# Also some file format parameters are provided. +# Another important parameter: cold_upper_bound +# It specifies the largest number of occurrences in +# training is still considered as C2 cold item. If the +# occurrence is C2+1, then it is considered as warm. + +#========== Parameter for PT dataset ========= +f_prefix = 'PT3' +f_catalog = 'catalog.csv' +f_train = 'train-sorted.csv' +f_seed = 'seed_as_train.csv' +f_recom = 'scores-sar-cold_reversed.tsv' +f_output = 'list_of_recom_no_feature_match.csv' + +f_catalog_header = True +f_seed_header = False +f_seed_sep = ',' +f_recom_sep = '\t' +f_recom_beginning_comment = True + +cold_upper_bound = 2 +#========== Parameter for PT dataset ========= + +# update file names based on f_prefix. Users need to change them +# accordingly based on your own file organization. +f_train = f_prefix + '/' + f_train +f_catalog = f_prefix + '/' + f_catalog +f_seed = f_prefix + '/' + f_seed +f_recom = f_prefix + '/data/' + f_recom +f_output = f_prefix + '/data/' + f_output +#============================================================================= +# The rest should be be changed in running for different datasets. + +# Read the catalog file +print('Read the catalog file') +fin_catalog = open(f_catalog) +line = fin_catalog.readline() +D_catalog = {} +if f_catalog_header: + # extract feature name + fnames = line.strip().split(',')[2:] + line = fin_catalog.readline() +else: + # use default feature name + f_num = len(line.strip().split(',')) - 2 + fnames = ['f_' + str(i) for i in range(f_num)] +while line: + fs = line.strip().split(',') + itemId = fs[0] + if itemId not in D_catalog: + D_catalog[itemId] = {} + + # We need to save all feature values for the current item + fs_feature = fs[2:] + fs_feature_mvalue = [v.strip().strip('"').split(';') for v in fs_feature] + for fi in range(len(fs_feature_mvalue)): + if len(fs_feature_mvalue[fi])==1 and len(fs_feature_mvalue[fi][0])==0: + # This is an empty feature value + pass + else: + # We process non-empty feature value only + fi_value_list = fs_feature_mvalue[fi] + D_catalog[itemId][fi] = {} + for fv in fi_value_list: + D_catalog[itemId][fi][fv] = 1 + + line = fin_catalog.readline() +fin_catalog.close() + +# Read the training file +print('Read the training file') +fin_train = open(f_train) +line = fin_train.readline() +D_item_user = {} +while line: + fs = line.strip().split(',') + userId = fs[0] + itemId = fs[1] + if itemId not in D_item_user: + D_item_user[itemId] = {} + D_item_user[itemId][userId] = 1 + + line = fin_train.readline() +fin_train.close() + +# Read the seed file +print('Read the seed file') +fin_seed = open(f_seed) +D_seed = {} +D_item_type = {} +line = fin_seed.readline() +if f_seed_header: + line = fin_seed.readline() +while line: + fs = line.strip().split(f_seed_sep) + userId = fs[0] + itemId = fs[1] + D_seed[userId] = itemId + + # Determine the type of the seed item + if itemId not in D_item_type: + itemFreq = 0 + if itemId in D_item_user: + itemFreq = len(D_item_user[itemId]) + if itemId in D_catalog: + if itemFreq > cold_upper_bound: + itemType = 'W' + elif itemFreq > 0: + itemType = 'C2' + else: + itemType = 'C1' + else: + # M means item missing in the catalog file + itemType = 'M' + D_item_type[itemId] = itemType + + line = fin_seed.readline() +fin_seed.close() + +# In this function we compute the pairwise similarity of items +# based on their features. +def compareItemFeatures(D_item1, D_item2): + # This function return the number of matched feature values + # for multi-valued feature. If at least one value is matched, + # we will consider it as matched + f1_index = D_item1.keys() + c_count = 0 + for fi in f1_index: + if fi in D_item2: + # if both items have this feature + # then we will compare their feature values + for fv in D_item1[fi].keys(): + if fv in D_item2[fi]: + c_count += 1 + break + return c_count + +# Read the recomdation file +print('Read the recommendation file') +# We use D_item_sim to cache item pairwise similarity +D_item_sim = {} +# We use D_item_nomatch to cache all seed items with unmatched items returned +D_item_nomatch = {} +fout = open(f_output, 'w') +fin_recom = open(f_recom) +line = fin_recom.readline() +if f_recom_beginning_comment: + print('Skip the first few lines of comments') + while line[0]=='#': + line = fin_recom.readline() +# Process the valid lines one by one +while line: + fs = line.strip().split(f_recom_sep) + userId = fs[0] + itemId = fs[1] + + if userId in D_seed: + seedItemId = D_seed[userId] + seedItemType = D_item_type[seedItemId] + if seedItemType=='C1' or seedItemType=='C2': + # compare item features + if itemId <= seedItemId: + itemA = itemId + itemB = seedItemId + else: + itemA = seedItemId + itemB = itemId + if itemA not in D_item_sim: + D_item_sim[itemA] = {} + if itemB not in D_item_sim[itemA]: + D_itemA_ft = D_catalog[itemA] + D_itemB_ft = D_catalog[itemB] + D_item_sim[itemA][itemB] = compareItemFeatures(D_itemA_ft, D_itemB_ft) + # logical check + simAB = D_item_sim[itemA][itemB] + if simAB==0: + # the case we need to investigate + fout.write('userId,' + userId + '\n') + fout.write('seedItemId,' + seedItemId + '\n') + fout.write('recomItemId,' + itemId + '\n') + + D_item_nomatch[seedItemId] = D_item_nomatch.get(seedItemId, 0) + 1 + + line = fin_recom.readline() +fin_recom.close() +fout.close() + +# For all items in the catalog, determine their types, and summarize number of +# items of different types. +for itemId in D_catalog: + if itemId not in D_item_type: + itemFreq = 0 + if itemId in D_item_user: + itemFreq = len(D_item_user[itemId]) + if itemFreq > cold_upper_bound: + itemType = 'W' + elif itemFreq > 0: + itemType = 'C2' + else: + itemType = 'C1' + D_item_type[itemId] = itemType +all_item_type_list = list(D_item_type.values()) +n_item_warm = all_item_type_list.count('W') +n_item_C1 = all_item_type_list.count('C1') +n_item_C2 = all_item_type_list.count('C2') + + +# Summarize some statistics in the end +n_item_total = len(D_catalog) +n_seed_nomatch = len(D_item_nomatch) +percent_nomatch = float(n_seed_nomatch) / n_item_total +print('the total number of items in catalog is %d'%n_item_total) +print('the total number of seed items which generate recom items with no feature match is %d'%n_seed_nomatch) +print('the percentage of seed items which generate recom items with no feature match is %f'%percent_nomatch) +print('the total number of warm item is %d'%n_item_warm) +print('the percentage of warm item is %f'%(float(n_item_warm)/n_item_total)) +print('the total number of C1 item is %d'%n_item_C1) +print('the percentage of C1 item is %f'%(float(n_item_C1)/n_item_total)) +print('the total number of C2 item is %d'%n_item_C2) +print('the percentage of C2 item is %f'%(float(n_item_C2)/n_item_total)) diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD deleted file mode 100644 index 52bf69ce..00000000 --- a/CONTRIBUTING.MD +++ /dev/null @@ -1,2 +0,0 @@ -#Contributing to Azure-MachineLearning-DataScience - diff --git a/Data-Science-Virtual-Machine/Linux/azuredeploy.json b/Data-Science-Virtual-Machine/Linux/azuredeploy.json index b0e29cce..95eb8a89 100644 --- a/Data-Science-Virtual-Machine/Linux/azuredeploy.json +++ b/Data-Science-Virtual-Machine/Linux/azuredeploy.json @@ -49,6 +49,7 @@ "imagePublisher": "microsoft-ads", "imageOffer": "linux-data-science-vm", "OSDiskName": "osdiskforlinuxsimple", + "DataDiskName": "datadiskforlinuxsimple", "sku": "linuxdsvm", "nicName": "[parameters('vmName')]", "addressPrefix": "10.0.0.0/16", @@ -174,7 +175,17 @@ }, "caching": "ReadWrite", "createOption": "FromImage" - } + }, + "dataDisks": [ + { + "name": "data-0", + "vhd": { + "uri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('DataDiskName'), variables('vmName'), '.vhd')]" + }, + "createOption": "fromImage", + "lun": 0 + } + ] }, "networkProfile": { "networkInterfaces": [ diff --git a/Data-Science-Virtual-Machine/Linux/multiazuredeploy.json b/Data-Science-Virtual-Machine/Linux/multiazuredeploy.json index f4b51092..1fb04eb1 100644 --- a/Data-Science-Virtual-Machine/Linux/multiazuredeploy.json +++ b/Data-Science-Virtual-Machine/Linux/multiazuredeploy.json @@ -57,6 +57,7 @@ "imagePublisher": "microsoft-ads", "imageOffer": "linux-data-science-vm", "OSDiskName": "osdiskforlinuxsimple", + "DataDiskName": "datadiskforlinuxsimple", "sku": "linuxdsvm", "nicName": "[parameters('vmName')]", "addressPrefix": "10.0.0.0/16", @@ -194,7 +195,17 @@ }, "caching": "ReadWrite", "createOption": "FromImage" - } + }, + "dataDisks": [ + { + "name": "data-0", + "vhd": { + "uri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('DataDiskName'), variables('vmName'), copyIndex(), '.vhd')]" + }, + "createOption": "fromImage", + "lun": 0 + } + ] }, "networkProfile": { "networkInterfaces": [ diff --git a/Data-Science-Virtual-Machine/Windows/README.md b/Data-Science-Virtual-Machine/Windows/README.md index f2a86c26..25b023d8 100644 --- a/Data-Science-Virtual-Machine/Windows/README.md +++ b/Data-Science-Virtual-Machine/Windows/README.md @@ -16,3 +16,14 @@ You can click on the "Deploy to Azure" button to immediately try out the VM (Azu [![Deploy to Azure](http://azuredeploy.net/deploybutton.svg)](https://azuredeploy.net/) + +To create multiple instances of the Windows DSVM you can run the following command from a Azure CLI. + +``` +azure login +# Change to your subscription if you want to create VMs in non defaulty subscription +azure group create -n [RGNAME] -l "West US 2" +azure group deployment create -g [RGNAME] --template-uri https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Data-Science-Virtual-Machine/Windows/multiazuredeploy.json +``` + + diff --git a/Data-Science-Virtual-Machine/Windows/buildout/README.md b/Data-Science-Virtual-Machine/Windows/buildout/README.md new file mode 100644 index 00000000..ef9d5079 --- /dev/null +++ b/Data-Science-Virtual-Machine/Windows/buildout/README.md @@ -0,0 +1 @@ +This directory will contain some of the files / scripts that are packaged into the Windows DSVM. diff --git a/Data-Science-Virtual-Machine/Windows/buildout/dsvm/tools/setup/JupyterSetPasswordAndStart.cmd b/Data-Science-Virtual-Machine/Windows/buildout/dsvm/tools/setup/JupyterSetPasswordAndStart.cmd new file mode 100644 index 00000000..52074720 --- /dev/null +++ b/Data-Science-Virtual-Machine/Windows/buildout/dsvm/tools/setup/JupyterSetPasswordAndStart.cmd @@ -0,0 +1,14 @@ +@echo off +echo This tool will help you set your Jupyter Password and Enable the Jupyter server task (named start_ipython_notebook) in task scheduler +c:\anaconda\python.exe -c "import IPython;p=IPython.lib.passwd();f=open('c:\\programdata\\jupyter\\jupyter_notebook_config.py', 'a');f.write('\nc.NotebookApp.password = u\''+p+'\'');f.close()" +if errorlevel 1 exit /b 1 +echo Updated the Jupyter config c:\\programdata\\jupyter\\jupyter_notebook_config.py +pause +schtasks /Change /TN start_ipython_notebook /ENABLE +schtasks /Run /TN start_ipython_notebook +echo Enabled and Started Jupyter Notebook Server +echo You can access Jupyter server by entering https://localhost:9999/ locally on the browser +echo You can also access Jupyter server remotely from a browser by entering https://[machine IP or DNS name]:9999/ +pause +@echo on + diff --git a/Data-Science-Virtual-Machine/Windows/buildout/dsvm/tools/setup/KillJupyterAndDisable.cmd b/Data-Science-Virtual-Machine/Windows/buildout/dsvm/tools/setup/KillJupyterAndDisable.cmd new file mode 100644 index 00000000..6911f035 --- /dev/null +++ b/Data-Science-Virtual-Machine/Windows/buildout/dsvm/tools/setup/KillJupyterAndDisable.cmd @@ -0,0 +1,5 @@ +echo This program will kill Jupyter and ALL python process +echo Press CTRL-C IF THIS IS NOT YOU WANT ELSE PRESS ENTER +pause +taskkill /f /im jupyter.exe /im jupyter-notebook.exe /im python.exe +schtasks /Change /TN start_ipython_notebook /DISABLE diff --git a/Data-Science-Virtual-Machine/Windows/buildout/dsvm/tools/setup/StartJupyter.cmd b/Data-Science-Virtual-Machine/Windows/buildout/dsvm/tools/setup/StartJupyter.cmd new file mode 100644 index 00000000..0f2d0034 --- /dev/null +++ b/Data-Science-Virtual-Machine/Windows/buildout/dsvm/tools/setup/StartJupyter.cmd @@ -0,0 +1,3 @@ +schtasks /Change /TN start_ipython_notebook /ENABLE +schtasks /Run /TN start_ipython_notebook +echo Enabled and Started Jupyter Notebook Server diff --git a/Data-Science-Virtual-Machine/Windows/deep-learning-extension/install.ps1 b/Data-Science-Virtual-Machine/Windows/deep-learning-extension/install.ps1 index 53ce621c..d4997627 100644 --- a/Data-Science-Virtual-Machine/Windows/deep-learning-extension/install.ps1 +++ b/Data-Science-Virtual-Machine/Windows/deep-learning-extension/install.ps1 @@ -1,57 +1,6 @@ -cd C:\Windows\temp - Import-Module BitsTransfer -Start-BitsTransfer -Source https://deeplearningtoolkit.blob.core.windows.net/assets/dsvm-deep-learning-v4.zip -Destination ".\dsvm-deep-learning.zip" -Start-BitsTransfer -Source https://deeplearningtoolkit.blob.core.windows.net/assets/mxnet.zip -Destination ".\mxnet.zip" -Start-BitsTransfer -Source https://deeplearningtoolkit.blob.core.windows.net/assets/cuda_8.0.44_windows.zip -Destination ".\cuda_8.0.44_windows.zip" -unzip dsvm-deep-learning.zip -unzip mxnet.zip -unzip cuda_8.0.44_windows.zip - -# cuda -certutil -addstore "TrustedPublisher" dsvm-deep-learning\nvidia_certificate.cer -cuda_8.0.44_windows\setup.exe -s | wait-process -$env:Path += ";C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\bin;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v8.0\libnvvp;C:\Program Files\NVIDIA Corporation\NVSMI" -[Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine) - -# CNTK GPU version -cd dsvm-deep-learning -Remove-Item C:\dsvm\tools\cntk\* -Recurse -Copy-Item -Recurse CNTK-1-7-Windows-64bit-GPU\* C:\dsvm\tools\cntk -Remove-Item C:\dsvm\tools\bin\cntk.exe -Copy-Item CNTK-1-7-Windows-64bit-GPU\cntk\cntk.exe C:\dsvm\tools\bin -$env:Path += ";C:\dsvm\tools\cntk\cntk" -[Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine) -cd .. -# mxnet -Copy-Item -Recurse mxnet\lib\* C:\dsvm\tools\mxnet\lib -$env:Path += ";C:\dsvm\tools\mxnet\lib" -$env:mxnet_path = "C:\dsvm\tools\mxnet" -[Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Machine) -[Environment]::SetEnvironmentVariable("mxnet_path", $env:mxnet_path, [System.EnvironmentVariableTarget]::Machine) - -# python -cd mxnet\python -python setup.py install -cd .. - -# R -$R_path = "C:\Program Files\Microsoft SQL Server\130\R_SERVER\bin\x64\R.exe" -& $R_path -e "install.packages(c('argparse', 'Rcpp', 'DiagrammeR', 'data.table', 'jsonlite', 'magrittr', 'stringr'))" -& $R_PATH CMD INSTALL .\R-package -cd .. - -# copy over the sample solutions -cd dsvm-deep-learning -mkdir C:\dsvm\deep-learning -mkdir C:\dsvm\deep-learning\solutions -Copy-Item -Recurse solutions\* C:\dsvm\deep-learning\solutions +cd C:\Windows\temp +Start-BitsTransfer -Source https://deeplearningtoolkit.blob.core.windows.net/assets/install.latest.ps1 -Destination ".\install.latest.ps1" -# add the readme -Copy-Item readme.txt C:\dsvm\deep-learning -# and link to it on the desktop -$WshShell = New-Object -comObject WScript.Shell -$Shortcut = $WshShell.CreateShortcut("C:\Users\Public\Desktop\deep learning toolkit readme.lnk") -$Shortcut.TargetPath = "C:\dsvm\deep-learning\readme.txt" -$Shortcut.Save() \ No newline at end of file +.\install.latest.ps1 diff --git a/Data-Science-Virtual-Machine/Windows/multiazuredeploy.json b/Data-Science-Virtual-Machine/Windows/multiazuredeploy.json index a465d1c4..10f70b83 100644 --- a/Data-Science-Virtual-Machine/Windows/multiazuredeploy.json +++ b/Data-Science-Virtual-Machine/Windows/multiazuredeploy.json @@ -16,7 +16,6 @@ }, "numberOfInstances": { "type": "string", - "defaultValue": "1", "metadata": { "description": "Number of VMs to deploy." } @@ -29,9 +28,10 @@ }, "vmSize": { "type": "string", - "defaultValue": "Standard_D3_v2", + "defaultValue": "Standard_DS2_v2", "allowedValues": [ "Basic_A3", + "Standard_DS2_v2", "Standard_A4", "Standard_A5", "Standard_A6", @@ -41,7 +41,9 @@ "Standard_A10", "Standard_A11", "Standard_D3_v2", - "Standard_D4_v2", + "Standard_D4_v2", + "Standard_DS3_v2", + "Standard_DS4_v2", "Standard_D12_v2", "Standard_D13_v2", "Standard_D14_v2" diff --git a/Examples/AzureML/Readme.md b/Examples/AzureML/Readme.md new file mode 100644 index 00000000..65aa461b --- /dev/null +++ b/Examples/AzureML/Readme.md @@ -0,0 +1 @@ +# Examples for Different Data Platforms diff --git a/Examples/HDInsight/Readme.md b/Examples/HDInsight/Readme.md new file mode 100644 index 00000000..3bba991c --- /dev/null +++ b/Examples/HDInsight/Readme.md @@ -0,0 +1 @@ +# Examples for HDInsight diff --git a/Examples/Readme.md b/Examples/Readme.md new file mode 100644 index 00000000..d2620b9b --- /dev/null +++ b/Examples/Readme.md @@ -0,0 +1 @@ +# This folder contains examples data sciences projects on different Microsoft data platforms diff --git a/LICENSE-CODE.TXT b/LICENSE-CODE.TXT new file mode 100644 index 00000000..66607ac3 --- /dev/null +++ b/LICENSE-CODE.TXT @@ -0,0 +1,17 @@ +The MIT License (MIT) +Copyright (c) Microsoft Corporation. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 00000000..19e66dcc --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,395 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. \ No newline at end of file diff --git a/Misc/KDD2017MRS/Prepare Jupyter Notebooks.ipynb b/Misc/KDD2017MRS/Prepare Jupyter Notebooks.ipynb new file mode 100644 index 00000000..5d18d2fd --- /dev/null +++ b/Misc/KDD2017MRS/Prepare Jupyter Notebooks.ipynb @@ -0,0 +1,90 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create directory for yourself on the Jupyter Notebook Server" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "newDir <- \"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create a directory for a specific user" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "if (file.exists(newDir)){\n", + " print(paste(\"Directory \", newDir, \" already exists. Please choose another name.\", sep=\"\"))\n", + "} else{\n", + " system(paste(\"mkdir \", newDir, sep=\"\"))\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Copy Jupyter notebook to the directory of the user" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fileName <- 'SQL_R_Services_End_to_End_Tutorial.ipynb'\n", + "system(paste(\"cp C:\\\\dsvm\\\\notebooks\\\\SQL_R_Services_End_to_End_Tutorial.ipynb \", newDir,\"\\\\.\", sep=\"\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "anaconda-cloud": {}, + "kernelspec": { + "display_name": "R", + "language": "R", + "name": "ir" + }, + "language_info": { + "codemirror_mode": "r", + "file_extension": ".r", + "mimetype": "text/x-r-source", + "name": "R", + "pygments_lexer": "r", + "version": "3.2.2" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/Misc/KDD2017MRS/SQL_R_Services_End_to_End_Tutorial.ipynb b/Misc/KDD2017MRS/SQL_R_Services_End_to_End_Tutorial.ipynb new file mode 100644 index 00000000..d676afd7 --- /dev/null +++ b/Misc/KDD2017MRS/SQL_R_Services_End_to_End_Tutorial.ipynb @@ -0,0 +1,566 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Configure the sql server credentials" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "sql_server <- ''\n", + "user_name <- ''\n", + "password <- ''" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install necessary libraries if missing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# PreRequisites: You have installed Revolution R Enterprise 7.5.0 or higher on the machine and SQL Server 2016 CTP3 or higher on the database server\n", + "# Install required R libraries for this walkthrough if they are not installed. \n", + "\n", + "if (!('ROCR' %in% rownames(installed.packages()))){\n", + " install.packages('ROCR', lib='./libsforjupyter')\n", + "}\n", + "if (!('RODBC' %in% rownames(installed.packages()))){\n", + " install.packages('RODBC', lib='./libsforjupyter')\n", + "}\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Add local lib directory to libPaths. The Jupyter windows user (LOCAL SERVICE) does not have write permission to global lib\n", + ".libPaths( c( .libPaths(), \"./libsforjupyter\") )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Confirm that you are running under your own directory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Confirm you are working in your directory named after your username\n", + "# The username is also used as prefix for DB objects you will be creating in the tutorial \n", + "username <- gsub(\"[^a-zA-Z0-9]\", \"\", basename(getwd()))\n", + "username" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Specifying the Database Connection\n", + "\n", + "* RODBC style connection string\n", + "* Compute context can be\n", + " * Server: rx Commands will run on the server closer to the data\n", + " * Local: Commands will run locally on the client. Data need to brought to the client. \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "connStr <- paste(\"Driver=SQL Server;Server=\", sql_server, \";Database=nyctaxi;Uid=\", user_name, \";Pwd=\", password, sep=\"\")\n", + "\n", + "# Set ComputeContext. Needs a temp directory path to serialize R objects back and forth\n", + "sqlShareDir <- paste(\"C:\\\\AllShare\\\\\",Sys.getenv(\"USERNAME\"),sep=\"\")\n", + "sqlWait <- TRUE\n", + "sqlConsoleOutput <- FALSE\n", + "cc <- RxInSqlServer(connectionString = connStr, shareDir = sqlShareDir, \n", + " wait = sqlWait, consoleOutput = sqlConsoleOutput)\n", + "rxSetComputeContext(cc)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up R environment to query the database\n", + "\n", + "* Define a DataSource (from a select query) to be used to explore the data and generate features from.\n", + "* Keep in mind that inDataSource is just a reference to the result dataset from the SQL query." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "sampleDataQuery <- \"select tipped, tip_amount, fare_amount, passenger_count,trip_time_in_secs,trip_distance, \n", + "pickup_datetime, dropoff_datetime, \n", + "cast(pickup_longitude as float) as pickup_longitude, \n", + "cast(pickup_latitude as float) as pickup_latitude, \n", + "cast(dropoff_longitude as float) as dropoff_longitude, \n", + "cast(dropoff_latitude as float) as dropoff_latitude,\n", + "cast(pickup_longitude as float) * 100 as pickup_longitude2, \n", + "cast(pickup_latitude as float) * 100 as pickup_latitude2, \n", + "cast(dropoff_longitude as float) * 100 as dropoff_longitude2, \n", + "cast(dropoff_latitude as float) * 100 as dropoff_latitude2,\n", + "payment_type from nyctaxi_sample\n", + "tablesample (5 percent) repeatable (98052)\n", + "\"\n", + "\n", + "ptypeColInfo <- list(\n", + " payment_type = list(\n", + " type = \"factor\",\n", + " levels = c(\"CSH\", \"CRD\", \"DIS\", \"NOC\", \"UNK\"),\n", + " newLevels= c(\"CSH\", \"CRD\", \"DIS\", \"NOC\", \"UNK\")\n", + " )\n", + ")\n", + "\n", + "inDataSource <- RxSqlServerData(sqlQuery = sampleDataQuery, connectionString = connStr, colInfo = ptypeColInfo,\n", + " colClasses = c(pickup_longitude = \"numeric\", pickup_latitude = \"numeric\", \n", + " dropoff_longitude = \"numeric\", dropoff_latitude = \"numeric\"),\n", + " rowsPerRead=500)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Understand the data\n", + "\n", + "These functions give you basic information about your data source, such as its size and the names of columns in the set:\n", + "\n", + "- `rxGetInfo`\n", + "- `rxGetVarInfo` (same as `rxGetInfo` with `getVarInfo = TRUE`)\n", + "- `rxGetVarNames`\n", + "\n", + "The `rxGetInfo` function offers information on the data location, size, number of columns, and other related information.\n", + " \n", + "## Numerical Data Summaries\n", + "\n", + "We now move to quantitative summaries of the data. The `rxSummary` command provides output on the number of observations contained in a data set and for a particular column, the function provides output on the following:\n", + "\n", + " - Name\n", + " - Mean value\n", + " - Standard Deviation\n", + " - Minimum and Maximum value\n", + " - Number of valid observations\n", + " - Number of missing observations\n", + "\n", + "The `rxSummary` function takes a formula as its first argument, and the name of the data set as the second.\n", + "\n", + "In addition, column subsets and transformations may also be computed as a sub-call to the function using the transforms (and so forth) commands.\n", + "\n", + "If compute context is SQL server, it is executed closer to the data. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "################################\n", + "# Data exploration #\n", + "################################\n", + "# Summarize the Sampled Data\n", + "rxGetVarInfo(data = inDataSource)\n", + "rxSummary(~fare_amount:F(passenger_count,1,6), data = inDataSource)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Understand the relationship between tip amount, passenger count, and trip distance in the sample data\n", + "cube1 <- rxCube(tip_amount~F(passenger_count,1,6):F(trip_distance,0, 25), data = inDataSource)\n", + "cubePlot <- rxResultsDF(cube1)\n", + "cubePlot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "################################\n", + "# Data Visualization #\n", + "################################\n", + "# Plot fare amount histogram on the SQL Server, and ship the plot to R client to display\n", + "rxHistogram(~tip_amount | payment_type, data = inDataSource, title = \"Tip Amount Histogram\")\n", + "rxHistogram(~fare_amount, data = inDataSource, title = \"Fare Amount Histogram\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Feature engineering" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Feature engineering using functions defined in open source R" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Define a function in open source R to calculate the direct distance between pickup and dropoff as a new feature \n", + "# Use Haversine Formula: https://en.wikipedia.org/wiki/Haversine_formula\n", + "env <- new.env()\n", + "\n", + "env$ComputeDist <- function(pickup_long, pickup_lat, dropoff_long, dropoff_lat){\n", + " R <- 6371/1.609344 #radius in mile\n", + " delta_lat <- dropoff_lat - pickup_lat\n", + " delta_long <- dropoff_long - pickup_long\n", + " degrees_to_radians = pi/180.0\n", + " a1 <- sin(delta_lat/2*degrees_to_radians)\n", + " a2 <- as.numeric(a1)^2\n", + " a3 <- cos(pickup_lat*degrees_to_radians)\n", + " a4 <- cos(dropoff_lat*degrees_to_radians)\n", + " a5 <- sin(delta_long/2*degrees_to_radians)\n", + " a6 <- as.numeric(a5)^2\n", + " a <- a2+a3*a4*a6\n", + " c <- 2*atan2(sqrt(a),sqrt(1-a))\n", + " d <- R*c\n", + " return (d)\n", + "}\n", + "\n", + "featuretable <- paste0(username , \"_features\")\n", + "#Define the featureDataSource to be used to store the features, specify types of some variables as numeric\n", + "featureDataSource <- RxSqlServerData(table = featuretable, \n", + " colClasses = c(pickup_longitude = \"numeric\", pickup_latitude = \"numeric\", \n", + " dropoff_longitude = \"numeric\", dropoff_latitude = \"numeric\",\n", + " passenger_count = \"numeric\", trip_distance = \"numeric\",\n", + " trip_time_in_secs = \"numeric\", direct_distance = \"numeric\"),\n", + " connectionString = connStr)\n", + "\n", + "# Create feature (direct distance) by calling rxDataStep() function, which calls the env$ComputeDist function to process records\n", + "# And output it along with other variables as features to the featureDataSource\n", + "# This will be the feature set for training machine learning models\n", + "start.time <- proc.time()\n", + "rxDataStep(inData = inDataSource, outFile = featureDataSource, overwrite = TRUE, \n", + " varsToKeep=c(\"tipped\", \"fare_amount\", \"passenger_count\",\"trip_time_in_secs\", \n", + " \"trip_distance\", \"pickup_datetime\", \"dropoff_datetime\", \"pickup_longitude\",\n", + " \"pickup_latitude\",\"dropoff_longitude\", \"dropoff_latitude\"),\n", + " transforms = list(direct_distance=ComputeDist(pickup_longitude, pickup_latitude, dropoff_longitude, \n", + " dropoff_latitude)),\n", + " transformEnvir = env, rowsPerRead=10000, reportProgress = 3)\n", + "used.time <- proc.time() - start.time\n", + "print(paste(\"It takes CPU Time=\", round(used.time[1]+used.time[2],2), \n", + " \" seconds, Elapsed Time=\", round(used.time[3],2), \" seconds to generate features using R functions.\", sep=\"\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Feature engineering by calling a stored procedure in SQL Server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Do feature engineering through a SQL Query\n", + "\n", + "ptypeColInfo <- list(\n", + " payment_type = list(\n", + " type = \"factor\",\n", + " levels = c(\"CSH\", \"CRD\", \"DIS\", \"NOC\", \"UNK\"),\n", + " newLevels= c(\"CSH\", \"CRD\", \"DIS\", \"NOC\", \"UNK\")\n", + " )\n", + ")\n", + "# Alternatively, use a user defined function in SQL to create features\n", + "# Sometimes, feature engineering in SQL might be faster than R\n", + "# You need to choose the most efficient way based on real situation\n", + "# Here, featureEngineeringQuery is just a reference to the result from a SQL query. \n", + "featureEngineeringQuery = \"SELECT tipped, fare_amount, passenger_count,trip_time_in_secs,trip_distance, \n", + "pickup_datetime, dropoff_datetime, \n", + "dbo.fnCalculateDistance(pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude) as direct_distance,\n", + "pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude, payment_type\n", + "FROM nyctaxi_sample\n", + "tablesample (70 percent) repeatable (98052)\n", + "\"\n", + "featureDataSource <- RxSqlServerData(sqlQuery = featureEngineeringQuery, colInfo = ptypeColInfo,\n", + " colClasses = c(pickup_longitude = \"numeric\", pickup_latitude = \"numeric\", \n", + " dropoff_longitude = \"numeric\", dropoff_latitude = \"numeric\",\n", + " passenger_count = \"numeric\", trip_distance = \"numeric\",\n", + " trip_time_in_secs = \"numeric\", direct_distance = \"numeric\", fare_amount=\"numeric\"),\n", + " connectionString = connStr)\n", + "\n", + "# summarize the feature table after the feature set is created\n", + "rxGetVarInfo(data = featureDataSource)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train logistic regression models to predict tipped or not (binary classification)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "################################\n", + "# Training models #\n", + "################################\n", + "# build classification model to predict tipped or not\n", + "system.time(logitObj <- rxLogit(tipped ~ passenger_count + trip_distance + trip_time_in_secs + direct_distance, data = featureDataSource))\n", + "summary(logitObj)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Make predictions using the trained logistic regression model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# predict and write the prediction results back to SQL Server table\n", + "outputtable <- paste0(username,\"_scoreoutput\")\n", + "scoredOutput <- RxSqlServerData(\n", + " connectionString = connStr,\n", + " table = outputtable\n", + ")\n", + "\n", + "rxPredict(modelObject = logitObj, data = featureDataSource, outData = scoredOutput, \n", + " predVarNames = \"Score\", type = \"response\", writeModelVars = TRUE, overwrite = TRUE)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Evaluate the model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# plot ROC curve from local compute context\n", + "rxSetComputeContext('local') #change compute context to local, meaning that the plotting is done on local machine\n", + "scoreDF <- rxImport(scoredOutput) # Import data from SQL Server to a data frame on local machine. \n", + "rxRocCurve(\"tipped\", \"Score\", scoreDF) #plot the ROC curve\n", + "rxSetComputeContext(cc) # Switch the compute context to be in database" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Persist (operationalize) the model as a record in the database" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "################################\n", + "# Model operationalization #\n", + "################################\n", + "# First, serialize a model and put it into a database table\n", + "modelbin <- serialize(logitObj, NULL)\n", + "modelbinstr <- paste(modelbin, collapse=\"\")\n", + "\n", + "library(RODBC)\n", + "conn <- odbcDriverConnect(connStr)\n", + "\n", + "# Persist model by calling a stored procedure from SQL\n", + "q <- paste(\"EXEC PersistModel @m='\", modelbinstr,\"', @modeler='\", user_name, \"'\", sep=\"\")\n", + "sqlQuery(conn, q)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Consume the model persisted in database through stored procedure execution" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Request-Response mode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Consume Model through Stored Proc Execution\n", + "# There are two stored Procs - One for predicting on single observation and another for predicting a batch of observations\n", + "\n", + "# Single Observation prediction\n", + "q <- paste(\"EXEC PredictTipSingleMode @passenger_count=1, @trip_distance=2.5, @trip_time_in_secs=631, \n", + " @pickup_latitude=40.763958,@pickup_longitude=-73.973373, @dropoff_latitude=40.782139,@dropoff_longitude=-73.977303,\n", + " @modeler='\", user_name, \"'\", sep=\"\")\n", + "sqlQuery(conn, q)\n", + "\n", + "q <- paste(\"EXEC PredictTipSingleMode @passenger_count=9, @trip_distance=100, @trip_time_in_secs=5214, \n", + "@pickup_latitude=40.75984, @pickup_longitude=-73.9754, @dropoff_latitude=41.0496, @dropoff_longitude=-73.54097,\n", + "@modeler='\", user_name, \"'\", sep=\"\")\n", + "sqlQuery(conn, q)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Batch mode" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Batch mode prediction. Score all data that were not part of the training dataset and select top 10 to preedict\n", + "input <- \"N'select top 3 a.passenger_count as passenger_count, \n", + "\ta.trip_time_in_secs as trip_time_in_secs,\n", + "\ta.trip_distance as trip_distance,\n", + "\ta.dropoff_datetime as dropoff_datetime, \n", + "\tdbo.fnCalculateDistance(pickup_latitude, pickup_longitude, dropoff_latitude,dropoff_longitude) as direct_distance , fare_amount, payment_type\n", + "from\n", + "(\n", + "\tselect medallion, hack_license, pickup_datetime, passenger_count,trip_time_in_secs,trip_distance, \n", + "\t\tdropoff_datetime, pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude, fare_amount, payment_type\n", + "\tfrom nyctaxi_sample\n", + ")a\n", + "left outer join\n", + "(\n", + "select medallion, hack_license, pickup_datetime\n", + "from nyctaxi_sample\n", + "tablesample (70 percent) repeatable (98052)\n", + ")b\n", + "on a.medallion=b.medallion and a.hack_license=b.hack_license and a.pickup_datetime=b.pickup_datetime\n", + "where b.medallion is null\n", + "'\"\n", + "q <- paste(\"EXEC PredictTipBatchMode @inquery = \", input, \", @modeler='\", user_name, \"'\",sep=\"\")\n", + "sqlQuery (conn, q)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "anaconda-cloud": {}, + "kernelspec": { + "display_name": "R", + "language": "R", + "name": "ir" + }, + "language_info": { + "codemirror_mode": "r", + "file_extension": ".r", + "mimetype": "text/x-r-source", + "name": "R", + "pygments_lexer": "r", + "version": "3.3.3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/Misc/KDD2017MRS/add_modeler.sql b/Misc/KDD2017MRS/add_modeler.sql new file mode 100644 index 00000000..73e04bb0 Binary files /dev/null and b/Misc/KDD2017MRS/add_modeler.sql differ diff --git a/Misc/KDD2017MRS/create_login.sql b/Misc/KDD2017MRS/create_login.sql new file mode 100644 index 00000000..095a19e0 --- /dev/null +++ b/Misc/KDD2017MRS/create_login.sql @@ -0,0 +1,37 @@ +USE master +go + + + +-- CHange authentication mode to mixed SQL and Windows authentication + +EXEC xp_instance_regwrite N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'LoginMode', REG_DWORD, 2 + +go + + + +-- Create a new SQL login user + +-- Replace login name 'sqldba' and password with your choice of user name/password + +CREATE LOGIN + + WITH PASSWORD = N'', + + CHECK_POLICY = OFF, + + CHECK_EXPIRATION = OFF; +go + + +-- Change role of new user to sysadmin role + +-- Replace user name 'sqldba' with the one created above + +EXEC sp_addsrvrolemember + + @loginame = N'', + + @rolename = N'sysadmin'; +go \ No newline at end of file diff --git a/Misc/KDD2017MRS/download_ipynbs.ps1 b/Misc/KDD2017MRS/download_ipynbs.ps1 new file mode 100644 index 00000000..399af5c9 --- /dev/null +++ b/Misc/KDD2017MRS/download_ipynbs.ps1 @@ -0,0 +1,2 @@ +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/KDD2017MRS/Prepare%20Jupyter%20Notebooks.ipynb -OutFile "Prepare Jupyter Notebooks.ipynb" +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/KDD2017MRS/SQL_R_Services_End_to_End_Tutorial.ipynb -OutFile SQL_R_Services_End_to_End_Tutorial.ipynb diff --git a/Misc/KDD2017MRS/dsvm_provisioning/RunDSVMCreation.ps1 b/Misc/KDD2017MRS/dsvm_provisioning/RunDSVMCreation.ps1 new file mode 100644 index 00000000..cab5f5ab --- /dev/null +++ b/Misc/KDD2017MRS/dsvm_provisioning/RunDSVMCreation.ps1 @@ -0,0 +1,44 @@ +####################################################################### +# FIRST LOGIN AND SAVE PROFILE IN A FILE WHICH WILL BE USED FOR SUBMITTING JOBS IN PARALLEL +####################################################################### +Login-AzureRmAccount + +$SubscriptionName = "Microsoft Azure Internal - Vanja" +$SubscriptionID = "" +$TenantID = "" +$resourcegroup = "KDDHalifax2017"; +$location = "West US" +#$sourceStorageName = "scaler"; +Set-AzureRmContext -SubscriptionID $SubscriptionID -TenantID $TenantID +#Get-AzureSubscription -Current +#$sourceKey = (Get-AzureRmStorageAccountKey -ResourceGroupName $resourcegroup -Name $sourceStorageName).Value[0] +$profilepath = "C:\Users\vapaunic\repos\KDD2017R\Scripts\rprofile2" +Save-AzureRmContext -Path $profilepath + +####################################################################### +# SPECIFY CLUSTER PREFIX + OUTPUT FILE WHERE CLUSTER PWDS WILL BE SAVED +####################################################################### +$dsvmPrefix = "kddr"; +$outFile = "C:\Users\vapaunic\repos\KDD2017R\Scripts\dsvmOutFileIS.csv" + +####################################################################### +# INDICATE START AND END INDEXES OF CLUSTERS +####################################################################### +$dsvmstartIndex = 1; +$dsvmendIndex = 5; + +####################################################################### +# SUBMIT CLUSTER JOBS TO BE RUN IN PARALLEL +# NOTE: YOU NEED TO SPECIFY THE LOCATION OF FUNCTION FILE, C:\Users\vapaunic\repos\KDD2017R\Scripts\SubmitDSVMCreation.ps1 +####################################################################### +for($i=$dsvmstartIndex; $i -le $dsvmendIndex; $i++){ + $vmname = $dsvmPrefix+$i; + $randnum = Get-Random -minimum 1 -maximum 100001 + $adminPassword = $vmname + "_" + $randnum + $vmname,$adminPassword -join ',' | out-file -filepath $outFile -append -Width 200; + + Start-Job -ScriptBlock {C:\Users\vapaunic\repos\KDD2017R\Scripts\SubmitDSVMCreation.ps1 $args[0] $args[1] $args[2] $args[3]} -ArgumentList @($vmname, $resourcegroup, $profilepath, $adminPassword) +} +####################################################################### +# END +####################################################################### \ No newline at end of file diff --git a/Misc/KDD2017MRS/dsvm_provisioning/RunDSVMDeletion.ps1 b/Misc/KDD2017MRS/dsvm_provisioning/RunDSVMDeletion.ps1 new file mode 100644 index 00000000..bdd38034 --- /dev/null +++ b/Misc/KDD2017MRS/dsvm_provisioning/RunDSVMDeletion.ps1 @@ -0,0 +1,36 @@ +####################################################################### +# FIRST LOGIN AND SAVE PROFILE IN A FILE WHICH WILL BE USED FOR SUBMITTING JOBS IN PARALLEL +####################################################################### +Login-AzureRmAccount + +$SubscriptionName = "Azure Internal - KDD 2016" +$SubscriptionID = "3a6c6f94-9a97-4b16-85ba-53a9da62f597" +$TenantID = "72f988bf-86f1-41af-91ab-2d7cd011db47" +$resourcegroup = "StrataDevDSVM"; +$location = "West US" +Set-AzureRmContext -SubscriptionID $SubscriptionID -TenantID $TenantID +$profilepath = "C:\Users\deguhath\Desktop\CDSP\Spark\StrataSanJose\ProvisionScripts\rprofile" +Save-AzureRmProfile -Path $profilepath + +####################################################################### +# SPECIFY CLUSTER PREFIX + OUTPUT FILE WHERE CLUSTER PWDS WILL BE SAVED +####################################################################### +$dsvmPrefix = "stratasjr"; + +####################################################################### +# INDICATE START AND END INDEXES OF CLUSTERS +####################################################################### +$dsvmstartIndex = 1; +$dsvmendIndex = 30; + +####################################################################### +# SUBMIT CLUSTER JOBS TO BE RUN IN PARALLEL +# NOTE: YOU NEED TO SPECIFY THE LOCATION OF FUNCTION FILE, C:\Users\deguhath\Source\Repos\KDD2016 Spark\Scripts\SubmitHDICreation.ps1 +####################################################################### +for($i=$dsvmstartIndex; $i -le $dsvmendIndex; $i++){ + $vmname = $dsvmPrefix+$i; + Start-Job -ScriptBlock {C:\Users\deguhath\Desktop\CDSP\Spark\StrataSanJose\ProvisionScripts\SubmitDSVMDeletion.ps1 $args[0] $args[1] $args[2]} -ArgumentList @($vmname, $resourcegroup, $profilepath) +} +####################################################################### +# END +####################################################################### \ No newline at end of file diff --git a/Misc/KDD2017MRS/dsvm_provisioning/SubmitDSVMCreation.ps1 b/Misc/KDD2017MRS/dsvm_provisioning/SubmitDSVMCreation.ps1 new file mode 100644 index 00000000..ec8be580 --- /dev/null +++ b/Misc/KDD2017MRS/dsvm_provisioning/SubmitDSVMCreation.ps1 @@ -0,0 +1,12 @@ +param([string]$vmname, [string]$resourcegroup, [string]$profilepath, [string]$adminPassword) + +function SubmitDSVMCreation { + param([string]$vmname, [string]$resourcegroup, [string]$profilepath, [string]$adminPassword) + $templatePath = "C:\Users\vapaunic\repos\KDD2017R\Scripts\azuredeployDSVM.json"; + Select-AzureRmProfile -Path $profilepath; + + $dsvmparams = @{vmName=$vmname;adminUsername="remoteuser";adminPassword=$adminPassword}; + New-AzureRmResourceGroupDeployment -Name $vmname -ResourceGroupName $resourcegroup -TemplateUri $templatePath -TemplateParameterObject $dsvmparams; +} + +SubmitDSVMCreation -vmname $vmname -resourcegroup $resourcegroup -profilepath $profilepath -adminPassword $adminPassword diff --git a/Misc/KDD2017MRS/dsvm_provisioning/SubmitDSVMDeletion.ps1 b/Misc/KDD2017MRS/dsvm_provisioning/SubmitDSVMDeletion.ps1 new file mode 100644 index 00000000..09af1303 --- /dev/null +++ b/Misc/KDD2017MRS/dsvm_provisioning/SubmitDSVMDeletion.ps1 @@ -0,0 +1,31 @@ +param([string]$vmname, [string]$resourcegroup, [string]$profilepath) + +function SubmitDSVMDeletion { + param([string]$vmname, [string]$resourcegroup, [string]$profilepath) + + # Get profile from saved profile + Select-AzureRmProfile -Path $profilepath; + + # Delete cluster + Remove-AzureRmVM -ResourceGroupName $resourcegroup -Name $vmname -Force + + # Remove storage account for cluster + $storageName = $vmname+'sjcstorage'; + Remove-AzureRmStorageAccount -ResourceGroupName $resourcegroup -Name $storageName -Force + + # Remove AzureRmNetworkInterface + Remove-AzureRmNetworkInterface -ResourceGroupName $resourcegroup -Name $vmname -Force + + # Remove AzureNetworkSecurityGroup + $nsgName = $vmname+'_NSG'; + Remove-AzureRmNetworkSecurityGroup -ResourceGroupName $resourcegroup -Name $nsgName -Force + + # Remove AzureRmPublicIpAddress + Remove-AzureRmPublicIpAddress -ResourceGroupName $resourcegroup -Name $vmname -Force + + # Remove AzureRmVirtualNetwork + Remove-AzureRmVirtualNetwork -ResourceGroupName $resourcegroup -Name $vmname -Force + +} + +SubmitDSVMDeletion -vmname $vmname -resourcegroup $resourcegroup -profilepath $profilepath diff --git a/Misc/KDD2017MRS/dsvm_provisioning/azuredeployDSVM.JSON b/Misc/KDD2017MRS/dsvm_provisioning/azuredeployDSVM.JSON new file mode 100644 index 00000000..42985896 --- /dev/null +++ b/Misc/KDD2017MRS/dsvm_provisioning/azuredeployDSVM.JSON @@ -0,0 +1,312 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "adminUsername": { + "type": "string", + "metadata": { + "description": "remoteuser" + } + }, + "adminPassword": { + "type": "securestring", + "metadata": { + "description": "KDD2017@0816" + } + }, + "vmName": { + "type": "string", + "metadata": { + "description": "StrataR" + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_DS12_v2", + "metadata": { + "description": "Standard_DS12_v2" + } + } + }, + "variables": { + "location": "[resourceGroup().location]", + "imagePublisher": "microsoft-ads", + "imageOffer": "linux-data-science-vm-ubuntu", + "OSDiskName": "osdiskforlinuxsimple", + "DataDiskName": "datadiskforlinuxsimple", + "sku": "linuxdsvm", + "nicName": "[parameters('vmName')]", + "addressPrefix": "10.0.0.0/16", + "subnetName": "Subnet", + "subnetPrefix": "10.0.0.0/24", + "storageAccountType": "Standard_LRS", + "storageAccountName": "[concat(parameters('vmName'),'sjcstorage')]", + "publicIPAddressType": "Dynamic", + "publicIPAddressName": "[parameters('vmName')]", + "vmStorageAccountContainerName": "vhds", + "vmName": "[parameters('vmName')]", + "vmSize": "[parameters('vmSize')]", + "virtualNetworkName": "[parameters('vmName')]", + "networkSecurityGroupName": "[concat(parameters('vmName'),'_NSG')]", + "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", + "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", + "fileUris": "https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Scripts/DSVM_Customization_Script.sh", + "commandToExecute": "dos2unix < DSVM_Customization_Script.sh > runscript.sh; bash runscript.sh", + "apiVersion": "2015-06-15", + "version": "latest" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "apiVersion": "2015-05-01-preview", + "location": "[variables('location')]", + "properties": { + "accountType": "[variables('storageAccountType')]" + } + }, + { + "apiVersion": "2015-05-01-preview", + "type": "Microsoft.Network/publicIPAddresses", + "name": "[variables('publicIPAddressName')]", + "location": "[variables('location')]", + "properties": { + "publicIPAllocationMethod": "[variables('publicIPAddressType')]", + "dnsSettings": { + "domainNameLabel": "[variables('publicIPAddressName')]" + } + } + }, + { + "apiVersion": "2015-05-01-preview", + "type": "Microsoft.Network/virtualNetworks", + "name": "[variables('virtualNetworkName')]", + "location": "[variables('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[variables('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetPrefix')]" + } + } + ] + } + }, + { + "type": "Microsoft.Network/networkSecurityGroups", + "name": "[variables('networkSecurityGroupName')]", + "apiVersion": "2015-05-01-preview", + "location": "[variables('location')]", + "properties": { + "securityRules": [ + { + "name": "yarndashboard", + "properties": { + "priority": 1010, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "8088", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "jupyterhub", + "properties": { + "priority": 1020, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "8000", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "Jupyter", + "properties": { + "priority": 1030, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "9999", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "RStudioServer", + "properties": { + "priority": 1040, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "8787", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "mrsdeploy", + "properties": { + "priority": 1050, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "12800", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "default-allow-ssh", + "properties": { + "priority": 1060, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "22", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + } + ] + } + }, + { + "apiVersion": "2015-05-01-preview", + "type": "Microsoft.Network/networkInterfaces", + "name": "[variables('nicName')]", + "location": "[variables('location')]", + "dependsOn": [ + "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]", + "[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]" + ], + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" + }, + "subnet": { + "id": "[variables('subnetRef')]" + } + } + } + ], + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]" + } + } + }, + { + "apiVersion": "2015-06-15", + "type": "Microsoft.Compute/virtualMachines", + "name": "[variables('vmName')]", + "location": "[variables('location')]", + "plan": { + "name": "linuxdsvmubuntu", + "publisher": "microsoft-ads", + "product": "linux-data-science-vm-ubuntu" + }, + "tags": { + "Application": "DataScience" + }, + "dependsOn": [ + "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", + "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "[variables('vmSize')]" + }, + "osProfile": { + "computerName": "[variables('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]" + }, + "storageProfile": { + "imageReference": { + "publisher": "microsoft-ads", + "offer": "linux-data-science-vm-ubuntu", + "sku": "linuxdsvmubuntu", + "version": "latest" + }, + "osDisk": { + "name": "osdisk", + "vhd": { + "uri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'), variables('vmName'), '.vhd')]" + }, + "caching": "ReadWrite", + "createOption": "FromImage" + }, + "dataDisks": [ + { + "name": "data-0", + "vhd": { + "uri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('DataDiskName'), variables('vmName'), '.vhd')]" + }, + "caching": "None", + "createOption": "FromImage", + "lun": 0 + } + ] + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": "true", + "storageUri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net')]" + } + } + }, + "resources": [ + { + "type": "extensions", + "name": "[variables('vmName')]", + "apiVersion": "[variables('apiVersion')]", + "location": "[variables('location')]", + "dependsOn": [ + "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'))]" + ], + "properties": { + "publisher": "Microsoft.Azure.Extensions", + "type": "CustomScript", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": { + "fileUris": ["[variables('fileUris')]"], + "commandToExecute": "[variables('commandToExecute')]" + } + } + } + ] + } + ], + "outputs": { + "DataScienceVmUrl": { "type": "string", "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'))]" } + } +} \ No newline at end of file diff --git a/Misc/KDD2017MRS/modify_sp_persistmodel.sql b/Misc/KDD2017MRS/modify_sp_persistmodel.sql new file mode 100644 index 00000000..51ea6b53 Binary files /dev/null and b/Misc/KDD2017MRS/modify_sp_persistmodel.sql differ diff --git a/Misc/KDD2017MRS/modify_sp_predicttipbatch.sql b/Misc/KDD2017MRS/modify_sp_predicttipbatch.sql new file mode 100644 index 00000000..2fff916d Binary files /dev/null and b/Misc/KDD2017MRS/modify_sp_predicttipbatch.sql differ diff --git a/Misc/KDD2017MRS/modify_sp_predicttipsingle.sql b/Misc/KDD2017MRS/modify_sp_predicttipsingle.sql new file mode 100644 index 00000000..d4226b3c Binary files /dev/null and b/Misc/KDD2017MRS/modify_sp_predicttipsingle.sql differ diff --git a/Misc/KDD2017MRS/run_createlogin.ps1 b/Misc/KDD2017MRS/run_createlogin.ps1 new file mode 100644 index 00000000..ff3cfbf4 --- /dev/null +++ b/Misc/KDD2017MRS/run_createlogin.ps1 @@ -0,0 +1,155 @@ +$server = 'localhost' +$serverName = Read-Host -Prompt 'Input the name of the SQL Server machine' +$numAccounts = Read-Host -Prompt 'Input the number of SQL accounts to create' +$accountFile = Read-Host -Prompt 'Input the path and file name that is going to store the account information' +$sqlFile = Read-Host -Prompt 'Input the path and name of the sql file' + +#Connect to MS SQL Server + +try + +{ + + $SQLConnection = New-Object System.Data.SqlClient.SqlConnection + + $SQLConnection.ConnectionString = "Server=" + $server + ";Database=master;Integrated Security=True" + + $SQLConnection.Open() + +} + +#Error of connection + +catch + +{ + + Write-Host $Error[0] -ForegroundColor Red + + exit 1 + +} + +#The GO switch is specified - parsing T-SQL code with GO + +function ExecuteSQLFile($sqlfile, $login, $pass) + +{ + + + #Reading the T-SQL file as a whole packet + + $SQLCommandText = @(Get-Content -Path $sqlfile) + $SQLCommandText = $SQLCommandText -replace "", $login + $SQLCommandText = $SQLCommandText -replace "", $pass + $SQLCommandText | Out-File "tmp.sql" + + #Execution of SQL packet + + foreach($SQLString in $SQLCommandText) + + { + + if($SQLString -ne "go") + + { + + $SQLPacket += $SQLString + "`n" + + } + + else + + { + + Write-Host "---------------------------------------------" + + Write-Host "Executed SQL packet:" + + Write-Host $SQLPacket + + $IsSQLErr = $false + + #Execution of SQL packet + + try + + { + + $SQLCommand = New-Object System.Data.SqlClient.SqlCommand($SQLPacket, $SQLConnection) + + $SQLCommand.CommandTimeout = 0 + + $SQLCommand.ExecuteScalar() + + } + + catch + + { + + + + $IsSQLErr = $true + + Write-Host $Error[0] -ForegroundColor Red + + $SQLPacket | Out-File -FilePath ($PWD.Path + "\SQLErrors.txt") -Append + + $Error[0] | Out-File -FilePath ($PWD.Path + "\SQLErrors.txt") -Append + + "----------" | Out-File -FilePath ($PWD.Path + "\SQLErrors.txt") -Append + + } + + if(-not $IsSQLErr) + + { + + Write-Host "Execution succesful" + + } + + else + + { + + Write-Host "Execution failed" -ForegroundColor Red + + } + + $SQLPacket = "" + + } + + } + + + Write-Host "-----------------------------------------" + + Write-Host $sqlfile "execution done" + +} + + + +Write-Host "Start creating logins for your database. It may take a while..." +echo "server_name, login_name, password" >> $accountFile +$start_time = Get-Date +for ($i=0; $i -lt $numAccounts; $i++) { + $pwd_rn = Get-Random -Maximum 99999 -Minimum 10000 + $login_rn = Get-Random -Maximum 99999 -Minimum 10000 + $login_name = "sqluser" + $login_rn + $pass = "Kdd17@Halifax_" + $pwd_rn + + ExecuteSQLFile $sqlFile $login_name $pass + echo $serverName","$login_name","$pass >> $accountFile +} + +$end_time = Get-Date + +$time_span = $end_time - $start_time + +$total_seconds = [math]::Round($time_span.TotalSeconds,2) + +Write-Host "This step (creating logins) takes $total_seconds seconds." \ No newline at end of file diff --git a/Misc/PythonSQL/DeserializeSavePlots.py b/Misc/PythonSQL/DeserializeSavePlots.py new file mode 100644 index 00000000..237df87a --- /dev/null +++ b/Misc/PythonSQL/DeserializeSavePlots.py @@ -0,0 +1,13 @@ +import pyodbc +import pickle +import os + +cnxn = pyodbc.connect('DRIVER={SQL Server};SERVER={SERVER_NAME};DATABASE={DB_NAME};UID={USER_NAME};PWD={PASSWORD}') +cursor = cnxn.cursor() +cursor.execute("EXECUTE [dbo].[SerializePlots]") +tables = cursor.fetchall() +for i, table in enumerate(tables): + fig = pickle.loads(table[0]) + fig.savefig(str(i)+'.png') + +print("The plots are saved in directory:", os.getcwd()) diff --git a/Misc/PythonSQL/DeserializeSavePlots2.py b/Misc/PythonSQL/DeserializeSavePlots2.py new file mode 100644 index 00000000..558b0bbf --- /dev/null +++ b/Misc/PythonSQL/DeserializeSavePlots2.py @@ -0,0 +1,13 @@ +import pyodbc +import pickle +import os + +cnxn = pyodbc.connect('DRIVER={SQL Server};SERVER={SERVER_NAME};DATABASE={DB_NAME};Trusted_Connection=yes;') +cursor = cnxn.cursor() +cursor.execute("EXECUTE [dbo].[SerializePlots]") +tables = cursor.fetchall() +for i, table in enumerate(tables): + fig = pickle.loads(table[0]) + fig.savefig(str(i)+'.png') + +print("The plots are saved in directory:", os.getcwd()) diff --git a/Misc/PythonSQL/Download_Scripts_SQL_Walkthrough.ps1 b/Misc/PythonSQL/Download_Scripts_SQL_Walkthrough.ps1 new file mode 100644 index 00000000..d6966d95 --- /dev/null +++ b/Misc/PythonSQL/Download_Scripts_SQL_Walkthrough.ps1 @@ -0,0 +1,66 @@ +################################################################################ +# Copyright (c) Microsoft. All rights reserved. +# +# Apache 2.0 License +# +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. See the License for the specific language governing +# permissions and limitations under the License. +# +################################################################################ +param([string]$DestDir) + +$web_client = new-object System.Net.WebClient + + +function DownloadRawFromGitWithFileList($base_url, $file_list_name, $destination_dir) +{ + # Download the list so we can iterate over it. + $tempPath = [IO.Path]::GetTempFileName() + $url = $base_url + $file_list_name + $web_client.DownloadFile($url, $tempPath) + + # Iterate over the different lines in the file and add them to the machine + $reader = [System.IO.File]::OpenText($tempPath) + try { + for(;;) { + $line = $reader.ReadLine() + if ($line -eq $null) { break } + + # download the file specified by this line... + $url = $base_url + $line + $destination = Join-Path $destination_dir $line + $web_client.DownloadFile($url, $destination) + } + } + finally { + $reader.Close() + } +} + +function GetSampleFilesFromGit($gitdir_name, $list_name, $destination_dir){ + #Write-Output "Getting Sample Notebooks from Azure-MachineLearning-DataScience Git Repository" + $file_url = "https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/" + $gitdir_name + "/" + DownloadRawFromGitWithFileList $file_url $list_name $destination_dir +} + + +###################### End of Functions / Start of Script ###################### +if (!(Test-Path $DestDir)) { + Write-Output "$DestDir does not exist and is created." + mkdir $DestDir +} +Write-Output "Start downloading the data file to $DestDir. It may take a while..." +$file_url = "http://getgoing.blob.core.windows.net/public/nyctaxi1pct.csv" +$file_dest = Join-Path $DestDir "nyctaxi1pct.csv" +$web_client.DownloadFile($file_url, $file_dest) +Write-Output "Fetching the sample script files to $DestDir..." +GetSampleFilesFromGit "PythonSQL" "FilestoDownload_SQL_Walkthrough.txt" $DestDir +Write-Output "Fetching the sample script files completed." +Write-Output "Now entering the destination directory $DestDir." +cd $DestDir diff --git a/Misc/PythonSQL/FilestoDownload_SQL_Walkthrough.txt b/Misc/PythonSQL/FilestoDownload_SQL_Walkthrough.txt new file mode 100644 index 00000000..41a53c1e --- /dev/null +++ b/Misc/PythonSQL/FilestoDownload_SQL_Walkthrough.txt @@ -0,0 +1,15 @@ +PredictTipSciKitPy.sql +PredictTipSingleModeSciKitPy.sql +TrainTipPredictionModelSciKitPy.sql +PredictTipRxPy.sql +PredictTipSingleModeRxPy.sql +TrainTipPredictionModelRxPy.sql +TrainingTestingSplit.sql +create-db-tb-upload-data.sql +fnCalculateDistance.sql +fnEngineerFeatures.sql +SerializePlots.sql +RunSQL_SQL_Walkthrough.ps1 +varbinplot.fmt +taxiimportfmt.xml +DeserializeSavePlots.py diff --git a/Misc/PythonSQL/PredictTipRxPy.sql b/Misc/PythonSQL/PredictTipRxPy.sql new file mode 100644 index 00000000..ee658f01 --- /dev/null +++ b/Misc/PythonSQL/PredictTipRxPy.sql @@ -0,0 +1,49 @@ +/****** Object: StoredProcedure [dbo].[PredictTipSciKitPy] Script Date: 5/17/2017 11:37:50 PM ******/ + +USE [TaxiNYC_Sample] +GO + +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +DROP PROCEDURE IF EXISTS PredictTipRxPy; +GO + +CREATE PROCEDURE [dbo].[PredictTipRxPy] (@model varchar(50), @inquery nvarchar(max)) +AS +BEGIN + DECLARE @lmodel2 varbinary(max) = (select model from nyc_taxi_models where name = @model); + + EXEC sp_execute_external_script + @language = N'Python', + @script = N' +import pickle +import numpy +import pandas +from sklearn import metrics +from revoscalepy.functions.RxPredict import rx_predict_ex + +mod = pickle.loads(lmodel2) +X = InputDataSet[["passenger_count", "trip_distance", "trip_time_in_secs", "direct_distance"]] +y = numpy.ravel(InputDataSet[["tipped"]]) + +prob_array = rx_predict_ex(mod, X) +prob_list = list(prob_rrray._results["tipped_Pred"]) + +prob_array = numpy.asarray(prob_list) +fpr, tpr, thresholds = metrics.roc_curve(y, prob_array) +auc_result = metrics.auc(fpr, tpr) +print("AUC on testing data is:", auc_result) +OutputDataSet = pandas.DataFrame(data=prob_list, columns=["predictions"]) +', + @input_data_1 = @inquery, + @input_data_1_name = N'InputDataSet', + @params = N'@lmodel2 varbinary(max)', + @lmodel2 = @lmodel2 + WITH RESULT SETS ((Score float)); + +END +GO diff --git a/Misc/PythonSQL/PredictTipSciKitPy.sql b/Misc/PythonSQL/PredictTipSciKitPy.sql new file mode 100644 index 00000000..3cfee812 --- /dev/null +++ b/Misc/PythonSQL/PredictTipSciKitPy.sql @@ -0,0 +1,51 @@ +/****** Object: StoredProcedure [dbo].[PredictTipSciKitPy] Script Date: 5/17/2017 11:37:50 PM ******/ + +USE [TaxiNYC_Sample] +GO + +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + + +DROP PROCEDURE IF EXISTS PredictTipSciKitPy; +GO + +CREATE PROCEDURE [dbo].[PredictTipSciKitPy] (@model varchar(50), @inquery nvarchar(max)) +AS +BEGIN + DECLARE @lmodel2 varbinary(max) = (select model from nyc_taxi_models where name = @model); + + EXEC sp_execute_external_script + @language = N'Python', + @script = N' +import pickle +import numpy +import pandas +from sklearn import metrics + +mod = pickle.loads(lmodel2) + +X = InputDataSet[["passenger_count", "trip_distance", "trip_time_in_secs", "direct_distance"]] +y = numpy.ravel(InputDataSet[["tipped"]]) + +prob_array = mod.predict_proba(X) +prob_list = [item[1] for item in prob_array] + +prob_array = numpy.asarray(prob_list) +fpr, tpr, thresholds = metrics.roc_curve(y, prob_array) +auc_result = metrics.auc(fpr, tpr) +print("AUC on testing data is:", auc_result) + +OutputDataSet = pandas.DataFrame(data=prob_list, columns=["predictions"]) +', + @input_data_1 = @inquery, + @input_data_1_name = N'InputDataSet', + @params = N'@lmodel2 varbinary(max)', + @lmodel2 = @lmodel2 + WITH RESULT SETS ((Score float)); + +END +GO diff --git a/Misc/PythonSQL/PredictTipSingleModeRxPy.sql b/Misc/PythonSQL/PredictTipSingleModeRxPy.sql new file mode 100644 index 00000000..2bf5bac5 --- /dev/null +++ b/Misc/PythonSQL/PredictTipSingleModeRxPy.sql @@ -0,0 +1,71 @@ +USE [TaxiNYC_Sample] +GO + +/****** Object: StoredProcedure [dbo].[PredictTipSingleModeRxPy] Script Date: 5/17/2017 3:09:40 PM ******/ +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +CREATE PROCEDURE [dbo].[PredictTipSingleModeRxPy] (@model varchar(50), @passenger_count int = 0, +@trip_distance float = 0, +@trip_time_in_secs int = 0, +@pickup_latitude float = 0, +@pickup_longitude float = 0, +@dropoff_latitude float = 0, +@dropoff_longitude float = 0) +AS +BEGIN + DECLARE @inquery nvarchar(max) = N' + SELECT * FROM [dbo].[fnEngineerFeatures]( + @passenger_count, + @trip_distance, + @trip_time_in_secs, + @pickup_latitude, + @pickup_longitude, + @dropoff_latitude, + @dropoff_longitude) + ' + DECLARE @lmodel2 varbinary(max) = (select model from nyc_taxi_models where name = @model); + EXEC sp_execute_external_script + @language = N'Python', + @script = N' +import pickle +import numpy +import pandas +from revoscalepy.functions.RxPredict import rx_predict_ex + +# Load model and unserialize +mod = pickle.loads(model) + +# Get features for scoring from input data +x = InputDataSet[["passenger_count", "trip_distance", "trip_time_in_secs", "direct_distance"]] + +# Score data to get tip prediction probability as a list (of float) + +prob_array = rx_predict_ex(mod, x) + +prob_list = [prob_array._results["tipped_Pred"]] + +# Create output data frame +OutputDataSet = pandas.DataFrame(data=prob_list, columns=["predictions"]) + ', + @input_data_1 = @inquery, + @params = N'@model varbinary(max),@passenger_count int,@trip_distance float, + @trip_time_in_secs int , + @pickup_latitude float , + @pickup_longitude float , + @dropoff_latitude float , + @dropoff_longitude float', + @model = @lmodel2, + @passenger_count =@passenger_count , + @trip_distance=@trip_distance, + @trip_time_in_secs=@trip_time_in_secs, + @pickup_latitude=@pickup_latitude, + @pickup_longitude=@pickup_longitude, + @dropoff_latitude=@dropoff_latitude, + @dropoff_longitude=@dropoff_longitude + WITH RESULT SETS ((Score float)); +END +GO diff --git a/Misc/PythonSQL/PredictTipSingleModeSciKitPy.sql b/Misc/PythonSQL/PredictTipSingleModeSciKitPy.sql new file mode 100644 index 00000000..7c350614 --- /dev/null +++ b/Misc/PythonSQL/PredictTipSingleModeSciKitPy.sql @@ -0,0 +1,67 @@ +USE [TaxiNYC_Sample] +GO + +/****** Object: StoredProcedure [dbo].[PredictTipSingleModeSciKitPy] Script Date: 4/26/2017 3:09:40 PM ******/ +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +CREATE PROCEDURE [dbo].[PredictTipSingleModeSciKitPy] (@model varchar(50), @passenger_count int = 0, +@trip_distance float = 0, +@trip_time_in_secs int = 0, +@pickup_latitude float = 0, +@pickup_longitude float = 0, +@dropoff_latitude float = 0, +@dropoff_longitude float = 0) +AS +BEGIN + DECLARE @inquery nvarchar(max) = N' + SELECT * FROM [dbo].[fnEngineerFeatures]( + @passenger_count, + @trip_distance, + @trip_time_in_secs, + @pickup_latitude, + @pickup_longitude, + @dropoff_latitude, + @dropoff_longitude) + ' + DECLARE @lmodel2 varbinary(max) = (select model from nyc_taxi_models where name = @model); + EXEC sp_execute_external_script + @language = N'Python', + @script = N' +import pickle +import numpy +import pandas + +# Load model and unserialize +mod = pickle.loads(model) + +# Get features for scoring from input data +X = InputDataSet[["passenger_count", "trip_distance", "trip_time_in_secs", "direct_distance"]] + +# Score data to get tip prediction probability as a list (of float) +prob = [mod.predict_proba(X)[0][1]] + +# Create output data frame +OutputDataSet = pandas.DataFrame(data=prob, columns=["predictions"]) + ', + @input_data_1 = @inquery, + @params = N'@model varbinary(max),@passenger_count int,@trip_distance float, + @trip_time_in_secs int , + @pickup_latitude float , + @pickup_longitude float , + @dropoff_latitude float , + @dropoff_longitude float', + @model = @lmodel2, + @passenger_count =@passenger_count , + @trip_distance=@trip_distance, + @trip_time_in_secs=@trip_time_in_secs, + @pickup_latitude=@pickup_latitude, + @pickup_longitude=@pickup_longitude, + @dropoff_latitude=@dropoff_latitude, + @dropoff_longitude=@dropoff_longitude + WITH RESULT SETS ((Score float)); +END +GO diff --git a/Misc/PythonSQL/Readme.md b/Misc/PythonSQL/Readme.md new file mode 100644 index 00000000..831fd5d4 --- /dev/null +++ b/Misc/PythonSQL/Readme.md @@ -0,0 +1,3 @@ +SQL Server 2017 (CTP 2.0) with Machine Learning Services now includes Python-based advanced analytics (AA) capabilities. Python is one of the most popular languages for AA wtih a rich ecosystem of libraries. Therefore, the 2017 release significantly enhances the AA capabilities of SQL Server and makes it an accessible platform for large community of data scientists & programmers developing intelligent applications. + +This folder in the repo contains code and instructions for SQL-developers on how to perform a walkthrough for creating intelligent applications in SQL Server 2017 (CTP 2.0) using Python. diff --git a/Misc/PythonSQL/RunSQL_SQL_Walkthrough.ps1 b/Misc/PythonSQL/RunSQL_SQL_Walkthrough.ps1 new file mode 100644 index 00000000..77542a78 --- /dev/null +++ b/Misc/PythonSQL/RunSQL_SQL_Walkthrough.ps1 @@ -0,0 +1,275 @@ +<#-------------------------------------------------------------------------- +.SYNOPSIS +Script for running T-SQL files in MS SQL Server +Hang Zhang +Built on a post by Andy Mishechkin at https://gallery.technet.microsoft.com/scriptcenter/The-PowerShell-script-for-2a2456c4 +This script has been tested on PowerShell V3, and not tested on older versions. + +.DESCRIPTION + +.\RunSQL_SQL_Walkthrough.ps1 + +Users will be prompted to input parameters for: +- name of Microsoft SQL Server with R Service instance +- database name that you want to create and use in this walkthrough +- path and name of the .csv file on the local machine to be loaded to the database +- user name which has the previliges of creating database, tables, stored procedures, and uploading data to tables +- password of users + +Examples. + +Execute on remote SQL Server Express with +.\RunSQL_SQL_Walkthrough.ps1 + +---------------------------------------------------------------------------#> + +function DownloadAndInstall($DownloadPath, $ArgsForInstall, $DownloadFileType = "exe") +{ + $LocalPath = [IO.Path]::GetTempFileName() + "." + $DownloadFileType + $web_client.DownloadFile($DownloadPath, $LocalPath) + + Start-Process -FilePath $LocalPath -ArgumentList $ArgsForInstall -Wait +} + +function InstallSQLUtilities(){ + # Install SQL Server Command Line Utilities + $b = Get-WmiObject -Class Win32_Product | sort-object Name | select Name | where { $_.Name -match “Microsoft SQL Server" -and $_.Name -match "Command Line Utilities" } + if($b -eq $null) + { + Write-Output "SQL Server Command Line Utilities not installed. Download and install SQL Server Command Line Utilities" -ForegroundColor "Yellow" + $os = Get-WMIObject win32_operatingsystem + $os_bit = $os.OSArchitecture + if($os_bit -eq '64-bit') + { + $download_url1 = "http://go.microsoft.com/fwlink/?LinkID=188401&clcid=0x409" + $download_url2 = "http://go.microsoft.com/fwlink/?LinkID=188430&clcid=0x409" + } + else + { + $download_url1 = "http://go.microsoft.com/fwlink/?LinkID=188400&clcid=0x409" + $download_url2 = "http://go.microsoft.com/fwlink/?LinkID=188429&clcid=0x409" + } + Write-host "Installing SQL Server Native Client..." -ForegroundColor "Yellow" + DownloadAndInstall $download_url1 "/quiet IACCEPTSQLNCLILICENSETERMS=YES" "msi" + Write-host "Installing SQL Command Line Utilities..." -ForegroundColor "Yellow" + DownloadAndInstall $download_url2 "/quiet" "msi" + } +} + +function SearchBCP(){ + $bcp_list = Get-ChildItem -Path "C:\Program Files*\" -Filter bcp.exe -Recurse -ErrorAction SilentlyContinue -Force | where {$_.FullName -like '*\bcp.exe'} + + if ($bcp_list -ne $null){ + $bcp_path = @('')*$bcp_list.count + for ($i=0; $i -lt $bcp_list.count; $i++){ + $bcp_path[$i] = $bcp_list[$i].DirectoryName + } + } + return $bcp_path +} + +function ExecuteSQLFile($sqlfile,$go_or_not) +{ + if($go_or_not -eq 1) + { + $SQLCommandText = @(Get-Content -Path $sqlfile) + foreach($SQLString in $SQLCommandText) + { + if($SQLString -ne "go") + { + #Preparation of SQL packet + if($SQLString -match "SET @path_to_data") + { + $SQLPacket += "SET @path_to_data = '" + $csvfilepath + "'`n" + } + Elseif($SQLString.ToLower() -match "set @db_name") + { + $SQLPacket += "set @db_name = '" + $dbname + "'`n" + } + Elseif($SQLString -match "SET @db_name") + { + $SQLPacket += "SET @db_name = " + $dbname + "`n" + } + Elseif($SQLString.ToLower() -match "use \[taxinyc_sample") + { + $SQLPacket += "USE [" + $dbname +"]`n" + } + Else + { + $SQLPacket += $SQLString + "`n" + } + } + else + { + Write-Host "---------------------------------------------" + Write-Host "Executed SQL packet:" + Write-Host $SQLPacket + $IsSQLErr = $false + #Execution of SQL packet + try + { + $SQLCommand = New-Object System.Data.SqlClient.SqlCommand($SQLPacket, $SQLConnection) + $SQLCommand.CommandTimeout = 0 + $SQLCommand.ExecuteScalar() + } + catch + { + + $IsSQLErr = $true + Write-Host $Error[0] -ForegroundColor Red + $SQLPacket | Out-File -FilePath ($PWD.Path + "\SQLErrors.txt") -Append + $Error[0] | Out-File -FilePath ($PWD.Path + "\SQLErrors.txt") -Append + "----------" | Out-File -FilePath ($PWD.Path + "\SQLErrors.txt") -Append + } + if(-not $IsSQLErr) + { + Write-Host "Execution succesful" + } + else + { + Write-Host "Execution failed" -ForegroundColor Red + } + $SQLPacket = "" + } + } + } + else + { + #Reading the T-SQL file as a whole packet + $SQLCommandText = @([IO.File]::ReadAllText($sqlfile)) + #Execution of SQL packet + try + { + $SQLCommand = New-Object System.Data.SqlClient.SqlCommand($SQLCommandText, $SQLConnection) + $SQLCommand.CommandTimeout = 0 + $SQLCommand.ExecuteScalar() + } + catch + { + Write-Host $Error[0] -ForegroundColor Red + } + } + #Disconnection from MS SQL Server + + Write-Host "-----------------------------------------" + Write-Host $sqlfile "execution done" +} + +###################### End of Functions / Start of Script ###################### + +$server = Read-Host -Prompt 'Input the database server name (the full address)' +$dbname = Read-Host -Prompt 'Input the name of the database you want to create' +$u = Read-Host -Prompt 'Input the user name which has the previlige to create the database' +$p0 = Read-Host -Prompt 'Input the password of user name which has the previlige to create the database' -AsSecureString +$p1 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($p0) +$p = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($p1) +$csvfilepath = Read-Host -Prompt 'Input the path to the csv file you want to upload to the database' + +# Check whether BCP is intalled on the computer. If no, install it. +$web_client = new-object System.Net.WebClient +try +{ + $bcp_path = SearchBCP + if ($bcp_path -eq $null){ + Write-Host "bcp.exe is not found in C:\Program Files*. Now, start installing SQL Utilities..." -ForegroundColor "Yellow" + InstallSQLUtilities + $bcp_path = SearchBCP + } + Write-Host "Adding path to bcp.exe to the system path..." -Foregroundcolor "Yellow" + $env_path = $env:Path + for ($i=0; $i -lt $bcp_path.count; $i++){ + if ($bcp_path.count -eq 1){ + $bcp_path_i = $bcp_path + } else { + $bcp_path_i = $bcp_path[$i] + } + if ($env_path -notlike â€*’+$bcp_path_i+'*'){ + Write-Host $bcp_path_i 'not in system path, add it...' + [Environment]::SetEnvironmentVariable("Path", "$bcp_path_i;$env_path", "Machine") + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + $env_path = $env:Path + } + } +} +catch +{ + Write-Host "Installing SQL Utilities failed. " +} + +#Connect to MS SQL Server +try +{ + $SQLConnection = New-Object System.Data.SqlClient.SqlConnection + #The MS SQL Server user and password is specified + if($u -and $p) + { + $SQLConnection.ConnectionString = "Server=" + $server + ";Database=master;User ID= " + $u + ";Password=" + $p + ";" + } + #The MS SQL Server user and password is not specified - using the Windows user credentials + else + { + $SQLConnection.ConnectionString = "Server=" + $server + ";Database=master;Integrated Security=True" + } + $SQLConnection.Open() +} +#Error of connection +catch +{ + Write-Host $Error[0] -ForegroundColor Red + exit 1 +} + +# Create database and tables, and upload data to database from local machine using BCP +Write-Host "Start creating database and table on your SQL Server, and uploading data to the table. It may take a while..." +$start_time = Get-Date +try +{ + ExecuteSQLFile $PWD"\create-db-tb-upload-data.sql" 1 +} +catch +{ + Write-Host "Creating database and tables failed. Probably the database or tables already exist." -ForegroundColor "Red" +} + +$db_tb = $dbname + ".dbo.nyctaxi_sample" +Write-host "start loading the data to SQL Server table..." -Foregroundcolor "Yellow" +try +{ + if($u -and $p) + { + bcp $db_tb in $csvfilepath -t ',' -S $server -f taxiimportfmt.xml -F 2 -C "RAW" -b 200000 -U $u -P $p + } + #The MS SQL Server user and password is not specified - using the Windows user credentials + else + { + bcp $db_tb in $csvfilepath -t ',' -S $server -f taxiimportfmt.xml -F 2 -C "RAW" -b 200000 -T + } +} +catch +{ + Write-Host "BCP uploading data to table failed. Please check whether bcp.exe is installed and the path is added to system path." -ForegroundColor "Red" +} +$end_time = Get-Date +$time_span = $end_time - $start_time +$total_seconds = [math]::Round($time_span.TotalSeconds,2) +Write-Host "This step (creating database and tables, and uploading data to table) takes $total_seconds seconds." -Foregroundcolor "Yellow" + +Write-Host "Start running the .sql files to register all functions and stored procedures used in this walkthrough..." +$start_time = Get-Date + +ExecuteSQLFile $PWD"\fnCalculateDistance.sql" 1 +ExecuteSQLFile $PWD"\fnEngineerFeatures.sql" 1 +ExecuteSQLFile $PWD"\TrainingTestingSplit.sql" 1 +ExecuteSQLFile $PWD"\TrainTipPredictionModelSciKitPy.sql" 1 +ExecuteSQLFile $PWD"\TrainTipPredictionModelRxPy.sql" 1 +ExecuteSQLFile $PWD"\SerializePlots.sql" 1 +ExecuteSQLFile $PWD"\PredictTipSciKitPy.sql" 1 +ExecuteSQLFile $PWD"\PredictTipSingleModeSciKitPy.sql" 1 +ExecuteSQLFile $PWD"\PredictTipRxPy.sql" 1 +ExecuteSQLFile $PWD"\PredictTipSingleModeRxPy.sql" 1 +Write-Host "Completed registering all functions and stored procedures used in this walkthrough." +$end_time = Get-Date +$time_span = $end_time - $start_time +$total_seconds = [math]::Round($time_span.TotalSeconds,2) +Write-Host "This step (registering all functions and stored procedures) takes $total_seconds seconds." +$SQLConnection.Close() diff --git a/Misc/PythonSQL/SerializePlots.sql b/Misc/PythonSQL/SerializePlots.sql new file mode 100644 index 00000000..74267ba0 --- /dev/null +++ b/Misc/PythonSQL/SerializePlots.sql @@ -0,0 +1,63 @@ +USE [TaxiNYC_Sample] +GO + +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +CREATE PROCEDURE [dbo].[SerializePlots] +AS +BEGIN + SET NOCOUNT ON; + DECLARE @query nvarchar(max) = + N'SELECT cast(tipped as int) as tipped, tip_amount, fare_amount FROM [dbo].[nyctaxi_sample]' + EXECUTE sp_execute_external_script + @language = N'Python', + @script = N' +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import pandas as pd +import pickle + +fig_handle = plt.figure() +plt.hist(InputDataSet.tipped) +plt.xlabel("Tipped") +plt.ylabel("Counts") +plt.title("Histogram, Tipped") +plot0 = pd.DataFrame(data =[pickle.dumps(fig_handle)], columns=["plot"]) +plt.clf() + +plt.hist(InputDataSet.tip_amount) +plt.xlabel("Tip amount ($)") +plt.ylabel("Counts") +plt.title("Histogram, Tip amount") +plot1 = pd.DataFrame(data =[pickle.dumps(fig_handle)], columns=["plot"]) +plt.clf() + +plt.hist(InputDataSet.fare_amount) +plt.xlabel("Fare amount ($)") +plt.ylabel("Counts") +plt.title("Histogram, Fare amount") +plot2 = pd.DataFrame(data =[pickle.dumps(fig_handle)], columns=["plot"]) +plt.clf() + +plt.scatter( InputDataSet.fare_amount, InputDataSet.tip_amount) +plt.xlabel("Fare Amount ($)") +plt.ylabel("Tip Amount ($)") +plt.title("Tip amount by Fare amount") +plot3 = pd.DataFrame(data =[pickle.dumps(fig_handle)], columns=["plot"]) +plt.clf() + +OutputDataSet = plot0.append(plot1, ignore_index=True).append(plot2, ignore_index=True).append(plot3, ignore_index=True) +', + @input_data_1 = @query + WITH RESULT SETS ((plot varbinary(max))) +END + + + +GO + diff --git a/Misc/PythonSQL/TrainTipPredictionModelRxPy.sql b/Misc/PythonSQL/TrainTipPredictionModelRxPy.sql new file mode 100644 index 00000000..c3f98342 --- /dev/null +++ b/Misc/PythonSQL/TrainTipPredictionModelRxPy.sql @@ -0,0 +1,42 @@ +/****** Object: StoredProcedure [dbo].[TrainTipPredictionModelSciKitPy] Script Date: 5/17/2017 11:36:11 PM ******/ + +USE [TaxiNYC_Sample] +GO + +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +DROP PROCEDURE IF EXISTS TrainTipPredictionModelRxPy; +GO + +CREATE PROCEDURE [dbo].[TrainTipPredictionModelRxPy] (@trained_model varbinary(max) OUTPUT) +AS +BEGIN + EXEC sp_execute_external_script + @language = N'Python', + @script = N' +import numpy +import pickle +import pandas +from revoscalepy.functions.RxLogit import rx_logit_ex +from revoscalepy.functions.RxPredict import rx_predict_ex + +logitObj = rx_logit_ex("tipped ~ passenger_count + trip_distance + trip_time_in_secs + direct_distance", data=InputDataSet); + +## Serialize model +trained_model = pickle.dumps(logitObj) +', + @input_data_1 = N' + select tipped, fare_amount, passenger_count, trip_time_in_secs, trip_distance, + dbo.fnCalculateDistance(pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude) as direct_distance + from nyctaxi_sample_training + ', + @input_data_1_name = N'InputDataSet', + @params = N'@trained_model varbinary(max) OUTPUT', + @trained_model = @trained_model OUTPUT; + ; +END; +GO diff --git a/Misc/PythonSQL/TrainTipPredictionModelSciKitPy.sql b/Misc/PythonSQL/TrainTipPredictionModelSciKitPy.sql new file mode 100644 index 00000000..a111cb41 --- /dev/null +++ b/Misc/PythonSQL/TrainTipPredictionModelSciKitPy.sql @@ -0,0 +1,51 @@ +/****** Object: StoredProcedure [dbo].[TrainTipPredictionModelSciKitPy] Script Date: 5/17/2017 11:36:11 PM ******/ + +USE [TaxiNYC_Sample] +GO + +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +DROP PROCEDURE IF EXISTS TrainTipPredictionModelSciKitPy; +GO + +CREATE PROCEDURE [dbo].[TrainTipPredictionModelSciKitPy] (@trained_model varbinary(max) OUTPUT) +AS +BEGIN + EXEC sp_execute_external_script + @language = N'Python', + @script = N' +import numpy +import pickle +import pandas +from sklearn.linear_model import LogisticRegression + +## Create model +X = InputDataSet[["passenger_count", "trip_distance", "trip_time_in_secs", "direct_distance"]] +y = numpy.ravel(InputDataSet[["tipped"]]) + +SKLalgo = LogisticRegression() +logitObj = SKLalgo.fit(X, y) + +## Serialize model +trained_model = pickle.dumps(logitObj) +', + @input_data_1 = N' + select tipped, fare_amount, passenger_count, trip_time_in_secs, trip_distance, + dbo.fnCalculateDistance(pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude) as direct_distance + from nyctaxi_sample_training + ', + @input_data_1_name = N'InputDataSet', + @params = N'@trained_model varbinary(max) OUTPUT', + @trained_model = @trained_model OUTPUT; + ; +END; +GO + +DECLARE @model VARBINARY(MAX); +EXEC TrainTipPredictionModelSciKitPy @model OUTPUT; + +INSERT INTO nyc_taxi_models (name, model) VALUES('linear_model', @model); diff --git a/Misc/PythonSQL/TrainingTestingSplit.sql b/Misc/PythonSQL/TrainingTestingSplit.sql new file mode 100644 index 00000000..c831c349 --- /dev/null +++ b/Misc/PythonSQL/TrainingTestingSplit.sql @@ -0,0 +1,31 @@ +USE [TaxiNYC_Sample] +GO + +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +--Split whole data into training and testing, specify an integer as the parameter of the stored procedure + +DROP PROCEDURE IF EXISTS TrainTestSplit; +GO + +CREATE PROCEDURE [dbo].[TrainTestSplit] (@pct int) +AS + +DROP TABLE IF EXISTS dbo.nyctaxi_sample_training +SELECT * into nyctaxi_sample_training +FROM nyctaxi_sample +WHERE (ABS(CAST(BINARY_CHECKSUM(medallion,hack_license) as int)) % 100) <= @pct + +DROP TABLE IF EXISTS dbo.nyctaxi_sample_testing +SELECT * into nyctaxi_sample_testing +FROM nyctaxi_sample +WHERE (ABS(CAST(BINARY_CHECKSUM(medallion,hack_license) as int)) % 100) > @pct + +GO + +EXEC TrainTestSplit 60 +GO diff --git a/Misc/PythonSQL/create-db-tb-upload-data.sql b/Misc/PythonSQL/create-db-tb-upload-data.sql new file mode 100644 index 00000000..1fc65ba1 --- /dev/null +++ b/Misc/PythonSQL/create-db-tb-upload-data.sql @@ -0,0 +1,59 @@ +DECLARE @db_name varchar(255), @tb_name varchar(255) +DECLARE @create_db_template varchar(max), @create_tb_template varchar(max), @create_tb_template2 varchar(max) +DECLARE @sql_script varchar(max) +SET @db_name = 'TaxiNYC_Sample' +SET @tb_name = 'nyctaxi_sample' +SET @create_db_template = 'create database {db_name}' +SET @create_tb_template = ' +use {db_name} +CREATE TABLE {tb_name} +( + medallion varchar(50) not null, + hack_license varchar(50) not null, + vendor_id char(3), + rate_code char(3), + store_and_fwd_flag char(3), + pickup_datetime datetime not null, + dropoff_datetime datetime, + passenger_count int, + trip_time_in_secs bigint, + trip_distance float, + pickup_longitude varchar(30), + pickup_latitude varchar(30), + dropoff_longitude varchar(30), + dropoff_latitude varchar(30), + payment_type char(3), + fare_amount float, + surcharge float, + mta_tax float, + tolls_amount float, + total_amount float, + tip_amount float, + tipped int, + tip_class int +) +CREATE CLUSTERED COLUMNSTORE INDEX [nyc_cci] ON {tb_name} WITH (DROP_EXISTING = OFF) +' + +SET @create_tb_template2 = ' +use {db_name} +CREATE TABLE nyc_taxi_models +( + name varchar(250), + model varbinary(max) not null +) +' + +-- Create database +SET @sql_script = REPLACE(@create_db_template, '{db_name}', @db_name) +EXECUTE(@sql_script) + +-- Create table +SET @sql_script = REPLACE(@create_tb_template, '{db_name}', @db_name) +SET @sql_script = REPLACE(@sql_script, '{tb_name}', @tb_name) +EXECUTE(@sql_script) + +-- Create the table to persist the trained model +SET @sql_script = REPLACE(@create_tb_template2, '{db_name}', @db_name) +EXECUTE(@sql_script) +GO diff --git a/Misc/PythonSQL/fnCalculateDistance.sql b/Misc/PythonSQL/fnCalculateDistance.sql new file mode 100644 index 00000000..d3b47b98 --- /dev/null +++ b/Misc/PythonSQL/fnCalculateDistance.sql @@ -0,0 +1,35 @@ +USE [TaxiNYC_Sample] +GO + +/****** Object: UserDefinedFunction [dbo].[fnCalculateDistance] Script Date: 10/29/2015 4:33:52 PM ******/ +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +IF EXISTS (SELECT * FROM sys.objects WHERE type IN ('FN', 'IF') AND name = 'fnCalculateDistance') + DROP FUNCTION fnCalculateDistance +GO + +CREATE FUNCTION [dbo].[fnCalculateDistance] (@Lat1 float, @Long1 float, @Lat2 float, @Long2 float) +-- User-defined function calculate the direct distance between two geographical coordinates. +RETURNS float +AS +BEGIN + DECLARE @distance decimal(28, 10) + -- Convert to radians + SET @Lat1 = @Lat1 / 57.2958 + SET @Long1 = @Long1 / 57.2958 + SET @Lat2 = @Lat2 / 57.2958 + SET @Long2 = @Long2 / 57.2958 + -- Calculate distance + SET @distance = (SIN(@Lat1) * SIN(@Lat2)) + (COS(@Lat1) * COS(@Lat2) * COS(@Long2 - @Long1)) + --Convert to miles + IF @distance <> 0 + BEGIN + SET @distance = 3958.75 * ATAN(SQRT(1 - POWER(@distance, 2)) / @distance); + END + RETURN @distance +END +GO diff --git a/Misc/PythonSQL/fnEngineerFeatures.sql b/Misc/PythonSQL/fnEngineerFeatures.sql new file mode 100644 index 00000000..344ea5ad --- /dev/null +++ b/Misc/PythonSQL/fnEngineerFeatures.sql @@ -0,0 +1,35 @@ +USE [TaxiNYC_Sample] +GO + +/****** Object: UserDefinedFunction [dbo].[fnEngineerFeatures] Script Date: 10/29/2015 4:36:07 PM ******/ +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +IF EXISTS (SELECT * FROM sys.objects WHERE type IN ('FN', 'IF') AND name = 'fnEngineerFeatures') + DROP FUNCTION fnEngineerFeatures +GO + +CREATE FUNCTION [dbo].[fnEngineerFeatures] ( +@passenger_count int = 0, +@trip_distance float = 0, +@trip_time_in_secs int = 0, +@pickup_latitude float = 0, +@pickup_longitude float = 0, +@dropoff_latitude float = 0, +@dropoff_longitude float = 0) +RETURNS TABLE +AS + RETURN + ( + -- Add the SELECT statement with parameter references here + SELECT + @passenger_count AS passenger_count, + @trip_distance AS trip_distance, + @trip_time_in_secs AS trip_time_in_secs, + [dbo].[fnCalculateDistance](@pickup_latitude, @pickup_longitude, @dropoff_latitude, @dropoff_longitude) AS direct_distance + + ) +GO \ No newline at end of file diff --git a/Misc/PythonSQL/taxiimportfmt.xml b/Misc/PythonSQL/taxiimportfmt.xml new file mode 100644 index 00000000..9e761c7a --- /dev/null +++ b/Misc/PythonSQL/taxiimportfmt.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Misc/PythonSQL/varbinplot.fmt b/Misc/PythonSQL/varbinplot.fmt new file mode 100644 index 00000000..f59aa8dc --- /dev/null +++ b/Misc/PythonSQL/varbinplot.fmt @@ -0,0 +1,3 @@ +13.0 +1 +1 SQLBINARY 0 0 "" 1 plot "" diff --git a/Misc/README.md b/Misc/README.md new file mode 100644 index 00000000..362bf87c --- /dev/null +++ b/Misc/README.md @@ -0,0 +1,5 @@ + # Azure-MachineLearning-DataScience/Misc + +This folder contains several published walkthroughs, templates, tutorials published on Machine Learning and Data Science, using services and data platfoms on Microsoft Azure. Walkthroughs are available using HDInsight Spark, Azure Data Lake, Azure SQL Data Warehouse, SQL-server with R and Python services, etc. The name of the sub-folders here indicate what platform is being used for the walkthroughs. + +In addition, there are materials and docs from tutorials we've presented in KDD and Strata. diff --git a/Misc/RSQL/RSQL_R_Walkthrough.R b/Misc/RSQL/RSQL_R_Walkthrough.R index cc26541d..4efcfe50 100644 --- a/Misc/RSQL/RSQL_R_Walkthrough.R +++ b/Misc/RSQL/RSQL_R_Walkthrough.R @@ -1,4 +1,4 @@ -# PreRequisites: You have installed Revolution R Enterprise 7.5.0 or higher on the machine and SQL Server 2016 CTP3 or higher on the database server +# Prerequisites: You have installed SQL Server 2016 R Services or SQL Server 2017 Machine Learning Services with the R language # Install required R libraries for this walkthrough if they are not installed. if (!('ggmap' %in% rownames(installed.packages()))){ diff --git a/Misc/Spark/Python/Spark2.0_ConsumeRFCV_NYCReg.py b/Misc/Spark/Python/Spark2.0_ConsumeRFCV_NYCReg.py new file mode 100644 index 00000000..2dda7bef --- /dev/null +++ b/Misc/Spark/Python/Spark2.0_ConsumeRFCV_NYCReg.py @@ -0,0 +1,89 @@ +################################################################################################################### +################################################################################################################### +## THIS FILE PROVIDES THE PYTHON SCRIPTS THAT CAN BE USED TO OPERATIONALIZE SCORING +## WITH SAVED MODELS (TRAINED IN PYSPARK3) IN SPARK 2.0.2 HDINSIGHT CLUSTERS +## YOU CAN RUN THIS USING THE FOLLOWING COMMAND: +# curl -k --user ":" -X POST --data "{\"file\": \"wasb:///example/Spark2.0_ConsumeRFCV_NYCReg.py\"}" -H "Content-Type: application/json" "https://.azurehdinsight.net/livy/batches" +################################################################################################################### +################################################################################################################### + + +################################################################################################################### +## IMPORT LIBRARIES +################################################################################################################### +## IF FOLLOWING LIBRARIES ARE NOT INSTALLED, INSTALL THEM FIRST +#sudo python -m pip install --upgrade pip +#sudo pip install --user numpy scipy matplotlib ipython jupyter pandas sympy nose +## For installing pip on python, if not installed, see: http://stackoverflow.com/questions/6587507/how-to-install-pip-with-python-3 +## Python3: sudo apt-get install python3-pip, on Ubuntu +## python3 -m pip install +################################################################################################################### +import pyspark +from pyspark.sql.types import * +from pyspark import SparkConf +from pyspark import SparkContext +from pyspark.sql import SQLContext +from pyspark.sql.functions import UserDefinedFunction +from pyspark.ml import PipelineModel +from pyspark.ml.evaluation import RegressionEvaluator +from pyspark.ml import Pipeline +from pyspark.ml.feature import OneHotEncoder, StringIndexer, VectorIndexer +import atexit + +################################################################################################################### +## CREATE SPARK CONTEXT +################################################################################################################### +sc = SparkContext(appName="PythonRFNYCPred") +sqlContext = SQLContext(sc) +atexit.register(lambda: sc.stop()) +sc.defaultParallelism + +################################################################################################################### +## READ IN DATA TO BE SCORED INTO A DATAFRAME FROM CSV +################################################################################################################### +taxi_valid_file_loc = "wasb://mllibwalkthroughs@cdspsparksamples.blob.core.windows.net/Data/NYCTaxi/JoinedTaxiTripFare.Point1Pct.Valid.csv" +taxi_valid_df = sqlContext.read.format("com.databricks.spark.csv").option("header", "true").option("inferschema", "true").option("mode", "DROPMALFORMED").load(taxi_valid_file_loc) + +## CREATE A CLEANED DATA-FRAME BY DROPPING SOME UN-NECESSARY COLUMNS & FILTERING FOR UNDESIRED VALUES OR OUTLIERS +taxi_df_valid_cleaned = taxi_valid_df.drop('medallion').drop('hack_license').drop('store_and_fwd_flag').drop('pickup_datetime')\ + .drop('dropoff_datetime').drop('pickup_longitude').drop('pickup_latitude').drop('dropoff_latitude')\ + .drop('dropoff_longitude').drop('tip_class').drop('total_amount').drop('tolls_amount').drop('mta_tax')\ + .drop('direct_distance').drop('surcharge')\ + .filter("passenger_count > 0 and passenger_count < 8 AND payment_type in ('CSH', 'CRD') \ + AND tip_amount >= 0 AND tip_amount < 30 AND fare_amount >= 1 AND fare_amount < 150 AND trip_distance > 0 \ + AND trip_distance < 100 AND trip_time_in_secs > 30 AND trip_time_in_secs < 7200" ) + +## REGISTER DATA-FRAME AS A TEMP-TABLE IN SQL-CONTEXT +taxi_df_valid_cleaned.createOrReplaceTempView("taxi_valid") + +### CREATE FOUR BUCKETS FOR TRAFFIC TIMES +sqlStatement = """ SELECT *, CASE + WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN "Night" + WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN "AMRush" + WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN "Afternoon" + WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN "PMRush" + END as TrafficTimeBins + FROM taxi_valid +""" +taxi_df_valid_with_newFeatures = sqlContext.sql(sqlStatement) + +## APPLY THE SAME TRANSFORATION ON THIS DATA AS ORIGINAL TRAINING DATA +# DEFINE THE TRANSFORMATIONS THAT NEEDS TO BE APPLIED TO SOME OF THE FEATURES +sI1 = StringIndexer(inputCol="vendor_id", outputCol="vendorIndex"); +sI2 = StringIndexer(inputCol="rate_code", outputCol="rateIndex"); +sI3 = StringIndexer(inputCol="payment_type", outputCol="paymentIndex"); +sI4 = StringIndexer(inputCol="TrafficTimeBins", outputCol="TrafficTimeBinsIndex"); + +# APPLY TRANSFORMATIONS +encodedFinalValid = Pipeline(stages=[sI1, sI2, sI3, sI4]).fit(taxi_df_valid_with_newFeatures).transform(taxi_df_valid_with_newFeatures) + +################################################################################################################### +## LOAD SAVED MODEL, SCORE VALIDATION DATA, AND SAVE PREDICTIONS +################################################################################################################### +CVDirfilename = "wasb:///user/remoteuser/NYCTaxi/Models/CV_RandomForestRegressionModel_02-13-2017-1486959163" +savedModel = PipelineModel.load(CVDirfilename) +predictions = savedModel.transform(encodedFinalValid) +outDF = predictions.select("label","prediction") +outFile = "wasb:///user/remoteuser/NYCTaxi/Outputs/CVRandomForest_RegValidationDataPredictions.csv" +outDF.write.format("com.databricks.spark.csv").options(header=True).mode('overwrite').save(outFile) +################################################################################################################### \ No newline at end of file diff --git a/Misc/Spark/Scala/Readme b/Misc/Spark/Scala/Readme index d6d36d3a..5b24775b 100644 --- a/Misc/Spark/Scala/Readme +++ b/Misc/Spark/Scala/Readme @@ -11,3 +11,10 @@ It shows how to use Scala for: 6. Loading saved models and scoring data-sets with these models. We do not show automated / scheduled model consumption. Users can easily get that from the pySpark walkthrough and associated notebooks. + +For further details check: https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-process-scala-walkthrough + + +We now have Spark 1.6 as well as Spark 2.0 versions of notebooks: +1. Spark 1.6: Exploration-Modeling-and-Scoring-using-Scala.ipynb +2. Spark 2.0: Spark2.0-Exploration-Modeling-and-Scoring-using-Scala.ipynb diff --git a/Misc/Spark/Scala/Spark2.0-Exploration-Modeling-and-Scoring-using-Scala.ipynb b/Misc/Spark/Scala/Spark2.0-Exploration-Modeling-and-Scoring-using-Scala.ipynb new file mode 100644 index 00000000..b168504f --- /dev/null +++ b/Misc/Spark/Scala/Spark2.0-Exploration-Modeling-and-Scoring-using-Scala.ipynb @@ -0,0 +1,821 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exploration and Modeling of 2013 NYC Taxi Trip and Fare Dataset on Spark 2.0 HDInsight Clusters (Scala)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Last updated:\n", + "Jan 08, 2016" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---------------------------------\n", + "### Here we show some features and capabilities of Spark's MLlib toolkit using the NYC taxi trip and fare data-set from 2013 (about 40 Gb uncompressed). We take a 0.1% sample of this data-set (about 170K rows, 35 Mb) to to show MLlib's modeling features for binary classification and regression problems using this data-set. We have shown relevant plots in Python.\n", + "\n", + "### We have sampled the data in order for the runs to finish quickly. The same code will on the full data-set.\n", + "\n", + "----------------------------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### OBJECTIVE: Show use of Spark MLlib's functions for featurization and ML tasks.\n", + "\n", + "### We address two learning problems:\n", + "#### 1. Binary classification: Prediction of tip or no-tip (1/0) for a taxi trip [Using regularized regression]\n", + "#### 2. Regression problem: Prediction of the tip amonut ($) [Using random forest]\n", + "\n", + "#### We have shown the following steps:\n", + "1. Data ingestion & cleanup\n", + "2. Data exploration and plotting\n", + "3. Data preparation (featurizing/transformation), \n", + "4. Modeling (using incl. hyperparameter tuning with cross-validation), prediction, model persistance\n", + "5. Model evaluation on an independent validation data-set." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Introductory material\n", + "\n", + "NYC 2013 Taxi data:\n", + "http://www.andresmh.com/nyctaxitrips/\n", + "https://azure.microsoft.com/en-us/documentation/articles/machine-learning-data-science-process-hive-walkthrough/\n", + "\n", + "An earlier version of this walkthrough was published in a set of notebooks to run on Spark 1.6 HDInsight clusters: https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-overview\n", + "\n", + "----------------------------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set directory paths and location of training, validation files, as well as model location in blob storage\n", + "NOTE: The blob storage attached to the HDI cluster is referenced as: wasb:/// (Windows Azure Storage Blob). Other blob storage accounts are referenced as: wasb://" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "modelDir: String = wasb:///user/remoteuser/NYCTaxi/Models/" + ] + } + ], + "source": [ + "// Location of training data\n", + "val taxi_train_file = \"wasb://mllibwalkthroughs@cdspsparksamples.blob.core.windows.net/Data/NYCTaxi/JoinedTaxiTripFare.Point1Pct.Train.csv\";\n", + "val taxi_valid_file = \"wasb://mllibwalkthroughs@cdspsparksamples.blob.core.windows.net/Data/NYCTaxi/JoinedTaxiTripFare.Point1Pct.Valid.csv\"\n", + "\n", + "// Set model storage directory path. This is where models will be saved.\n", + "val modelDir = \"wasb:///user/remoteuser/NYCTaxi/Models/\"; //The last backslash is needed;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set SQL context and import necessary libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "warning: there was one deprecation warning; re-run with -deprecation for details\n", + "sqlContext: org.apache.spark.sql.SQLContext = org.apache.spark.sql.SQLContext@f8f4e7" + ] + } + ], + "source": [ + "import org.apache.spark.sql.SQLContext\n", + "import org.apache.spark.sql.functions._\n", + "import java.text.SimpleDateFormat\n", + "import java.util.Calendar\n", + "\n", + "// Spark SQL functions\n", + "import org.apache.spark.sql.types.{StringType, IntegerType, FloatType, DoubleType}\n", + "\n", + "// Spark ML and MLlib functions\n", + "import org.apache.spark.ml.{Pipeline, PipelineModel}\n", + "import org.apache.spark.ml.feature.{StringIndexer, VectorAssembler, OneHotEncoder, VectorIndexer, Binarizer, RFormula}\n", + "import org.apache.spark.ml.tuning.{ParamGridBuilder, CrossValidator, CrossValidatorModel}\n", + "import org.apache.spark.ml.regression.{LinearRegression, LinearRegressionModel, RandomForestRegressor, RandomForestRegressionModel, GBTRegressor, GBTRegressionModel}\n", + "import org.apache.spark.ml.classification.{LogisticRegression, LogisticRegressionModel, RandomForestClassifier, RandomForestClassificationModel}\n", + "import org.apache.spark.ml.evaluation.{BinaryClassificationEvaluator, RegressionEvaluator, MulticlassClassificationEvaluator}\n", + "import org.apache.spark.mllib.evaluation.{BinaryClassificationMetrics, MulticlassMetrics, RegressionMetrics}\n", + "\n", + "val sqlContext = new SQLContext(sc)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Data ingestion & cleanup: Read in joined 0.1% taxi trip and fare file (as csv), format and clean data, and create data-frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The taxi trip and fare files were joined based on the instructions provided in: \n", + "\"https://azure.microsoft.com/en-us/documentation/articles/machine-learning-data-science-process-hive-walkthrough/\"\n", + "\n", + "A 0.1% sample of the joined data-set was taken to show the ML examples in this walkthrough." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "// Read IN TAXI TRAIN DATA\n", + "val taxi_train_df = sqlContext.read.format(\"com.databricks.spark.csv\").option(\"header\", \"True\").option(\"inferSchema\", \"True\").load(taxi_train_file)\n", + "\n", + "// CLEAN DATA BY DROPPING OR FILTERING SOME FIELDS\n", + "val taxi_df_train_cleaned = (taxi_train_df.drop(taxi_train_df.col(\"medallion\"))\n", + " .drop(taxi_train_df.col(\"hack_license\")).drop(taxi_train_df.col(\"store_and_fwd_flag\"))\n", + " .drop(taxi_train_df.col(\"pickup_datetime\")).drop(taxi_train_df.col(\"dropoff_datetime\"))\n", + " .drop(taxi_train_df.col(\"pickup_longitude\")).drop(taxi_train_df.col(\"pickup_latitude\"))\n", + " .drop(taxi_train_df.col(\"dropoff_longitude\")).drop(taxi_train_df.col(\"dropoff_latitude\"))\n", + " .drop(taxi_train_df.col(\"surcharge\")).drop(taxi_train_df.col(\"mta_tax\"))\n", + " .drop(taxi_train_df.col(\"direct_distance\")).drop(taxi_train_df.col(\"tolls_amount\"))\n", + " .drop(taxi_train_df.col(\"total_amount\")).drop(taxi_train_df.col(\"tip_class\"))\n", + " .filter(\"passenger_count > 0 and passenger_count < 8 AND payment_type in ('CSH', 'CRD') AND tip_amount >= 0 AND tip_amount < 30 AND fare_amount >= 1 AND fare_amount < 150 AND trip_distance > 0 AND trip_distance < 100 AND trip_time_in_secs > 30 AND trip_time_in_secs < 7200\"));\n", + "\n", + "// REGISTER TABLE\n", + "taxi_df_train_cleaned.createOrReplaceTempView(\"taxi_train\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Data exploration & visualization: Plotting of target variables and features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### First, summarize data using SQL, this outputs a Spark data frame. If the data-set is too large, it can be sampled" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%sql -q -o sqlResultsPD\n", + "SELECT fare_amount, passenger_count, tip_amount, tipped FROM taxi_train WHERE passenger_count > 0 AND passenger_count < 7 AND fare_amount > 0 AND fare_amount < 200 AND payment_type in ('CSH', 'CRD') AND tip_amount > 0 AND tip_amount < 25" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Plot histogram of tip amount, relationship between tip amount vs. other features" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n83z27NnU1dXxxBNPcPjhh7Pxxhszfvx4jjzySF577bVOr1uxYgVXXHEFdXV11NXVFWoMkD0rkqQh\n4YADDuDPf/4z11xzDRdccAFvfetbiQg23XTTLserzJo1i4033pjTTz+dRx99lEsuuYTFixdzxx13\n9Pi4Dz74IPfddx8NDQ1sscUWPPnkk1xyySXstttuPPLII2y44Yad6p9wwglsttlmnHHGGdx33318\n97vfZfz48dxzzz1sueWWnHXWWdx88818/etf513vehczZ85c89r99tuPO++8k6OOOortt9+eefPm\ncdJJJ/H000+vFVp6ouO8HHjggUybNo2zzz6b5uZmvve97zFp0iTOOussAH70ox/xH//xH+y0004c\nffTRAPzjP/5jr4/XXwwrkqQh4Z3vfCczZszgmmuuYf/99+/RJaQNN9yQ+fPnM2rUKACmTJnCySef\nzE033cS+++7bo+Puu+++HHDAAZ3K9ttvP973vvdx3XXXccghh3Tattlmm/Hzn/8cgGOPPZbHHnuM\nc889l+OOO46LLroIgE996lNMnTqV73//+2vCyg033MAdd9zBV7/6Vb7whS8AcNxxx3HggQdywQUX\nMGvWLLbaaqsetblcfX093/nOd9Y8b21t5bLLLlsTVg4++GCOOeYYpk2bxsEHH1zVMfqTl4EkScPW\n0UcfvSaoQPbLf9SoUdx888093sfo0aPX/HvVqlW8+OKLTJs2jfHjx9Pc3NypbkSsdflkp512AuhU\nXldXxw477MDChQvXlN1yyy2st956nHDCCZ1e/7nPfY729nZuueWWHre5vE3HHHNMp7Kdd96ZF154\ngb/97W9V7XOgGVYkScNSRLD11p3Hx2y00UZsttlmPPnkkz3ez2uvvcapp57KlClTGD16NBMmTGDi\nxIm0tbXR1ta2Vv3yHp9x48YB8La3vW2t8mXLlq15/tRTT7H55puz0UYbdarXcefs8jEyvVHepo03\n3hig0/GLzMtAkiR1Y9asWVx55ZU0Njbyvve9j3HjxhERfPKTn6S9vX2t+qU9Oesq72p2U3e6Gp9T\nqS3ralM1xx8MhhVJ0pDRm4XfUko89thj7LrrrmvKli9fzjPPPMM+++zT4/1cd911HH744Zxzzjlr\nylauXNnrWUXrsuWWWzJ//nyWL1/eqXelpaVlzXb4e69I+fH70vMCvTu3A83LQJKkIaPjl3hPg8J3\nvvMdVq1ateb5JZdcwurVq9l77717fMxRo0at1Wtx4YUXsnr16h7voyf23ntvVq1atWYQboc5c+ZQ\nV1fHXnvtBcBb3vIWJkyYwF133dWp3sUXX9ynwLHRRhvVPIDVij0rkjTCLFn4+JA9fn19PSkl/vu/\n/5uDDjqI9ddfn/3226/L+q+//jq77747Bx54IH/605/41re+xc4779zjmUCQzQb64Q9/yNixY9lu\nu+249957mT9/PhMmTFirbl8uq+y3337stttufOlLX2LRokVrpi7feOONNDY2dpoJdNRRR3H22Wfz\nqU99ih122IG77rqLxx57rE/Hr6+v5/bbb2fOnDlsvvnmbLXVVuy4445V76+WDCuSNEJMmDCBMWPG\ncMFJswa7KWsWV+utHXbYga985St8+9vfZt68ebS3t7No0SIiYq1ehYjgoosu4qqrruK0007jjTfe\n4JBDDuGCCy7o1TEvvPBC1ltvPa6++mpee+01PvjBD3L77bez5557Vjxmb5TWjwhuvPFGTj31VH78\n4x9zxRVXMHXqVL7+9a/T2NjY6XWnnnoqra2tzJ07l5/85Cfsvffe3HLLLUycOLHq3pXzzjuPY445\nhi9/+cu8+uqrHHbYYYUJKzFUBtcMhoiYATz00EMPMWPGjKr309zcTH19PededyvT3vHuqvez8I+/\n46QDPkJf2yNpeOv4mVPpZ8VQv5FhT1155ZUceeSRPPjgg/687Cfdfc5KtwP1KaXmtSr0gj0rkjSC\nTJkypV9DgtQfDCuSpGFpXVcOli9fvs5F0TbddFPv5lwAhhVJ0rC0rrEbX//61zn99NO7ff2iRYvs\niSoAw4okadg57LDDOOyww9ZZZ+edd+62zuTJk2vZLFXJsCJJGpGmTp3K1KlTB7sZ6gEvxEmSpEIz\nrEiSpEIzrEiSpEJzzIokDVMdN8CT+sNAfr4MK5I0zHQsqz9z5szBboqGuWpvm9BbhhVJGmamTJlC\nS0tLIZbV1/DW37dN6GBYkaRhyGX1NZw4wFaSJBWaYUWSJBWaYUWSJBWaYUWSJBVaIcJKROwcET+L\niL9GRHtEfLRCnTMi4umIWBERv4iIrcu2j46IiyOiNSJeiYi5ETGxrM7GEXFVRLRFxLKI+F5EbNTf\n70+SJFWvEGEF2Aj4DfBpIJVvjIiTgVnA0cCOwHJgXkRsUFLtfGAf4ABgF2Bz4LqyXV0NTAd2z+vu\nAlxayzciSZJqqxBTl1NKtwK3AkREVKhyInBmSummvM6hwFLgY8C1ETEWOBI4KKV0Z17nCKAlInZM\nKT0QEdOBPYH6lNLDeZ0TgJ9HxH+llJ7t33cpSZKqUZSelS5FxFbAZGB+R1lK6WXgfuD9edEOZMGr\ntM6jwOKSOu8DlnUEldztZD05O/VX+yVJUt8UPqyQBZVE1pNSamm+DWAS8HoeYrqqMxl4rnRjSmk1\n8GJJHUmSVDCFuAxUdI2NjYwbN65TWUNDAw0NDYPUIkmSiqOpqYmmpqZOZW1tbTXb/1AIK88CQdZ7\nUtq7Mgl4uKTOBhExtqx3ZVK+raNO+eygUcAmJXUqmjNnDjNmzKj6DUiSNJxV+gO+ubmZ+vr6muy/\n8JeBUkqLyMLE7h1l+YDanYB78qKHgFVldbYBpgD35kX3AuMj4r0lu9+dLAjd31/tlyRJfVOInpV8\nrZOtyYIDwLSI2B54MaX0F7JpyadExOPAk8CZwBLgBsgG3EbEZcB5EbEMeAW4EFiQUnogr/OniJgH\nfDcijgM2AL4JNDkTSJKk4ipEWCGbzXMH2UDaBHwjL78SODKldE5EjCFbE2U8cDewV0rp9ZJ9NAKr\ngbnAaLKp0MeXHedg4CKyWUDted0T++MNSZKk2ihEWMnXRun2klRKaTYwu5vtK4ET8kdXdV4CZlbV\nSEmSNCgKP2ZFkiSNbIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYV\nSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJU\naIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYV\nSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJU\naIYVSZJUaIYVSZJUaIYVSZJUaEMirEREXUScGRELI2JFRDweEadUqHdGRDyd1/lFRGxdtn10RFwc\nEa0R8UpEzI2IiQP3TiRJUm8NibACfAE4Bvg0sC3weeDzETGro0JEnAzMAo4GdgSWA/MiYoOS/ZwP\n7AMcAOwCbA5cNxBvQJIkVWe9wW5AD70fuCGldGv+fHFEHEwWSjqcCJyZUroJICIOBZYCHwOujYix\n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\nAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\nAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "# TIP BY PAYMENT TYPE AND PASSENGER COUNT\n", + "ax1 = sqlResultsPD[['tip_amount']].plot(kind='hist', bins=25, facecolor='lightblue')\n", + "ax1.set_title('Tip amount distribution')\n", + "ax1.set_xlabel('Tip Amount ($)'); ax1.set_ylabel('Counts');\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()\n", + "\n", + "# TIP BY PASSENGER COUNT\n", + "ax2 = sqlResultsPD.boxplot(column=['tip_amount'], by=['passenger_count'])\n", + "ax2.set_title('Tip amount by Passenger count')\n", + "ax2.set_xlabel('Passenger count'); ax2.set_ylabel('Tip Amount ($)');\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()\n", + "\n", + "# TIP AMOUNT BY FARE AMOUNT, POINTS ARE SCALED BY PASSENGER COUNT\n", + "ax = sqlResultsPD.plot(kind='scatter', x= 'fare_amount', y = 'tip_amount', c='blue', alpha = 0.10, s=2.5*(sqlResultsPD.passenger_count))\n", + "ax.set_title('Tip amount by Fare amount')\n", + "ax.set_xlabel('Fare Amount ($)'); ax.set_ylabel('Tip Amount ($)');\n", + "plt.axis([-2, 80, -2, 20])\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Feature engineering, transformation and data preparation for modeling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create a new feature by binning hours into traffic time buckets using Spark SQL" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "res21: Long = 126050" + ] + } + ], + "source": [ + "/* CREATE FOUR BUCKETS FOR TRAFFIC TIMES */\n", + "val sqlStatement = \"\"\"\n", + " SELECT *,\n", + " CASE\n", + " WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN \"Night\" \n", + " WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN \"AMRush\" \n", + " WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN \"Afternoon\"\n", + " WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN \"PMRush\"\n", + " END as TrafficTimeBins\n", + " FROM taxi_train \n", + "\"\"\"\n", + "val taxi_df_train_with_newFeatures = sqlContext.sql(sqlStatement)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Indexing and one-hot encoding of categorical features\n", + "Here we only transform some variables to show examples, which are character strings. Other variables, such as week-day, which are represented by numerical valies, can also be indexed as categorical variables.\n", + "\n", + "For indexing, we used stringIndexer, and for one-hot encoding, we used OneHotEncoder functions from MLlib." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "encodedFinal: org.apache.spark.sql.DataFrame = [vendor_id: string, rate_code: int ... 19 more fields]" + ] + } + ], + "source": [ + "// Create a pipeline of transformations\n", + "val sI1 = new StringIndexer().setInputCol(\"vendor_id\").setOutputCol(\"vendorIndex\");\n", + "val en1 = new OneHotEncoder().setInputCol(\"rate_code\").setOutputCol(\"vendorVec\");\n", + "val sI2 = new StringIndexer().setInputCol(\"vendor_id\").setOutputCol(\"rateIndex\");\n", + "val en2 = new OneHotEncoder().setInputCol(\"rateIndex\").setOutputCol(\"rateVec\");\n", + "val sI3 = new StringIndexer().setInputCol(\"payment_type\").setOutputCol(\"paymentIndex\");\n", + "val en3 = new OneHotEncoder().setInputCol(\"paymentIndex\").setOutputCol(\"paymentVec\");\n", + "val sI4 = new StringIndexer().setInputCol(\"TrafficTimeBins\").setOutputCol(\"TrafficTimeBinsIndex\");\n", + "val en4 = new OneHotEncoder().setInputCol(\"TrafficTimeBinsIndex\").setOutputCol(\"TrafficTimeBinsVec\");\n", + "\n", + "//encodedFinal = Pipeline(stages=[sI1, en1, sI2, en2, sI3, en3, sI4, en4]).fit(taxi_df_train_with_newFeatures).transform(taxi_df_train_with_newFeatures);\n", + "val featTransformPipeline = new Pipeline().setStages(Array(sI1, en1, sI2, en2, sI3, en3, sI4, en4))\n", + "val encodedFinal = featTransformPipeline.fit(taxi_df_train_with_newFeatures).transform(taxi_df_train_with_newFeatures);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create a random sampling of the data, as needed (50% is used here). This can save some time while training models. Then, split into train/test." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "testData: org.apache.spark.sql.Dataset[org.apache.spark.sql.Row] = [vendor_id: string, rate_code: int ... 19 more fields]" + ] + } + ], + "source": [ + "val trainingFraction = 0.5; \n", + "val testingFraction = (1-trainingFraction);\n", + "val seed = 1234;\n", + "val encodedFinalSampled = encodedFinal.sample(withReplacement = false, fraction = 0.5, seed = seed)\n", + "\n", + "\n", + "// SPLIT SAMPLED DATA-FRAME INTO TRAIN/TEST, WITH A RANDOM COLUMN ADDED FOR DOING CV (SHOWN LATER)\n", + "// INCLUDE RAND COLUMN FOR CREATING CROSS-VALIDATION FOLDS\n", + "val splits = encodedFinalSampled.randomSplit(Array(trainingFraction, testingFraction), seed = seed)\n", + "val trainData = splits(0)\n", + "val testData = splits(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Binary classification model training: Predicting tip or no tip (target: tipped = 1/0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create logistic regression model, save modle, and evaluate performance of model on test data set" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create logistic regression model using RFormula and LogisticRegression functions" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ROC on test data = 0.9841004305669758" + ] + } + ], + "source": [ + "//## TRAIN USING PIPELINE FORMULA + LOGISTIC REGRESSION ESTIMATOR\n", + "val logReg = new LogisticRegression().setMaxIter(10).setRegParam(0.3).setElasticNetParam(0.8)\n", + "\n", + "//## DEFINE TRAINING FORMULA\n", + "val classFormula = new RFormula().setFormula(\"tipped ~ pickup_hour + weekday + passenger_count + trip_time_in_secs + trip_distance + fare_amount + vendorVec + paymentVec + rateVec + TrafficTimeBinsVec\").setFeaturesCol(\"features\").setLabelCol(\"label\")\n", + "\n", + "//## TRAIN PIPELINE MODEL\n", + "val pipeline = new Pipeline().setStages(Array(classFormula, logReg));\n", + "val model = pipeline.fit(trainData)\n", + "\n", + "// SAVE MODEL\n", + "val datestamp = Calendar.getInstance().getTime().toString.replaceAll(\" \", \".\").replaceAll(\":\", \"_\");\n", + "val logRegDirfilename = modelDir.concat(\"logisticRegModel_\").concat(datestamp)\n", + "model.save(logRegDirfilename);\n", + "\n", + "// SCORE AND EVALUATE MODEL ON TEST DATA\n", + "val predictions = model.transform(testData)\n", + "predictions.registerTempTable(\"testResults\")\n", + "predictions.select(\"label\",\"probability\").createOrReplaceTempView(\"testResults\");\n", + "\n", + "val evaluator = new BinaryClassificationEvaluator().setLabelCol(\"tipped\").setRawPredictionCol(\"probability\").setMetricName(\"areaUnderROC\")\n", + "val ROC = evaluator.evaluate(predictions)\n", + "\n", + "println(\"ROC on test data = \" + ROC)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Plot ROC curve from prediction result on test data" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%sql -q -o predictions_pddf\n", + "select label, probability from testResults" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n3niDzp07B12SiEjoha6Z1uVkumXLFvr160dBQQE5OTmkp6cHXZKIiKBzpnXGpk2b6NmzJ2bG6tWr\n1UhFRJJI6JppXU2meXl5HHvsseTk5NC+ffugyxERkRLqaGuJXV1Npj/60Y948803MbOgSxERkTKU\nTOsQNVIRkeQUumZaV5OpiIgkr9A107qcTEVEJDmFrpkmezL9z3/+E3QJIiJSTaHLacmcTOfOncvN\nN9/MO++8w7HHHht0OUlr69at7Ny5M+gyRCRJtWjRotb/hiZxa0mMZG2mM2fO5Oqrr+aKK67g6KOP\nDrqcpLV161Y6duzIt99+G3QpIpKkGjduzEcffVSrDTVJW0viJFszdXemTZvGLbfcQlZWFnfeeadm\n7VZi586dfPvttyxYsED3JBaRA3z00UeMHTuWnTt3qpkmUjKdM3V3srKymDFjBtOmTePGG28MuqQ6\no2PHjnTp0iXoMkREgBA202RJpkVFRVx55ZXMmTOHmTNn8otf/CLokkREJEZJ0lpqT7Ik0/nz5/Pg\ngw8yb948Jk2aFHQ5IiJSA6FrpsmSTCdMmMCJJ55I3759gy5FRERqSNeZBqRBgwZqpCIi9UTommmy\nJFMRSR5r167l0EMP5f/9v/8XdClSRmFhIcceeywPPfRQ0KVUKnTNNCV0z1jqiscff5yUlJT9Hw0a\nNODoo49mwoQJbN++vcL1fv/739O7d2+OOOIIDjvsME455RRuv/32Sq/Fff755xk8eDAtW7bk0EMP\npV27dowePZrXX389EU8t6d10001cfPHFHHPMMUGXkhReeuklunbtSqNGjWjfvj233nor+/btq9K6\nX3zxBRMmTKBVq1Y0btyYrl278uyzz5a77LJly+jRoweHHXYYRx55JKNGjeKf//xnqWUOOeQQrr76\naqZNm0Z+fn6Nn1uiqLWIJBEzY9q0aSxYsICHH36YwYMHs2DBAvr06XPAH5KioiJGjx7N+PHjMTNu\nu+027r//fn784x9z2223ceaZZ7Jjx44D9jFhwgRGjhzJF198wTXXXMPDDz/MFVdcwebNm+nfvz9r\n1qypraebFN59912WL1/O5MmTgy4lKbz66quMGDGCI488kgceeIARI0Ywbdo0rrrqqoOu+/XXX9O9\ne3eef/55/vu//5t7772Xww8/nAsvvJCnnnqq1LKvvPIK5557LoWFhfz2t7/l2muvZdWqVfTs2ZMv\nv/yy1LITJkxg586dPPnkk3F9rnHl7qH4ALoAvn79eq8t27Zt83vuuceLiopqbZ/13fr16722f4+1\n5bHHHvOUlJQDnltWVpanpKT4M888U2r8zjvvdDPz66+//oBtvfLKK56amuqDBw8uNX733Xe7mfk1\n11xTbg0LFizwd955p4bPpGa++eabWt3fVVdd5R06dIjrNr/99tu4bq82derUybt06eL79u3bP3bT\nTTd5amqqb9q0qdJ1Z8yY4SkpKf7GG2/sHysqKvLTTz/d27Zt6wUFBaX2k56e7oWFhfvH3nvvPU9N\nTfVrr732gG0PGTLEe/fufdD6D/Y3ovhxoIvHs8fEc2PJ/FHbzXTz5s1+3HHH+THHHOM7duyolX2G\nQRib6dKlS93M/K677to/lpeX50ceeaR37Nix1B+9kiZOnOgpKSn+5z//ef86Rx11lJ988sk1eoFX\nVFTks2bN8s6dO3taWpq3bNnSBw0atL/uLVu2uJn5448/fsC6Zua33Xbb/u9//etfu5n5hx9+6JmZ\nmX7EEUd4ly5d/J577nEz861btx6wjaysLG/YsKHv3r17/9iaNWt84MCB3qxZM2/cuLH37t3b//Sn\nP1Xp+bRv394nTpx4wPiLL77o5513nrdt29YPPfRQP/744/32228/4Ofdu3dv79y5s69fv9579uzp\njRs39qlTp+5//A9/+IP37NnTDzvsMG/atKmfd955/sEHH5TaxsaNG/2nP/2pH3fccZ6WluatW7f2\niRMn+pdfflml5xAvH374oZuZP/TQQ6XGt2/f7mbmd9xxR6XrDx061Fu1anXA+D333OMpKSm+fPly\nd3f/6quvKnwh+MMf/tCPPvroA8Znz57tqampvmvXrkprCKqZ6jBvAmzatImePXtiZqxevZoWLVoE\nXZLUYZs3bwbgiCOO2D/25ptvsmvXLsaMGUNKBRMBLrnkEtydV155Zf86X331FWPGjKnRLSsnTpzI\n1KlTad++PTNmzOBXv/oVjRo1iunwcHEdo0aNYu/evUyfPp1LL72UCy+8EDPj6aefPmCdZ555hkGD\nBtGsWTMAVq5cSe/evcnNzeXWW29l+vTp7Nmzh3POOYd169ZVuv/t27ezdevWcu+m9dhjj9G0aVOu\nueYaZs+ezWmnncYtt9zCr371qwOew86dOxk8eDBdunTh/vvv3z9T//e//z3nn38+TZs2ZcaMGdxy\nyy189NFH9OzZk61bt+7fxrJly9i8eTMTJ07kgQceIDMzk6eeeorzzjuvSj/HL7/8skofBzvn+Je/\n/AUzo2vXrqXG27Rpw9FHH81f/vKXStf/7rvvaNSo0QHjjRs3xt1Zv379/uWACpfdvn07X3zxRanx\nrl27UlRUxFtvvVVpDYGJZ2dO5g9qKZm+9957/r3vfc87derk27dvT+i+wigMyXTlypW+c+dO/9e/\n/uXPPvusf+973/PGjRv7tm3b9i97//33e0pKir/44osVbm/Xrl1uZn7BBRe4e+SV/cHWOZiVK1e6\nmZVKXmVVJ5neeuutbmY+duzYA5Y9++yzvVu3bqXG1q5d62bmCxcu3D+Wnp5+wOHsvXv3+nHHHecD\nBw6s9PmsWLHCzcyXLl16wGN79+49YGzy5MnepEkTz8/P3z/Wp08fT0lJ8Xnz5pVaNjc314844gif\nPHlyqfEvvvjCmzdv7pdffnml+3rqqac8JSXF33zzzUqfg3vk53qwj5SUlHJ/JyUVJ8h//etfBzx2\n+umn+9lnn13p+ldddZUfcsghBxxRuOiiizwlJcWvuuoqd48c3TjiiCN8wIABpZbbuXOnN2nSxFNS\nUnzDhg2lHvvss8/czPzuu++utIagkqkuFImjtWvXMmjQIDp06MBrr72mRBqwb7+Fjz9O/H5OOgka\nN47Pttydfv36lRr7/ve/z5NPPknbtm33j3399dcANG3atMJtFT9W/B65xZ8rW+dgnnvuOVJSUrjl\nllti3kZZZsbll19+wPjo0aOZOnUqmzdv5vvf/z4AixcvJi0tjaFDhwKRyUOffvopN998c6lJK8U/\nxwULFlS67y+//BIzK5X6ix166KH7v87NzeW7776jR48ezJ07l48//pjOnTuXWvanP/1pqfWXLVvG\nnj17uOiii0rVZmacccYZpWZOl9zXd999R25uLmeccQbuzoYNG+jevXulz2P58uWVPl7s5JNPrvTx\nvLy8A+oplpaWtv/fXUUmTZrEQw89xKhRo5g5cyatWrVi8eLFvPDCC6W2X/w7nzFjBjfccAMTJ05k\nz549XH/99RQUFJRatljx7yhZ335RzTROcnJyOP/88+ncuTNLly6lefPmQZcUeh9/DGWOViXE+vUQ\nr3vumxlz5szhxBNPZM+ePfzv//4vOTk5NGzYsNRyxQ2xsj9uZRvu4YcfftB1DuYf//gHbdu2jfu/\n7+JmWdKoUaO4+uqrWbx4MVlZWQA8++yznHvuuTRp0gSATz/9FIgc0i5PSkoKe/bs2X9IuCIeOXpV\nyocffsiNN97I66+/vv+FCER+R3v27Cm1bLt27TikzEXsn376Ke5e7s1ZzKxUTbt27eLWW29l8eLF\npQ5vlrev8pxzzjkHXaYqig+7Fh+GLWnv3r3lHpYtqXPnzixatIjJkyfTo0cP3J02bdpw//33M3ny\n5P2/N4Df/OY3fPnll9x9993cddddmBkZGRlMnDiRhx9+uNSy8H+/o2R9Vy010zjJy8ujR48ePP30\n0wf8I5BgnHRSpNHVxn7iqVu3bvvP4Q0bNowePXowZswYNm3aRONoBO7YsSPuzsaNG/entLI2btwI\nQKdOnaJ1noS78/7771e4TjxU9MeuqKiownXK+yPdpk0bevbsydNPP01WVhZvv/02W7du5e677z5g\nm/feey+nnnpquduu7P/jUUcdhbuza9euUuN79uyhV69eNG/enGnTpnHccceRlpbG+vXrycrKOuC5\nlFd/UVERZsaCBQto1arVAY+XbL6jRo1izZo1XHfddZx66qk0adKEoqIiBg4cWOnPrdjnn39+0GUA\nmjVrRlpaWoWPt2nTBoDPPvuMdu3alXrss88+44wzzjjoPn7yk58wdOhQ3nvvPfbt20eXLl32p/D0\n9PT9yzVo0IC5c+dyxx138Mknn9CqVStOOOGE/fMATjjhhFLbLf4dJesRPzXTOBk4cCAZGRlJ+6op\njBo3jl9iDEpKSgrTp0+nb9++PPDAA1x33XUA9OjRg+bNm/Pkk09y4403lvvv7vHHH8fMOP/88/ev\nc8QRR7Bo0SJuuOGGmP6tHn/88bz22mvs3r27wnRafDhu9+7dpcbLXoxfFaNHj2bKlCl8+umnLF68\nmMMOO2z/8ymuByLpO5Z0dlL0lVDxJK9ib7zxBrt27eLFF18sdYj173//e5W3ffzxx+PutGzZstLa\ndu/ezcqVK7n99ttLvQ3j3/72tyrvq02bNphZuQm7mJnx6KOPVpjiAX70ox/h7qxbt47TTjtt//hn\nn33Gv/71rypfi3vIIYeUmsS0bNkyzIz+/fsfsGzLli1p2bIlEHkBsmrVKs4888z9LxyLFf+OkvV9\njDWbN47USCURevfuzemnn86sWbP2z8Zs1KgR1157LR9//DE33HDDAessXbqUxx9/nEGDBnH66afv\n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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "#PLOT ROC CURVE AFTER CONVERTING PREDICTIONS TO A PANDAS DATA FRAME\n", + "from sklearn.metrics import roc_curve,auc\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "labels = predictions_pddf[\"label\"]\n", + "prob = []\n", + "for dv in predictions_pddf[\"probability\"]:\n", + " prob.append(list(dv.values())[1][1])\n", + " \n", + "fpr, tpr, thresholds = roc_curve(labels, prob, pos_label=1);\n", + "roc_auc = auc(fpr, tpr)\n", + "\n", + "plt.figure(figsize=(5,5))\n", + "plt.plot(fpr, tpr, label='ROC curve (area = %0.2f)' % roc_auc)\n", + "plt.plot([0, 1], [0, 1], 'k--')\n", + "plt.xlim([0.0, 1.0]); plt.ylim([0.0, 1.05]);\n", + "plt.xlabel('False Positive Rate'); plt.ylabel('True Positive Rate');\n", + "plt.title('ROC Curve'); plt.legend(loc=\"lower right\");\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train a regression model: Predict the amount of tip paid for taxi trips" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Train a random forest regression model using the Pipeline function, save, and evaluate on test data set" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "R-sqr on test data = 0.7826300666458392" + ] + } + ], + "source": [ + "//## DEFINE TRAINING FORMULA\n", + "val regFormula = new RFormula().setFormula(\"tip_amount ~ pickup_hour + weekday + passenger_count + trip_time_in_secs + trip_distance + fare_amount + vendorVec + paymentVec + rateVec + TrafficTimeBinsVec\").setFeaturesCol(\"features\").setLabelCol(\"label\")\n", + "val featureIndexer = new VectorIndexer().setInputCol(\"features\").setOutputCol(\"indexedFeatures\").setMaxCategories(32)\n", + "val randForest = new RandomForestRegressor().setLabelCol(\"label\").setFeaturesCol(\"indexedFeatures\").setNumTrees(20).setSeed(1234).setMaxDepth(6).setMaxBins(100).setImpurity(\"variance\");\n", + "\n", + "\n", + "// Fit model, with formula and other transformations\n", + "val pipeline = new Pipeline().setStages(Array(regFormula, featureIndexer, randForest))\n", + "val model = pipeline.fit(trainData)\n", + "\n", + "// SAVE MODEL\n", + "val datestamp = Calendar.getInstance().getTime().toString.replaceAll(\" \", \".\").replaceAll(\":\", \"_\");\n", + "val randForestDirfilename = modelDir.concat(\"RandomForestRegressionModel_\").concat(datestamp)\n", + "model.save(randForestDirfilename);\n", + "\n", + "// MAKE PREDICTIONS ON TEST SET & EVALUATE\n", + "val predictions = model.transform(testData)\n", + "predictions.registerTempTable(\"testResults\")\n", + "\n", + "val evaluator = new RegressionEvaluator().setLabelCol(\"tip_amount\").setPredictionCol(\"prediction\").setMetricName(\"r2\")\n", + "val r2 = evaluator.evaluate(predictions)\n", + "println(\"R-sqr on test data = \" + r2)" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%%sql -q -o predictionsPD\n", + "select label, prediction from testResults" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAcMAAAHUCAYAAABGVUP9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAAPYQAAD2EBqD+naQAAIABJREFUeJzs3Xl8XHW9+P/Xe7JMJvvapEmXdG9KS5dgAUUsZRMrFEQp\nVZRdoSpSL/d6vS643MXrV0D9KReVVZaCWJBNLEvLIoKVtBVaupC2aWnapk2zZybrfH5/nEmYmUyW\nmcxkZjLv5+ORRzufOXPO+8yZmff5bOeIMQallFIqkdmiHYBSSikVbZoMlVJKJTxNhkoppRKeJkOl\nlFIJT5OhUkqphKfJUCmlVMLTZKiUUirhaTJUSimV8DQZKqWUSniaDNWoicgrIrIx2nHEKhG5X0T2\n+5W5ReT70YrJX6AYR7m+H4iIO1zrU6MnIhkiUiciqwd5/koRuXWQ55JF5KCI3BDZKKNHk2GMEZE1\nnh/KN0e5nm+LyMpwxTWMmL2mnydRu73+TojIZhG5WkRkjMIwDHyPApUNSUQqRORWEZkStshGGI/n\nh9I9gr99XuuLSDIUkbmebTlFJDsS24gmETndc5zDvW83Ay3Ao0MsE/AzYIzpAW4HvisiqWGOKyZo\nMow9nwf2A0tFZPoo1vMfwFglw1hmgA+ALwBXAD8CkoB7gP+KYlyOELY/D7gVKA97NMN7Fev98/7r\nBF7zK7vZs/yPgfQIxXIFcMTz/89GaBvR9FHg+0BuuFYoIsnATcDvzOAXpE4B7EOs5j6gEOs3atxJ\njnYA6kMiMg3ri3AJ8FusH/AfRzWo8aHZGLOu74GI/BbYDXxNRL5njOkN9CIRcRhjXJEIyBjTFcLL\nhCjVwo0xNUCNTzAivwH2GWMeCbC8GwhlH0fi88AjwDSs78i9EdpOtESixeJCrET2+ICNiXwb+AYw\nwfP4FmAb8HVjzOa+5YwxzSLyAnAVcH8EYowqrRnGli8ADcBzwB89jwcQyzdE5B0RcYnIMRF5XkSW\neJ53Y52VX+XVfHWv57mAfUOB+ng8TYkve/oZOkRkR6h9BiLyroi8PMi+1IrIH7zKLheRt0WkRUSa\nPft5UyjbDcST4N4CMoAizzZf8WxniYi8JiLteNXcROQCT3mbJ65nRWRegP25WES2e47LOyJycaAY\nAvUZikipiNzjeT86RGSfiNzp6a+5Euh7j/qafntF5MxIxTgag3ye3CLySxH5vIjs8mz/bRH5eBDr\nPQOYitXU9xhwpoiUBliuRkSeFpFPiMg/PE2q74jIJzzPf8br+/O2iCwKsI7lIvK65/1sFJE/ichc\nv2WC+T717f9Kz/ehw3Mczvda5lbgp56HNV7HeYrn+XM9MTWKSKvnfRxJC8NKoMYY4993fRXW53wD\n8Ausk4yvAQeByQHW8yJwhoiErdYaK7RmGFs+D6w3xvSIyDrgBhGpNMZU+S13L3AlVtL8HdZx/Dhw\nGrAFqxnpHuDvWDVMgL2efwfrGwpUfgOwHXgK6ME6u7xTRMQY839B7ttjwK0iMsEYc8yr/OPARGAd\nWF92rC/ki8C/eZapwKox/zLIbQ5lBtALNHkeG6wz5z9j/dD+HqjzxPRFrDPhv3hiSgduBF4XkcXG\nmIOe5c7DOonZDvw7UIDVtHRouGBEZCLwDyAb+A1WzbUMqxkwHas58pfA14H/BHZ5XrpzrGIM0mCf\ns2XAKs++dAJrgOdFZKkx5r0RrPcLwF5jTJWI7ABcwGrgtgDbnwU8jPV+Pgj8K/C0iNyIlQB+jVUL\n+w+sz+ecvheLyDlYn4W9WE3TDqxmxr+KyJK+93OI/Rys/OPAZ4A7gVbPOv8oIlOMMY3AemA2cDlW\nbe2E53XHPSc2z2DV2r6H9f7NxPpuDOejWL8N/j4F7DbGXOk54ZpqjPkd1u9KIFVYlaiPYr0/44cx\nRv9i4A+oxBpwcJZX2UHgdr/lzvIsd/sw62sF7g1Qfh9W05Z/+a1Ar1+ZPcByzwPv+5VtAjYOE88s\nT9xr/Mp/DTT3bQu4A2gM4/u6CdiB9aNfgPWD9wtPLE/6LdcLXOf3+gys2vr/+ZUXAY3AXV5lW7GS\nSqZX2dmebe3ze70b+L7X4weAbmDxEPtyqSfGM8cixhG8twE/Y0N8ntye+Bd5lU0GnMAfR7C9ZOA4\n8EOvsoeALQGW3e/Z1lKvsnM9MbQBZV7l1/u/r5736QiQ41W2AOuk8L4Qv09urORd7rdOn+8F8C+e\neKb4vf4bnvK8II9Tkud1Pw3w3Dpgn2eZK70/k4Osq8QT7y2hfB9j+U+bSWPHF4CjwCteZY8Bl4v4\njHq8FOvD+KNIB2SM6ez7v4hki0gBVg1luohkBbmu97HOaFd5rdOGtT9Pe22rCcjwbjoKgwqsH9Hj\nWDWpr2KdYV/rt1wnA/tCzgVygEdFpKDvD+us/+9YJyeISAmwELjfGNPW92JjzMvAkDUez/FdifU+\nbA1h/yIeYxj9zRizzWvbH2C1PJzv9zkP5FNAPp5WBI91wEIRqQiw/HvGq88L670AeNkYU+tXLsB0\n8Hmf7jPGNHvF+i5Wi8WnholzKC8aq//Ve50tfdseRl8rxiUjeK+85WPtX2OA5+7HanZ+HVgBFIk1\n2GYwfesoDGL7cUGTYQzwJIVVWLWT6SIyQ0RmAJuxzsTO9lp8OnDYGNM0cE1hj+tjIvKSiLRhfRGP\n82E/Wk4Iq3wM+JinSRCsH+kJnvI+dwJ7gD+LyAeePrTRJsb9WO/h2cDHgBJjzEpjTIPfcrXGGkLu\nbRbWD8kmPkyox4FjWEmoyLPcVM+/1QG2v3uY+Iqwmkd3DL8rAY1FjOESaNt7sJp1iwI85+0KrGPZ\n7fUd2YdV2wrUv37Q+4ExpsXzX/8m4b6El+f5t+992hNgnTuBQhFxDBPrYD4IUNbote2hPAa8gdWE\nWSci60Tkc0EkxgHLGWM2YH0vmrGS4VeBRhG5S0QCxdS3jpidThUq7TOMDcux+s0ux+r/8Gawvugv\nhWlbg32Ik7wfiDWt4yWsL/9arC9xF9YX5mZCO5F6DPgf4HNYfUaXYSXZDf3BGXPcM5jhfOACz9/V\nIvKAMebqELYJ0G6M2TSC5QKNHLVhvWdX4OlD9OOfPKMhHmIcFU9LxKexhv6/7/e0wepv/65fecBR\nwkOUhzKKc0Tfp3Bs2xjTgTVg6Cys7+EnsU6iXxaR84ynHTOABk+cAROuMeYVrEFZV2L16dZi9a9O\nYWAtuG8d9cPFG280GcaGvh+xNQz8UlyK1Sxyg6cpcS9wnojkDlM7HOyL0Ujg+Uvlfo8vBFKBC72b\nlETkbEJkjKkRkc3AKhH5NdYUkieNMd1+y/VgDQ56zrPN/wO+LCI/Nsbs819vhO3FOibHjTFDXWXn\ngOffWQGemxOgzNtxrKay+cMsN9gxHYsYw2WwbTux3ofBXIqVCG/gw0El3q//TxH5qDHmb2GIse99\nCvSezAXqzYdTbkb6fQrGkLUuz4ndJuAWsaZF/CdWK0vAY2+M6RWRvVhTUYaz3xjzI8/Jx1dFJMMY\n0+71fN86do5gXXFFm0mjTETSsJLCM8aYJ40xT3j/Ab/CakK7yPOS9VjHLeBlk7y0E/hLuhfIEZH+\nH15Ps6X/8Pq+M1ib13I5WHOMRuMxrFGv12D1O3g3kSIi+QFe867nX7tnmWQRmePp24m0DViJ6j8C\n9aWISCGAMeYoVp/old79qZ7RsQOmN3jznNH/CbhQPNNjBtGOlfT8j2vEYwyj00Vksde2J2N9tjcM\nUbMBq3VknzHmdwG+I7dhvTcBpyIFy+996r8KjOc7cx6ekzSPkX6fgtGXfHyO8yDNlv/E+kwMNVke\n4E3gFP/CIaZIpGL9BvjPFT0Fa8zCqK6QFYu0Zhh9K4Es4OlBnn8L64z5C8DjxphXRORB4CYRmY01\nlN6GNWR7ozHmTs/rqoBzRGQtcBjrjG8z1rSB/wX+JCK/xBqJeANWn5H3D/ELWKMbnxVrcnUWcB1W\nDXY0SegPwM88fycA/7mHd3sS4kasvp1yrHlPW40xfWejZVhnpvdjJdWIMca0eobi/x7YIiKPYh2P\nKVhNVX/FGh4P8G3gWeANseZ1Fnhi3w5kDrOp/8Dq33tNrIsC7ARKsaZWfMzT37UN6wfqW54fsU6s\nwSD1YxRjOGwH/iIi/x/WD+2NWDWhHwz2ArHmEZ4F/DzQ88aYLhHZAHxORG4yg1xEIUj/ijV14C0R\nuQerT/NrWDXBH3otN9LvUzCqsBLcf3uOZTfWgK/vizWv9Dms2msx1vt3EOsYD+Up4AoRmWmM8e63\n/YOI1GF9JmYD00Tkp1iDyx73b7UBzgHeMNY0kPEl2sNZE/0P60PaBqQNscy9QAeeIdVYX5RvYg24\ncGGNQn0W3yHrs7GaUtqwfkDv9XrubKwzShfWKMLVBB4KvgJriHk71hnwv2DVDH2GfXu283IQ+/y6\nZx13BXjuEqzpG0c88e3Hmn4xwWuZqZ7X3zOCbW0C/jna5YAzsX4cGzzvxx6suZyL/Za7GOsH34lV\no12JNfx+r99yvcD3/MomeZY96nn9+1jTQJK9lrnGU97FwOkAYY1xBO9Zy2DHwPN56vErc2P1Fa/G\nShZOrLmVHx9mO2s9+7psiGW+5Fnm057H+4GnAizXC/zCr6zv87TWr/wsrNHTbVhJ8ElgToB1jvT7\nNGDbnvJ9/u8j1snRQaxE2It1YnMW8ARW/73L8++DwIwRHKsUrAFV/xFgHx/xxNCBdYJVjZXgM/yW\nzfYsc1Uwn5N4+RPPTiqlVESJdUWWXxljwnY1ITVyIvJd4Gpgpgnwwy8iX8KaAxlw2paI3AzcgpV8\nOwMtE8+0z1AppRLDHVjNuJcP8vygI1o9fdE3Az8ej4kQtM9QKaUSgrFGhQ7V378Vq3k50Gt7iM7d\nUsaMJkOl1FgxjMPJ2uOFMeadaMcQTdpnqJRSKuGNy5qh57qM52Pdf60jutEopZSKojSsJt4Nxhj/\nCzb0G5fJECsRPhztIJRSSsWML2BNIwlovCbDGoCHHnqIiopAF7MfvbVr13LHHXdEZN1jJd73Id7j\nh/jfh3iPH+J/H+I9fojsPuzcuZMrrrgCPHlhMOM1GXYAVFRUsGRJqBeBGFpOTk7E1j1W4n0f4j1+\niP99iPf4If73Id7jhzHbhyG7zHSeoVJKqYSnyVAppVTC02SolFIq4WkyDNHq1f734I0/8b4P8R4/\nxP8+xHv8EP/7EO/xQ2zsw7icdO+5J1xVVVVV3Hcsq9h28OBB6uvH3U2/lYobhYWFTJkyZdDnt2zZ\nQmVlJUClMWbLYMuN19GkSkXcwYMHqaiowOl0RjsUpRJWeno6O3fuHDIhjoQmQ6VCVF9fj9PpjOh8\nVqXU4PrmENbX12syVCraIjmfVSk1NnQAjVJKqYSnyVAppVTC02SolFIq4WkyVEoplfA0GSqllEp4\nmgyVUuPOsmXLWL58ebTDiFmvvvoqNpuN1157rb/sqquuYtq0aVGMylegGCNJk6FSakh33nknNpuN\n008/fVTr+Z//+R+eeuqpMEU1NBEZk+3EM//3SESw2YJPCZE8rmN5HDUZKqWG9MgjjzBt2jQ2b97M\nvn37Ql7Pf//3f49ZMlTBu/vuu9m1a1fQrxsvxzUmkqGIfFxEnhaRWhFxi8hFQyx7l2eZm8YyRqXG\ngjGGhoYG9uzZw549e2hoaCCa1w/ev38/f/vb37j99tspLCzk4Ycfjlosiohe+i8pKYmUlJSIrT/W\nxUQyBDKAbcAaYNBvvohcApwK1I5RXEqFRXd3N06nE7fbPegyxhh27HiPDRve4fXXm3j99SY2bHiH\nHTvei1pCfPjhh8nPz2fFihV89rOfHTQZGmP4xS9+wcknn4zD4WDChAlccMEFbNliXRfZZrPhdDq5\n//77sdls2Gw2rrnmGmDwvqof/OAHA5rt7rvvPs4++2yKi4tJS0vjpJNO4q677gpp3xYsWMDZZ58d\ncF/Kysq47LLL+sseffRRTjnlFLKzs8nJyeHkk0/ml7/8ZUjbLS8v56KLLuLFF19k8eLFOBwOTjrp\nJJ588kmf5R544IH+PrM1a9ZQXFzM5MmT+58/fPgw11xzDSUlJaSlpTF//nzuu+++Adurra3l4osv\nJjMzk+LiYr75zW/S2dk54DMV6DiM5rhGIsZIionLsRlj/gL8BUAGaSQWkTLgF8D5wJ/HLjqlQtfd\n3c2uXXvYu7eezk5Dbm4yc+dOZurUqQOWraurY+vW42RlncTEiUUANDUdZ9u2HRQW1lFSUhJwG83N\nzbS2tpKamkphYWFI/T6DeeSRR7j00ktJTk5m9erV3HXXXVRVVfXdBaDfNddcwwMPPMCKFSu4/vrr\n6enp4fXXX+ett95iyZIlPPTQQ1x77bWceuqpfPnLXwZgxowZgNUvFOhrH6j8rrvuYv78+axcuZLk\n5GSeeeYZ1qxZgzGGG2+8Mah9W7VqFT/84Q85duwYEyZM6C9//fXXOXLkSP9thV588UU+//nPc+65\n5/LTn/4UsK6J+be//Y2bbgq+gUpE2LNnD5dffjk33HADV111Fffddx+f+9zn2LBhw4AEvWbNGiZM\nmMCtt95Ke3s7AMeOHePUU08lKSmJm266icLCQp5//nmuvfZaWltb++Pq6Ohg+fLlHDp0iG984xtM\nnDiRBx98kI0bNwbsM/QvG81xjUSMEWWMiak/wA1c5FcmwMvA1zyP9wM3DbGOJYCpqqoySkVKVVWV\nGepz5na7zVtvvW3uvvsN8/jjteaZZxrNQw+9bx56aJM5cOBAgPVtM/fcs81s2mR8/u65Z5upqto2\nYPnu7m7z9ttbzbp1m8w997xqHnhgk3nppTdMc3NzWPbv7bffNiJiNm7c2F82efJks3btWp/lNm7c\naERkQLm/zMxMc/XVVw8ov+qqq8y0adMGlP/gBz8wNpvNp6yjo2PAcp/85CfNzJkzfcqWLVtmzjrr\nrCHj2bNnjxER8+tf/9qnfM2aNSY7O7t/WzfffLPJzc0dcl3BKC8vNzabzfzpT3/qL2tpaTGlpaWm\nsrKyv+z+++83ImI+8YlPGLfb7bOOa6+91pSVlZnGxkaf8tWrV5u8vLz+2H/+858bm81m1q9f37+M\ny+Uys2bNMjabzbz66qv95f7HYbTHNRIx+hvuO+i9DLDEDJF7YqWZdDj/DnQZY34V7UCUGqmmpib2\n7WuluHgehYWlZGbmUlY2E7e7lN27PxjQBNTT00tSUuqA9SQlpdLT0zug/P33q9m2rQ2HYwHTpn2c\n4uJTqalxsHnzu/T2Dlw+WA8//DAlJSUsW7asv2zVqlU8+uijPrGvX78em83G97///VFvczh2u73/\n/y0tLZw4cYIzzzyTffv20draGtS6Zs2axaJFi3jsscf6y9xuN+vXr+eiiy7q31Zubi7t7e1s2LAh\nPDsBlJaWsnLlyv7HWVlZfOlLX2Lr1q0cO3asv1xEuP766wfUkJ544gkuvPBCent7OXHiRP/feeed\nR1NTU38z5vPPP8/EiRP5zGc+0//atLS0/lrcUEZ7XMcixnCKiWbSoYhIJXATsDjY165du5acnByf\nstWrV8fEXZXV+NfW1obLlcTEibk+5Tk5hTQ1Haazs5O0tLT+8qKiPN599xBdXZ2kplo/xF1dnfT0\n1FNUNMlnHb29vezde4ysrGnk5BQAYLc7mDRpLocP/536+nqKi4tDjt3tdvPYY49x1lln+YwgXbp0\nKbfddhsvv/wy55xzDgD79u2jtLSU3NzcwVYXNm+88Qa33norb731ls9gEhGhubmZrKysoNa3atUq\nvvOd73DkyBEmTpzIpk2bOHbsGKtWrepfZs2aNTz++ON86lOforS0lPPOO4/LLruM888/P+T9mDlz\n5oCy2bNnA1BTU+PTbFteXu6z3PHjx2lqauK3v/0tv/nNbwasR0T6E+qBAwcCbmvOnDnDxjia4zpW\nMfpbt24d69at8ylrbm4e0WtjPhkCZwBFwAdeZ0dJwO0icrMxZvpgL7zjjjv01joqaux2O8nJvXR2\nurDbHf3lLlcbGRm2ASP3ysrKmDHjKNXVVWRkTASgvf0IM2cmU1ZW5rNsd3c3LpebtLQMv2066OlJ\noqura1Sxb9y4kSNHjvDoo48O+HERER5++OH+ZDhag/UL+ddu9+3bxznnnENFRQV33HEHkydPJjU1\nleeee46f//znQw5OGsyqVav49re/zeOPP85NN93EH/7wB3Jzc30SXVFREdu2bWPDhg08//zzPP/8\n89x3331ceeWVAQeDhJvD4fB53LefV1xxBVdeeWXA15x88skRj2so0YoxUGXH6073Q4qHZPh74EW/\nshc85ZH/JCoVosLCQsrKUtm/fyeTJs3FbnfQ0nKC9vaDLFxYTFJSks/yqampnHbaYkpKajh48DAA\nixcXUF5eTmqqb/Op3W4nLy+VI0fqyc7O7y9vbW3E4eglMzNzVLE/9NBDFBcXc+eddw5ozl2/fj1P\nPvkkd911F3a7nRkzZvDCCy/Q1NQ0ZC1isKSXl5dHU1PTgPKamhqfx8888wxdXV0888wzPicHL7/8\nchB75qu8vJylS5fy2GOP8dWvfpUnn3ySSy65ZMCJSnJyMitWrGDFihUA3Hjjjfz2t7/le9/7HtOn\nD3o+Pqjq6uoBZbt37+6PaShFRUVkZWXR29s77FV2pk6dyo4dOwaUj2Q+4WiO61jFGE4x0WcoIhki\nslBEFnmKpnseTzbGNBpj3vP+A7qBo8aY96MYtlJDstlsfOQj85k+vYP6+s3s3/8aHR3bWbQoi1mz\nBjYLgdVXMnfuXM4772Ocd97HmDt3rk9Tah8RYc6cydhsh/nggz20tjZy/Pghjh17jxkzssnLyws5\n7o6ODp588kkuvPBCLrnkEj7zmc/4/H3ta1+jpaWFp59+GoBLL70Ut9vND3/4wyHXm5GRETDpzZgx\ng+bmZrZv395fduTIEf70pz/5LNd38uBdA2xubub+++8PdVcBq3b41ltvce+991JfX+/TRArQ0NAw\n4DULFiwAoLOzE4Cenh52797N0aNHR7TNw4cP+0ylaGlp4cEHH2Tx4sU+TaSB2Gw2Lr30UtavXx8w\nidTX1/f//1Of+hSHDx9m/fr1/WVOp5Pf/e53w8Y4muM6VjGGU6zUDE8BNmGN+DHAbZ7yB4BrAiwf\nvVnISgUhOzubZctOo6Ghga6uLjIzM8nOzg7LuidPnsyZZ8KuXQdpajpMaqpw2mlFzJ49a1Trfeqp\np2htbeWiiwJf++K0006jqKiIhx9+mM997nMsW7aML37xi/zyl79kz549fPKTn8TtdvP666+zfPly\n1qxZA0BlZSUvvfQSd9xxB6WlpUybNo2lS5dy+eWX861vfYuLL76Ym266ifb2du666y7mzJnTP8gC\n4LzzziMlJYVPf/rTfOUrX6G1tZW7776b4uLiESehQC677DJuueUWbrnlFgoKCgZMbbjuuutoaGhg\n+fLlTJo0iZqaGn71q1+xePFiKioqAGueXEVFBVdddRX33nvvsNucPXs21113Hf/4xz8oLi7mnnvu\n4dixYzzwwAM+y/nXyvv85Cc/4ZVXXuHUU0/l+uuvZ968eTQ0NFBVVcXGjRv7k83111/Pr371K774\nxS/y9ttv909byMjICLheb6M9rmMRY1gNNdQ0Xv/QqRVqDIxkWPdY6O3tNU6n03R3d4dlfRdddJHJ\nyMgwLpdr0GWuvvpqY7fbTUNDgzHGmkZy2223mXnz5pm0tDRTXFxsVqxYYbZu3dr/mt27d5tly5aZ\njIwMY7PZfIbjv/TSS+bkk082aWlppqKiwjzyyCMBp1Y8++yzZtGiRSY9Pd1Mnz7d/OxnPzP33Xef\nsdlsPtNVli1bZpYvXz7ifT7jjDOMzWYzX/nKVwY898QTT5hPfvKTpqSkxKSlpZny8nKzZs0aU1dX\n179MTU2Nsdls5pprrhl2W+Xl5ebCCy80L774olm4cKFxOBxm3rx55oknnvBZ7v777zc2m23Qz9fx\n48fN17/+dTN16lRjt9tNaWmpOffcc80999zjs9wHH3xgLr74YpOZmWkmTJhgvvnNb5oXXngh4NSK\n6dOn+7x2tMc13DH6C+fUCjFRvNRTpIjIEqCqqqpKB9CoiOnrmNfPmQrGtGnTWLBgQX8zswrdSL6D\nXgNoKo0xWwIuRIz0GSqllFLRpMlQKaVUwtNkqJRSY2iwa7Gq6IqV0aRKKZUQRnNPSBU5WjNUSimV\n8DQZKqWUSniaDJVSSiU8TYZKKaUSng6gUWqUdu7cGe0QlEpI4fzuaTJUKkSFhYWkp6dzxRVXRDsU\npRJWeno6hYWFo16PJkOlQjRlyhR27tzpcwV+pdTYKiwsZMqUKaNejyZDpUZhypQpYfkiKqWiSwfQ\nKKWUSniaDJVSSiU8TYZKKaUSniZDpZRSCU+ToVJKqYSnyVAppVTC02SolFIq4WkyVEoplfA0GSql\nlEp4mgyVUkolPE2GSimlEp4mQ6WUUglPk6FSSqmEp8lQKaVUwtNkqJRSKuFpMlRKKZXwNBkqpZRK\neJoMlVJKJTxNhkoppRKeJkOllFIJT5OhUkqphKfJUCmlVMLTZKiUUirhaTJUSimV8DQZKqWUSngx\nkQxF5OMi8rSI1IqIW0Qu8nouWUT+V0TeEZE2zzIPiMjEaMaslFJq/IiJZAhkANuANYDxey4dWAT8\nEFgMXALMAZ4aywCVUkqNX8nRDgDAGPMX4C8AIiJ+z7UA53uXicjXgL+LyCRjzKExC1QppdS4FCs1\nw2DlYtUgm6IdiFJKqfgXd8lQROzAT4BHjDFt0Y5HKaVU/IuJZtKREpFk4HGsWuGa4ZZfu3YtOTk5\nPmWrV69m9erVkQlQKaVU1Kxbt45169b5lDU3N4/otWKM/3iV6BIRN3CxMeZpv/K+RFgOLDfGNA6x\njiVAVVVVFUuWLIlkuEoppWLYli1bqKysBKg0xmwZbLm4qBl6JcLpwFlDJUKllFIqWDGRDEUkA5gJ\n9I0knS4iC4EG4AiwHmt6xaeBFBEp9izXYIzpHut4lVJKjS8xkQyBU4BNWH2BBrjNU/4A1vzCCz3l\n2zzl4nk28Ig9AAAgAElEQVR8FvDamEaqlFJq3ImJZGiMeZWhR7bG3ahXpZRS8UOTjFJKqYSnyVAp\npVTC02SolFIq4WkyVEoplfA0GSqllEp4mgyVUkolPE2GSimlEp4mQ6WUUglPk6FSSqmEp8lQKaVU\nwtNkqJRSKuFpMlRKKZXwNBkqpZRKeJoMlVJKJTxNhkoppRKeJkOllFIJT5OhUkqphKfJUCmlVMLT\nZKiUUirhaTJUSimV8DQZKqWUSniaDJVSSiU8TYZKKaUSniZDpZRS41flyBbTZKiUUmr8eROQkS+e\nHLFAlFJKqWgIIgn20ZqhUkqp8eEYAxNh1cheqslQKaVU/BOg2OvxZsCM/OXaTKqUUip+9QApfmVB\nJME+WjNUSikVn87FNxH+hpASIWjNUCmlVDzy7xsMMQn20ZqhUkqp+PH/8E2Eqxh1IgStGSqllIoX\n/rVBd4CyEGnNUCmlVGzbhG/SS8OqDYYpEYLWDJVSSsUy/4TXBOSEfzNaM1RKKRV7DhF4kEwEEiFo\nMlRKKRVrBJjs9fgdwjJIZigxkQxF5OMi8rSI1IqIW0QuCrDMj0TksIg4ReRFEZkZjViVUkpFSBeB\na4MLIr/pmEiGQAawDVhDgPwvIt8CvgZ8GVgKtAMbRCR1LINUSikVIacAdq/HDxHx2qC3mBhAY4z5\nC/AXABEJND7oG8CPjTHPepb5ElAHXAz8YaziVEopFQFhnkAfilipGQ5KRKYBJcDLfWXGmBbg78Dp\n0YpLKaXUKH0P30T4ZaKSCCFGaobDKMF6e+r8yus8zymllIo3EZxAH4p4SIYhW7t2LTk5vuNwV69e\nzerVq6MUkVJKJbjngE97PS4BjoRn1evWrWPdunU+Zc3NzSN6rRgTpTrpIETEDVxsjHna83gasBdY\nZIx5x2u5V4Ctxpi1AdaxBKiqqqpiyZIlYxO4UkqNgNPppKOjA4fDgcPhiHY4Y8u/5teGNXwygrZs\n2UJlZSVApTFmy2DLxXzN0BizX0SOAmdjzTZBRLKBU4FfRzM2pZQaqe7ubnbs2EV19QlcLnA4YObM\nAubPryA5OeZ/ikdnHzDDryy26mGxkQxFJAOYyYfnDdNFZCHQYIz5APg58F0RqQZqgB9jXZ/gqSiE\nq5RSQduxYxdVVS3k5VVQXJxLW1sTVVXVwE4WLRqDiXTR4l8b3APMikYgQ4uJZIg1w2QT1rmCAW7z\nlD8AXGOM+amIpGPdujEXeB24wBjTFY1glVIqGE6nk+rqE+TlVZCfXwzQ/2919U7mzHGNvyZTF5Du\nVxZjtUFvMTG1whjzqjHGZoxJ8vu7xmuZHxhjSo0x6caY840x1dGMWSmlRqqjowOXCzIzc33KMzNz\ncbnA5XJFKbIImYlvInySmE6EEDs1Q6WUGrfS0tJwOKCtram/RgjWY4eD8VMrNAysYsV4EuwTEzVD\npZQaz9LT05k5s4DGxmoaGuro6uqkoaGOxsZqZs4sGB/J8GZ8M8otxE0iBK0ZKqXUmJg/vwLYSXX1\nTurqrNGklZUFnvI4FwOXUxstTYZKKTUGkpOTWbRoAXPmuHC5XONjnuEfgc95Pa4A3otSLKOkyVAp\npcbQuEiCMLA22IHvXSfijPYZKqWUGrmdBG4WjeNECFozVEopNVL+SXA/UB6FOCJAk6FSSqmhtQLZ\nfmVxOEhmKNpMqpRSanB5+CbCDYy7RAhaM1RKKRVIHE+gD4XWDJVSSvm6Gt/s8CPGdSIErRkqpdSY\niYt7GY6DCfSh0GSolFIRFhf3MnwAuMrr8UeBN6ITSjTEyFFQSqnxK+bvZehfG+wCUqIRSPRon6FS\nSkXQh/cynEl+fjGpqXby84vJy5vpqSlG8fZNbxC4WTTBEiFoMlRKqYiK2XsZCnCG1+PDJEz/YCCa\nDJVSKoK872XoLWr3MjxO4NrgxLENI9ZoMlRKqQiKqXsZCjDB6/GzJHRt0JsOoFFKqQiL+r0Mexn4\na69J0IcmQ6WUirCo3stwLrDb6/Fa4Pax2XQ80WSolFJjZMwn2yfoBPpQaJ+hUkqNN9/BNxFORhPh\nMLRmqJRS44l/bbAHSIpGIPFFa4ZKKTUevEDgZlFNhCOiNUOllIp3/knwMAk/bzBYWjNUSql49T46\ngT5MNBkqpVQ8EmC21+OH0EEyo6DNpEopFU+6ALtfmSbBUdOaoVJKxQvBNxGuRBNhmGjNUCml4oFO\noI8orRkqpVQsOwVNhGNAa4ZKKRWr/JNgL1qFiRB9W5VSKtbcQeDaoP5iR4zWDJVSKpb4J8FaoDQa\ngSQWPc9QSqlY8E8C1wY1EY4JTYZKKRVtAizyevw0OkhmjGkzqVJKRYsTyPAr0yQYFXFRMxQRm4j8\nWET2iYhTRKpF5LvRjksppUIm+CbCa9BEGEXxUjP8d+ArwJeA97Bm3twvIk3GmF9FNTKllAqWzhuM\nOSNOhiJy+0iXNcZ8M7RwBnU68JQx5i+exwdF5PPA0jBvRymlIsc/CYImwhgRTM1wsd/jJZ7X7/Y8\nno01JbQqDHH5+xtwvYjMMsa8LyILgY8BayOwLaWUCj//ROgOUKaiZsTJ0BhzVt//ReSbQCtwpTGm\n0VOWB9wHvB7uIIGfANnALhHpuwbDd4wxj0ZgW0opFT6fB9b5lWltMOaE2mf4L8B5fYkQwBjT6BnU\n8gJwWziC87IK6yN1OVaf4SLgFyJy2BjzYJi3pZRS4eFf8zsClEQjEDWcUJNhNlAUoLwIyAo9nEH9\nFPgfY8zjnsc7RKQc+DYwaDJcu3YtOTk5PmWrV69m9erVEQhRKaU8/gys8CvT2mDErVu3jnXrfKvh\nzc3NI3ptqMnwSeA+EfkXYLOn7FTg/wFPhLjOoaRj9Ud6czPM1JA77riDJUuWRCAcpZQahH9t8I/A\npdEIJPEEquxs2bKFysrKYV8bajK8AfgZ8AiQ4inrAe4B/jXEdQ7lGeC7InII2IE1eGctcHcEtqWU\nUsE7ART6lWltMG6ElAyNMU5gjYj8KzDDU7zXGNMetsh8fQ34MfBrYAJwGPg/T5lSSkWXf23wQqxL\nqqm4MdpJ9xM9f68ZY1wiIsaYsJ8LeZLsNz1/SikVGwLdVklrg3EppMuxiUiBiLwM7MHqKp7oeeoe\nEQn3SFKlVAJwOp00NDTgcrmiHcrICJoIx5FQa4Z3AN3AFGCnV/ljwO1YUy+UUmpY3d3d7Nixi+rq\nE7hc4HDAzJkFzJ9fQXJyjF4xUi+nNu6EeqHu84BvGWMO+ZW/D0wdXUhKqUSyY8cuqqpasNkqKC4+\nHZutgqqqFrZv3zn8i8faOWgiHKdCPe3KwLr5iL98oDP0cJRSicTpdFJdfYK8vAry84sB+v+trt7J\nnDkuHA5HNEP8kH8SbARyoxGIioRQa4avY91Boo8RERvwb8CmUUellEoIHR0duFyQmembVTIzc3G5\niI3+w0cIXBvURDiuhFoz/DfgZRE5BUjFukLMSVg1w4+FKTal1BhyOp10dHTgcDjGrDaWlpaGwwFt\nbU39NUKwHjscRL9W6J8EXwLOjkYgkRON4x6LQp1nuF1EZmPN/2sFMrGuPPNrY8yRMManlIqwaA5g\nSU9PZ+bMAqqqqgGrRtjW1kRjYzWVlQXR+3E+BEz2KxtnfYNxOXApgkLaYxGZAnxgjPmvQM8ZYw6O\nOjKl1JjoG8CSl1dBcbGVjKzktJNFixZEfPvz51cAO6mu3kldnfWjXFlZ4CmPAv/a4LWMy2tdRfu4\nx5pQ0/9+rLmFx7wLRaTA81zSKONSSo2BWBjAkpyczKJFC5gzx4XL5Rq0uS7izXkJNIE+Fo57rAk1\nGQqBPyaZQEfo4SilxlLfAJbi4oEDWOrq6E9OY2GwJDcmzXkJdgf6WDrusSKoT5KI3O75rwF+LCLe\n0yuSsO5csS1MsSmlIizmB7AQnua8IWuVCThvMB6O+1gL9rRqsedfARYAXV7PdQH/xLqbhVIqDsTs\nABaP0TbnDVmrLEm27jThLQESIcT+cY+GoJKhMeYsABG5D/iGMaYlIlEppcZMzA1g8TLa5rzBapWL\nFvv99LVj3TU1gcTycY+GUBvcbw70WhHJB3o0SSoVP0Y6gCUaRtOcF6hWueSpYsp/X+y7YILUBv3F\n8nGPhlCvQPMocFmA8ss8zyml4ozD4SA/Pz+mfhD7mvMaG6tpaKijq6uThoY6GhurmTlz6OY8/6vb\nLDsLyn//4fMtz7YkbCL0FovHPRpCTYanEviya694nlNKRVnc3RJpEPPnVzBvXgrNzX/ngw9ewe3e\nSWVl9rDNeX21yqR32lh2lu9zf3z8FVKWp0QwahVvQm0mtWNdhs1fCpDYpxdKRdl4urJI377U1jpx\nu9Ow2TqZPDl/RPuSnp7OZz+3zKfs4JkuXvzSFio9tUq9FJnqE+o3YzPwZeDrfuU3AFWjikgpNSqx\neGWRUJOO975Mnmzty/bt1djtw+xLNwNO1x/8/SvWIJGZBcyZM5Nt294dFycMKjxCPerfBV4SkYXA\ny56ys4GPYN3rUCkVBd6DRtLTc+jo6CQjIxeYGZEriwyX5EZTSw15WkWACfQPP7SBKZMyWLJkIVlZ\nWWzb9m7MnTCo6Ar1Qt1viMjpwL9iDZpxAe8A1xpj3g9jfErFjVhocuvo6KC1tYeOjgaOHTtCZyfY\n7TBhgh2HozfgVIRQ4h5pkhtNLTWkaRV+ifCJ9XVkZubiaFtEdXU1OTk1zJ49Qy9FpgYIuT3AGLMN\n+EIYY1HjSCwkhrESS310aWlp1NfXsn9/MiUlJ5OXl4nL1ca7777DtGmHcDg+Gpa4R5LkRjthPqhp\nFQFqg0+srwu43aKifL0UmRpgxN9UEcnumz8oItlDLavzDBNXLCWGsRJ7fXS9iDgR6UbEeP51Ar1h\niXukSW60E+ZHfJUUv0TYUNvAcy+/Q3Hm6QG363Q66elppaGhjpKSKf3PJ/KlyFRwUysaRWSC5/9N\nQGOAv75ylaD6fmBttgqKi0/HZqugqqqF7dt3Rju0iPgwMcwkP7+Y1FQ7+fnF5OXN9JwQjO20ho6O\nDoqKpnLSScW43TtpaHgTt3snJ51UTFHR1P54RhP3SO9O712z8xZM0pk/v4LKymzc7p3U1b3pO63i\n0wS8rmhabuDtNjfXc/ToATZv3kdt7XFeffVFtmzZjMvlHPHcRTV+BXOqvhxo8Pz/rKEWVIkpEW8L\nE2tX/09LSyMzM4ns7EnMmJFNZ6cLu91Be3sLbndrfyyjiXukzZcjrdkN1aQ+6FVS/JPgq8CZDLnd\n7dtfBwSHYxGVlWfgcPyD99/fgtO5i7lzpyT0pchUEMnQGPNqoP8r1SfWEsNYiLWr//smgpmDJqDR\nxB3MRZ6Huv5lME3q/UnwWeBCv4ACXEXGf7s2WyeZmU7Ky8/v39/KymWUlpbT0fEOy5efTH5+/kjf\nZjUOBdNnePJIlzXGvBNaOCqexVpiGAuxePX/kVyAebRxj/Qiz0Nd/zLo6Q3+tcHLgXWB4/Pfrsvl\nYuNGOzk5hT7LFRRMpK6uZsh9VYkhmGbSbVjnYIPd2Neb3uk+AcViYhgLsXb1/5FegHk0cQd7kWfv\n5/suE7djx2Hy8hYN36TeCvgP2RvhNUX7tut0OnE44MSJw2Rk5GC3O7DbHePmRC2RRm9HSjDJcJrX\n/xdj3bfw/wFvespOB/4F+LfwhKbiUd8P7I4dW6mr6yI7205lZem47ouJ1av/GzN0xggUtzGGlpaW\nIffB/4c3lLmJx4+38N57h5g3bwo5OYUkJVnnzwOa1MN0B/qUlBR6e0/w2mu7SE2dSHa2g9zcVLKy\nhKVL4/dELRFHb0dKMH2GB/r+LyKPAzcZY/7stcg7IvIB8GPgT+ELUcWTD3+AhdCvAx9+Y3HmHCtJ\nMNgfSIfDQXJy8rCv8V5vU1MHSUldVFRM5JRTFo/oh9d7KkdZWSr79r3Ku+/WkZJiZ+bM6YBfk3oY\n70C/Y8cumpsLmTVrMo2N0NLSSn39XpYty2X+/I+FvuIoi71pPfEr1FOHBcD+AOX7gXmhh6Pi3Ydf\nzkUUFUX/y5mIZ86h/ECO5DU7duzi739voK0NmppstLQYNm+u4sCBg1x66Uq6uroGPeEINNJ49uwZ\nbNv2Abt376e4uJDubheNjdVce92ygQGOIhH2bbuwsILZs4vp7Oyks7MTp3MWSUn76O7ujsvPQiKO\n3o6kUD8BO4Fvi8h1xpguABFJBb7teU4loFj8cibamXMox2Cw13R2Otm69V2mTCkjLS2N6uoTtLXB\n0aMpZGdbF84+dmw/L7/8Kl1dj+NwTBz0hCPQSOPp0yvo7u5i5843OXTIRVFR5sBE2M0orpNFwG3b\n7XbsdjtpaXbq6vbF7SjnRBy9HUmhfsxuAJ4BDolI38jRk7HO3/wHPqtxYCTNjLH25YzF5BxpwR4D\np9PJkSNHaG7uZPJk6zU9Pd3s37+LgwePcOzYIeANpk/P48SJdpqarESYlVVMV5eTjIxcamp62bix\ngWXLlpCRkU13dydVVbV4n3AEGmmclJRMSckkcnJm8YUrzh+4M2G68e54HeU8XvcrWkK9UPdmEZmO\ndW3SuZ7ix4BHjDHt4QouVo2HkVsj3Ydgmhlj7csZa8k5XIY6diM9Br79f53s3Pk+TU12Fi06g/37\nd7FnTwsiU5gwIReHo4T33qvm2LG9tLRMpbQ0g9radzl+/AQNDfUcPXqQ3Nxstm9/n5SUTOx2yMyE\n3buP9Z9wDDXS+Nrr/BLh37CG44XJeB3lPF73K1pGc6HuduC3YYwl5o2H/qdg9yGYZsax/HKOJJnH\nWnIerZEcu5EeA+/jOmVKLsePp/P229txOpvp6nIgMgVjupk6tZCSkimkptppaKjG6TzIe+9txOnM\nISlpEmCnq+tdjh7NYurUicyZMx+Xq4na2p24XAdwuRb3b9N/KsfJL09i4QN+g1fCVBv0F2vTX8Jl\nvO5XNIT8Cy4iXwS+AkwHTjfGHBCRtcA+Y8xT4QowloyH/qdg9iGUZsZIfzmDSebj7cx5pMduuGPg\nfVyzswuoqTmIy5WOSB5//esmHA47J520kmnTSpg2bSpgvXdlZTNIT9/J00+/jt1+FkVF3RQUuKmt\nNeTlzae7Owe3W8jKKqa1tZHGxu0+8XtP5XCk+7335wIvRO69i9XpL6M1XvcrGkJKhiJyI/Aj4OdY\nN/rtm2TfCNwMjLtkOB76n4Ldh1CaGSP95exLCOnpU3E40gL2T3kbL2fOwRy74Y6B93GtqTnI7t3t\nZGVNY8GC2ezfD21tB8nKauuf7gDQ1HSc48cPkJk5gdzcerq7T2Cz9VBYmMmUKfmAg9bWZlwuJx0d\nbXR3t1NQEODmNkfBMdHv8xCh2mAg4zVZjNf9Gkuh1gy/DlxvjPmTiPy7V/nbWJPxx53x0P8U7D6M\nppkxEl9Op9PJrl11NDUls2/fbpzOLjIy7OTl2X36p7zF8plzMH3PoXz+Bltv33E9ceIotbUtZGVN\nITs7n9bWOsrKSkhLK6Wm5j2mTq2hoGAiJ04cZuvW50lOzmTSpI8wf34u7e2ldHc7KStLweFYwIED\n7bhcLbS1ucnMTGHy5F7Kysp8tx+mCfRqoPEwjiHaQk2G04CtAco7gYzQw4ld3okhPT2Lrq6O/rsB\nxEv/U7DJLdaaGTs6Oti9ex/vv59Md3c+NlsGbnc7KSm1tLX1cu65iweNKZZ+JELpew5n/2ffcX3t\ntV00NCRTUjKD1tY6WlqqmT27gEmTZtDdXUNb2xb27m3i+PFm6uraKCsroa2tjbKyIvbubSY5uYAT\nJ1rIyOhF5DBLlsxk1qxyurtdtLd/wJw5Ez6MK4wT6NWHxsM4hlgR6ru1H1gEHPAr/yQRmmcoIqXA\n/wIXAOnA+8DVxpgtkdiev/T0dKZOzebZZzfgdGYjkoEx7aSnt3DhhQsiUgsK95leKMktlpoZ3W43\n1dU11NcvprT0dOz2XDo7mzh8+DVEAp2bxaZQ+p7DfWIyf34FHR0uDhzYzNGjbeTn5zB7dgHTp1fQ\n3HyCuXOnUFSUjMuVQXHxqdjt7aSlTWTPnoPMnJnO7NnZHDx4kGPHqvnIR0q54IIsoBOnc4fvZ0Rr\ngxE1HsYxxIpQk+HtwK9FJA3r475URFZjTbq/LlzB9RGRXOAN4GXgfKAemMUY30jYZhMgFcjy/NmA\nDiTQFz5EkT7TCza5xVIzY0dHB93dqSQnl2KMA2NsGOMgObmU7u4dY34j3VCMpu85nCcmycnJnHba\nUkD4+9/rmTChnIKCiTQ3n6CxsZp589KprXUyadIi0tNzOHx4FyIZZGfP5OjRnZx22qlkZmYxfXoH\nK1eeQX5+fv/dIQa956CbwMlRhWQ8jGOIJaHOM7xbRFzAf2LV0h4BDgPfMMY8Gsb4+vw7cNAY451o\n/WulEeV0Otm/v5mFC88iIyOXzs5O7HY77e1N7N+/k3nzwvPBi/SZXqjJLVaaGQsKCsnJyaSz8xit\nrZCSAmVlmSQnFw7/4hgwmr7nSJyYzJs3h87OTg4d2k1dXU1/gi0tLaa6+j2Ki3NJTbVTVpbN7t21\nOByFdHR0c/ToAXp6TlBZOa3/PoCDJkHQ2mAEjIdxDLEk6GQoIgJMBtYbYx4WkXQg0xhzLOzRfehC\n4C8i8gfgE0AtcKcx5u4IbtOH9wcvNdW6nBOASPg+eGN5phcryS0Y+fn5lJdnsX9/I1OmlJCUZKe3\nt5P6+kOUl2fFxc1Zw9H3F45j598CYbPBlCnJLFmykKysrP5bHvXFaU2xOMDu3e/R0bGb5GQ3CxcG\nuBuJfyLcBiwcVaijMp4Hloy3ebTRFkrNUIBq4CTgfWOME3CGNaqBpgM3ArcB/wUsBX4pIp3GmAcj\nvG1gbD54eqY3tPT0dJYvP5mnn96N09mLSBbGtFJUdJzly0+Oi/cmVgYlBWqBqK6uJienhkWLFgSM\nMz8/g+nThXnzFrF0aaVvrF/Aah/yFsXaYCIMLImVz9J4EfSnwhjjFpH3gQKsQSxjwQZsNsZ8z/P4\nnyIyH+saqYMmw7Vr15KTk+NTtnr1alavXh10AGPxwdMzveEtXLiApKRkduw4TEvLMbKzUzjppJPi\nat5gtAcljbQFIlCcp59eNDCh+NcGvwA8NCa7MqhEGVgS7c9SrFm3bh3r1q3zKWtubh7Ra2W4G4AG\nfJHIhVg38b3RGLN9uOVHS0RqgBeMMV/2KrsB+I4xZnKA5ZcAVVVVVSxZsiRscfT09LB9+86Inm1u\n2/au50s80y/hZo+rL/FoDRisEYeitQ8NDQ0899w7FBefTmqqvb+8q6uTuro3WbHiZJ8m50Hj3AHM\n91t5DPQNOp1O/vznzdhsFT4nlQ0NdbjdO1mx4tS4/cwMZjx8HyJly5YtVFZWAlQONfsg1F/w32MN\nnPmniHQBPsP4jDHh7rx5A5jjVzaHMR5EMxYjK/VMb2TGw5d+rPehr//MGBNUC0TAOGN4kEwidjeM\nh+9DtIWaDG8OaxTDuwN4Q0S+DfwBOBVrCsf1YxwHENkPXixNZYhl43lgRLgF6j/r7T1BQ8MuIMgm\nf4PVaeFfFkO0u0GFIqhkKCI24BZgJdaEu5eBHxpjIjrByxjztohcAvwE+B7WpP9ITeOICZH+ka+v\nr6e5uZm8vLy4GIXZZywHRsRawg01nkDXc21tFdLSamlubqax0U5Ojn34FogYrg1604ElKhTB/np8\nB7gVeAnoAL4BTACuCXNcAxhj/gz8OdLbGe+cTifPPPM8mzcfoa3NRmamm6VLJ7Jy5QrS0tKiHd6w\nxmJgRKyNRBxNPN7Xcz106ACdnZCc3ENb20GMaWX27GzS0rqYPDl/6PXF2eXUtLtBBSvYb/aXgDXG\nmN8CiMg5wHMicp0xxh326FTYPfPM8zz/fCsTJpxDeXkZjY21PP/8W8BzrFp1abTDG9JYzcP8sCY1\njfT0dLq7XVRVfUC0RiKO5gSgo6OD6uqDNDRM97xvuezd+0/27jUUFKRzxhmnIuJm+/Zq7PYA64uT\n2qA/7W5QwfJv/R/OFOD5vgfGmJewvhql4QxKRUZ9fT2bNx9hwoTTKC2di8ORRWnpXCZMOI3Nm4/Q\n0NAQ7RCH1DcwIjNz4MAIl4uwXI6trybV3JzCnj2NVFXVsnt3A83NKezefSysl3xzOp00NDQMuc4P\nTwBmkp9fTGqqnfz8YvLyZnpqikPH43a7aWx0kpxcQlZWMW63weVKJT19Eb29yaSkpAy+Pv9E+D5x\nkQi9ORwO8vPzNRGqYQVbM0zGah711g2khCec+BBrfUkj1dzcTFubjfLyMp/yvLwyampsNDY2xnT/\n4WgGRoz0mFk1qcOcOJFHRkYhdnsyPT29fPDBUZzO2rCMRAym2XO0IyNtNht5edmcONFOS0sDxhha\nW9sQySErKwPrgqF+6zvVAe/6rSjOkqBSwQo2GQpwv4h0epWlAXeJSHtfgTHmM+EILtYMvIRVJ5Mm\nZfRfwirW5eTkkJnpprGxFodjbn95Y2MtmZlu8vLyohjd8EIZGBFsf5vb7aa+vpHGxloaG1vp7rau\nf2pMC+G6LnwwzZ6hngB4T6OYNauM9PReWlsP0tbWRVLScbKyDGVlBdjtDp/15Rf4nQxdC4zZRQ+V\nip5gk+EDAcqifK2JsdP3I5adPYu2thb27TvMpk272bx5D+ee+5GYv9RTYWEhS5dO9PQRWjXCxsZa\njh17iwsumBjWWmGkas/BDowItr/NZrPR3d3CkSPHKSiYTVZWGa2ttZw4sZfMzJYRxTjUvgfb7xns\nCUCg5A9NZGYmU1paQkqKg4KCLnbtqiYnpxwRGw0NddjfPMaKny3z3RGtDaoEEtQvtzHm6kgFEuu8\nf8QaGtrZt6+HrKzFOByzOXasijffPA4Q81eJWblyBfAcmze/RE2NNZr0ggsmespHL9IjMfsGRkye\nfIKmpqYhp4aEMuDG7XaTmprDxInliHTQ3r4Xux0mTiwnNbV9wDZGuu9dXV2eJk9X0M2ewZwABEr+\nDTUgVYIAACAASURBVA27yMmpJynJ4HTCtGm9TJuWBfRSV/cmX/zSMqDYd0WaCFWCid1qTIzp67vJ\nyUmjtvYIWVlTyM7Op6cng56eXNLTS6iuPhrz9xBLS0tj1apLOffcBhobG8M+zzDSUx/6Eo51bdJu\nz7VJS8PW32az2SgoyAVyycwsITk5hZ6ebtraej3lg3v77a1s3nyCCRMqKC6eSFtbE5s372L37qdI\nSirwNK13cPRoHQ7HbAoLPxx3NlSz50hHRg6V/N3unSxffjLw4RxWV5sLR5bfekaYBOO131ypwWgy\nHKG+vpumpuN0dkJGRirt7e10dDRgt0N+fglNTUfj5lJP+fn5YR8s4/1jnJ6eRUdHOxkZ2cDMsE19\n+Oc/3+WZZ/bgdBYhkocxrezZs4Pe3h4qKxf7LBtKf1taWppXH9tROjvBbofJk3spKysbtF+yqmor\nf/zjZnp6JtPUVENpaSvTp1dQU+Ng69bjnHlmZX+CbGvbwLvvvsbChWcFNSF8JIN/vJN/R0cHXV1d\npKQ4aGqyluk/5gIOgk+EsTYHU6lw0U/vCPX13fz1r/s5dKgep9OF2y309u5n0aJcOjudCX+pp46O\nDlpbe3C5DnH8eGt/IikqyiI9vXfUJwpOp5NNm97l+PGplJScjMORicvVxtGj77Bx4zvMmzd3VP1t\nfa+ZM2cCbW0t/X1s3d0u2ts/YM6cCQFfs2PHLjZvbqSnp4KSkqX09DjZs6earq5tNDZCaupEMjKy\n+6dFzJ9/NjU1G3C5ttHebg/bhPC+5N/cXE9zcye1tS10dkJ39wkmTDhESkqlteAoJtAnyt0gVOLR\nZBiE+fMr2L17D62tO2hoOER+/mSKi4upqzPAa1xyyUkJnQzT0tKorz9ITU0XJSWV5Ofn4nI1sWNH\nFeXlR3E4Pjqq9Tc0NHDwYDuFhXPIzrZqOCkp+fT0zOHgwfdoaGigrMx32kgoVyL58DX7cDqHfk1f\nbbioaC6NjY10d3eTnW3VQg8erKKtDfLzHf2jNgFycgopKZnOsmWz+mt74fjc9CX/J598nePHSygs\nnENycg/NzYdpaxOysgOMeA4iEY7lzaeVGmuaDIPQ3t7OwYNtTJ58KhkZ9bhczfT22sjKSiYz08mM\nGeXRDjEGJGFMOsakYIx4/k0HksK0fjfQQ1dXB93dXaSk2IEe+ubL+QvlSiTBvObDpskSysp62b27\nFoCUlHRaW9txuQ4zffpCn2TYP40hApPBp0+fSmbmP3A6m+jp2Y3dDosWTeb6L3/Kd8E6rAspBmE0\ncx61j1HFOk2GQdi27V127eqgrOwUJk8uoKXlOI2N+5kyJYP8/Ga6u7ujHWJUdXR0UFQ0iczMYo4d\nO0hjo9VMumBBMQ7H6JtJ8/PzKSuzs3XrC0B5/53uoYYlS+xD9oGG8iM8knt9evdLTps2FTjA/v27\nqa09it3+AcuXl+ByCQ0NdWNyweienh5KSqYzd+4ijHGz4rN5JHX7tYuGOFLUe1/T07Po6urAbnfQ\n3t4yaBeB9jGqeKGfxhFyOp0cOtRGfn4xKSk2UlJSKSgoIyXFwaFDWyks7En4M960tDQyM5PIzs5n\n+vRcOjs7sdvttLc34XbXjfr9SU9PZ8qUHN54Yw9udxLJydn09LRisx1h8uTZYXv/g/kB9+6X7Onp\npqurkfb2QzidRygvz2bevAqMgZqasblgdF/C6u7u5DOX+k6X2HHZfqbfXzJw4MwIpaenM3VqNs8+\nuwGnMxuRDIxpJz29hQsvXDBof6r2Map4oMlwhDo6OnC705g2rYj9+60BGQ5HLj097TQ21jBp0pyE\nT4a+A1Zmhr0m5HQ6MSaHWbNO4sCBTtra2snMtDN16klARthG8gb7A97Xx/jnPz/Lzp1u8vOn8dGP\nnktRUQ7btu2nsjKbFStOHdUFo0d6y6309HROeX865f/hmwjvufsNKiuzR/3+2GzC/8/emwfHcZ55\nmk9mHVmVdReqABAncRAgCVKkCEqyZEuWHG3LR9uy1zPj0fb0tb09Pb3R07veuXp6d2YidnZ7xzG9\nYfduj3dnO8Iz0z2x6sPhtkdS25Zs65YsijcJgiDuGwXUfWRlVWZl7h8JgAABSgAJkASZTwQDZDEr\n8/sqC98v3/d7D6t7W2D5jwioCJsU9Lb3GG32ErYYbpGVJ26PpxGXK83c3CDpNGhalt5egRMnjt3t\nId4T7GbrHFVVGR9PYJp99PW14HBI1GoVCoUZxsYGdkQMb2UBN02TcrlMIqHi9Xbh99fhcklEIg2I\nomP5fd0fWhzgwyrW3KzllmEYG98nwH7a1p3ju3/1Ov3dt38PFEVhfDzHsWPP4POtt/zHxwc5fHj9\nZ/Mgdpy32bvYYrhFrls9E9TVddPY2Eo6vYCiGDz+eNeeqE16J9jN1jlWB4Y8LpdvXd6gqmbIZLZW\nKu2juJUF3EqtSAEddHQ8jaZpy4E0k7S1Na9731rhczqdH+mO3azl1ssvv83ExLfp6jqx+r4DLTGO\nPX5k3bjKinUPvuB9bEfuwdrPxu2WkCQJAEHY/LO5mx3n7YAdm+1ii+E2WGv1ZLPg88GxY/E92TB0\ntxeL3Tiv1YFBJp1eoFCI4PVaqRu6vkA0Ku/INba7gK9YkvX1B8lmJ9F1ZTW1YnZ2Cr/fuVysIcs7\n7/ycZLKG0xnA64VaLUUuFyMW29wde73l1i/Q1GQVVvd6D5JIXOOnP51j374WGhtb+cyz0saJmFZS\n/U7eg+1+Nnej47wdsGNzq9jfjm1wPzQM3cuLhVUdpp2ZGSgWLTe1JEFzM7S0tO9ort5WF/Dr1lIT\nTU1Frl2z3udyySwspJiZmaZWG+YHP3iFqSkvoVALR454OHiwg/feG6W7u46ens3dsZu13KpWFTTN\nBbThcHg3CGFZ2T3X462I253uOG8H7NjcKvf26nePshdFcIW9vFisrQ4Ti0UxDBNRFNH1FL29O2dp\nbGcBX2stdXZa75ubG2RhIYfTOY2qKly54qdU6qOl5QlqNQdnz15B00Zwu/eTzVapVMqreYhr3bGb\ntdzSNJVcLs0rr/4avLp+LH/2p6/zhfJDu/rd3K643ckHSDtgx+Z2sMXwAeJOLha75Ybt7e1maOhv\nOHXq3LqAkoMH+3fsGtvpjHGjtdTe3oPfP8/i4iC9vb28+uoQdXUnEASVYLAJh8Oy5MbGTrF/fx35\nfHGdGK51OXq93g0tt5LJSV7+m19aN4a3XoYlNYHX2P1ygLcqbjv5PbjZd8sO2LG5HWwxfIC4E4vF\nbrthh4ZGKBYbeeSRR1brhhaL01y9Orxjlu3GOUx/6Bw2s5Y++ckWvF43xeIwTU0dFAqDVCpZXK4I\nLpdMPi8gSWUUZZ5SKY/H49vU5bi25dY///3fAdYXI3/lx5Vd34fbjLvhHfmo79bdDNix2fvYYvgA\ncScWi910w25m2QKk09KOWrbbncPNrKVkMonfb1AqZYhGQwwMvEu1uo9SqUytNkapFOT4cTeGMUQi\nMbGpy3Gl5dbX/u76a07/T7O8f3yYcsJqC9Xd7b/vywF+1H25GwE7NvcPthg+QOz2YrHbbtg7Ydne\nzhxutJZisdiqm7NWa1q2YgdQlHmi0Sxzc2Wi0X10d0Nbm5MTJ45tTNH5X4F/ccOFTGilmVA+wLlz\nF5mZEZia0llaOrNngqG2y1bvy50O2LG5f7i/fmNsPpLdTorfTbG6E5btTs/huee+gKp+j+9+96c4\nna20tbnw+UKEQk/j9cbQ9SlEsYeRkVlCoYn1lucmVV3W1hUdG5tkZEQgEjm++mCzV4KhtstW78v9\nEPFtc3ewxfABYzcXi90WqzvhBtvpOXg8Hr74xc+iKAG83l5kOcjg4AKC0IYs+0inF/D5gkiS97qF\no3ghdsOJbiiu/aBFTm73vtgiaLNdxLs9AJu7g9fr3fEWQitilcmMkE4nqFYrpNMJMpkRurt3RqyO\nHDlEf3+Qcvk8o6OvUi6fp78/uGNusN2Yg8fjIR4PEAyG8HhkKhWwGhNnkSSQJC9+f9gKCpE/Wgjh\nuqXk92+0lMply1K6n7gT3y2bBxvbMrTZUXZ7z2Z9W6XNexjeLjs9h7UWrSy34HBUWVoawzRT9PTU\nIUle0ukEv/wrT69/44e0WnoQIyft/UCb3cQWQ5sdZbf3bK5HFB4nHt+dfbLdmMP1hXwcSZoimVTo\n7j5IS0snTz8DsL7LxEf1HHwQIyft/UCb3cQWQ5tdYTcWqju9T7aTc1i7kD/11GHGxiaZmSnxuc/7\n1h9YBVxbO+eDainZImizG9hiaLNnuB8qjKws5PUN9Rv/c5sd6G1LycZm57ADaGz2DGv3ydayk/tk\niqKQTqd3NwDlxpSJ77FtIVzLbgRD2dg8aNiWoc2eYTf3ybZSRu62661+Bfj+Da/dhgja2NjsHLYY\n2uwpdmuf7PTpc5w6laK+/hANDfvWBeb09R28/XqrN1qDTkC7rSHb2NjsILYY3gZ2N+07z07vk2ma\nxpkz5/jud0+h660sLl4jHp+np+chIpFuRkYGqVTOc/ly9dbqrV4Dem94zbYGbWzuOWwxvAX2coPc\n+4WdegAZGLjKqVMZKpUeBGE/IyNpzp0b5+rVGY4dO4IoligUCkQij24/gvUjyqnZ2NjcO9gBNLfA\nSq6bKB6ioeFxRPEQZ87kuXx58G4PzWYbrKRqxOMH0XWDqSkFWT5MLPYkmYyHc+emWFiYwDCk7Vd6\nuVEITWwhtLG5h7HFcJtcz3XrJhptwO2WiEYbll1qqfuuDNatckeiMm+TdDrN0lIRl8sLGAiCiiDo\neDwhdN2gVsvidIp4PObWI1gFNhdCGxube5o96dMTBOH3gD8AvmWa5v94J699p3Ld9up+5F5wIa+M\n8ezZUc6eHcLhqFCpSLS1BUmnR1hcTCBJ4xw9+gjhcIyWFomRkS1EsN4ogsYmr9nY2NyT3Bur0zYQ\nBOER4O8DF+7G9Xe7JuReEJMPYzeb++4U589f4qWXLqMoQarVemZnR8nnFWKxFA6Hh2JxilhMolbT\n8HrhxIljhEITN49gtfcGbWz2PPf+6roGQRD8wH8G/ls2tjy9I+x2TcgVMZHldrxeD5pW4cyZWe6E\nmNyuNboX2gopisLrr58nmWyksbGfSMSLovyUq1f/hqtXXycaPUBjYzvJZIzXX3+b55/vIRAI3DyC\n9UYhfAX49J2by170HtjY3IvsKTEE/h3wommaPxME4a6IIexerpuiKFy9miCbdTIzM0mlApIEfj8M\nDS3umpjslDW6F8qlpdNppqYU6uqOIkkRxsevkkx6gW7ABxynUJAIBFxEo+1MT+fWCeDq+NuA6RtO\nfhNrcKdFa697D2xs7kX2zG+OIAh/FzgOnLzbY9mtmpCqqjIyMkU63blsXYUpl7PMzg5SLk9SLj+8\nY2KydoEeGhrZEdfmXmkrVCoVWVq6gK5HGB0dJ5eLUKlECQbbaWt7DFVdwu1eorX1CCMjLzE7O0t3\nd/f1E9xoDXoBZeN1dku09oIr2sZmr7EnxFAQhBbgW8AvmKa55bodX//61wmFQutee/7553n++ed3\nZFw77Z4yDINMRsHpbCQQsMQkEGigUMiQyVzekWvcuECLYoW5uVn273/2tl2b93JboZXo1h/96Cec\nPz/AwsIiHo8fVS0jy1+mWh3B640TDjdRLvuZmxtG10eoVBL88Ifv8+STZY7kDuF8+oZfmQ/ZG9xM\ntN599zK5XI5HH+2/b13RNjZ3ixdeeIEXXnhh3Wu5XG5L790TYgj0A3HgrCAIK8/lDuApQRB+B5DM\n9V1fAfjmN7/JiRMn7uAwbw9RFIlEgqRSJfL59HI39CKaVqKuLvih792qK+7GBXphYZqhoRl8vgXi\n8ebV427VtXmvtRVaEf+f//wqP/vZKSYmAI4RjR5DUcoUCu9gmj9Elp0IQiOqWiGXS7O4mKFSKRCP\nBxkdjfAPf3cTi+tDhPBG0dJ1jVRqkfHxAoODQ8zMlOjra7ovXdE2NneLzYyds2fP0t/f/5Hv3Sti\n+BPgxtXoPwKDwL/ZTAj3Ih6PhwMHmpHlGoXCFJmMtWfY2lqjubl500VuO664zayK+voWIpH9jI9P\n09V1GEmyrnGrrs17ra3Q++9/wLe+9SOuXXMwM+PG4ehGknIcOKAhCB1UKvNUq+/Q2XmMQqFAKvVz\n5ucnMc0EsVgbxx56lm/90aPrT7qFb9uNojU+fpVr1/J4vf0IQhO1WpQzZxLcr65oG5u9xp4QQ9M0\nS8CVta8JglACUqZp3jdlX2RZpre3nmIxT1NTIy6XF00rUypN09tbj9fr3WABbmf/aDOrwuPx0NnZ\nxOnTb7KwMMm+fR074tq82yIIlvh/+9t/yQcf1COKbUAJw3iITGaciYkhPvWpjyNJBsPDl5FlA693\nDhgnmZyio+MYr7/xm/DW+nPOzszSTPO6a2xmka8VLVF0MTIyjcdzFKfTh8/npqGhlVIpsOuuaDvi\n1MZma+wJMbwJ94U1eCPX3YxjKMp1N2Nvbzfnz19aZwE2N8tMTRWIRI5saf9o7QItywGqVRVJ8hKP\nhzh40IPTOUkisbAjrs17YREeGRnhgw8WEIRnCIX6UNXzmKYfXe9iaWmAVGoan0+kuVmmpUWntTVG\nXZ2Xt99W+Ovv/+N15/qX/2KaTOYH/B5fAbZmkUci8Nprr5DN+hkfT+P1RvD5yjzxRCuSJCEIu+eK\ntiNObWy2x579rTBN81N3ewy7wc3cjOfPX9pgAZ46dZZCYYlHH/3EunPcbP9IlmXa24O89NKPUZQg\nguDDNEvIcp4vfvEh+voO3rZrc2URHhiYI5/XCAZdt7Q3diusFeBqtcpPfvIzUikFr9eHqnpxuz0o\nyihOpw9dr5JIfIDTucDJk36+9rVfoLOznfqGev7RDef91/9LgtTCJfbvl4lGo8DNIzp1/SJOp4uR\nkRQDA8MMDc3jdkdxOHQ0bQmQVs97q67NarVKW1szbW2WhbrZ/bIjTm1stseeFcO7yZ2wetae+2YR\nhJXKQaamRkmnE4TD9VSrVSRJolS6+SIrigLgBgLLf0RARRB2xrV54cIlXnzxGooSRxAimGaBa9cG\nqNV0+vsfvq1z34y1VlChoJNMTjE5Oc3YmEKtpqOqE7hc+4Am3O6LlEoDOByX6eoSePrpA3z1q18i\nEolsSJn41i+/xlyjjpkvEYvleeaZh1dd1TeL6HzttR/h97cSDneg61U6Op6kWi3Q1DRGrebH6Wxi\nZiZDKDRFqTS9LVf0h1l7a7EjTm1sto8thtvgbrmebhZBWFfXRDAocfbsTzHNNgQhgGkWkOUlvvSl\n3k33j8bHcxw79gw+X5hKpbIqnuPjgxw+fPNFcisPAIqi8Nprl1haaqex8aHVaNiFhYv87GcXOXz4\n4K4swmsb8xaLCd57r8DwsAqEcTiCZLMvUSoNIUl1iGIatzvBI4/E+cY3fpvm5uZNy6mdP3eJ+EAV\nKV9BkgS6ujrp7bVyDW92P5xOD1NTJU6ebMLnC1Gruamv70BRSui6QGOjwPz8FIuLI3R2ttLfv39b\nruitWnt2xKmNzfaxxXAb3C3X04dFEEqSg0qlhqYVsCpDl4Aqm8XXrl0k3W4JSbJcdh+2d7WdBwCr\nukuJWKyXYNByJ7pcUXS9l6mpK6TTaUt8doiVxrwvvPA2lUo9c3MXuHbtAouLUXK5/WhaHZIk4XJd\nwTAMarUGBMFk3z6Nv/23T24uhAeBQTjOUTo72zl37iIzMyWmpnSWls7Q3V1HZ2f7pvcjm10CDKLR\nRlwuN5IE5XIWrzdCJuOkpaWTSKSOzk6V5577+KrLdStsx9qzI05tbLaPLYZb5G66nm4WQZhIDOBw\nwMmTX8TnC1KplJEkL6VSnomJQfr6bh5As9VFcvsPAAag3/Cavvz6znL+/CW+972zDA0ZeL1upqdn\nuXLlGl7vFxGENkQxiMvVTDC4D9N8n0gEAgGZL3/5OXrPxuDXbzjhDQ8QY2OTjIwIRCLHVz9za+6T\nm96PUmmKtjYZTavgcnkIhSSmpgZxOhuRpCqKkkVRZunv79iWEML2rL17ufiBjc29ii2GW+Ruu542\niyA8fNjN+Hg7fv+KpWddXxDEmwbQbDcsf+UBQJYDqGoJny8IdG/6ABCNRmlrk5mYuITL5cbrtcrJ\npVLrg092gmQyyYsvvsHQkEGhEKBU8qBpkMsZGIYft9uPYZSp1ZxIUheadpbW1gAtLXX8i3/59MYT\n3iCEH/Xw8+yz/cD6ThaPPx5HVf388IevoShxTNNLMjlMufw2R4+24HQqtxylu90HmXut+IGNzb2O\nLYZb5E67nm7co9ssytQ0TZaWTm1rTNtZJFVVpVDQKZdnWFoqrBYOj8cDyHJtU7F95pmHefHFS+Tz\n71EoWNGqa4NPbpcVt+377w/xyiuDaNph/P4ODKMTUaxDEM6Tz08SDMrUamnyeQVdr+J0zqIWB/hP\nf/qv1p/wJgk6H/Xwo2naplG/p0+fA6pAAVE0aGiI43IJfPzj+3niicdu+TPY7oPMvVb8wMbmXscW\nwy1yp1xPH7VHd+Oitt0xbWeR9Hg8JJNTTExUaWzsXy0cPjBwhv37F/B6n9jwnuPHj+J0OpdTK8oE\ng176+rp2zCJZcdtq2n50fT9u9ycQRReSlMM0G/B46igUBlCULMViK+DGNOcplv4/OHXDyT4kU3Wr\nDz83Rv1OTOQ4duzZDW7rTOb2a0PcirVni6CNzdawxXAb3AnX03b7Gd7qmLa+SDowTRnTdGGawvJP\nGas07Eaui233tiySrUarDgzM4XJ1IUlOgkEf5bIXwwhQrc5SV+cgHK5D064iigZebwBdFykU/+m6\n8/yTf/xtvva1xznJzVM9buXhZ2OA0oe7rbeLbe3Z2Owethhug91ejG6ln+HKmFpbU2SzWSKRyJb2\n5pLJJLlc7kOPV1WVeLwFv7+BxcXrtVKPHm3A693oJl3LVj+brUarzs/P89prb/L665NWTiAqXm+V\nTGaQxcUIhcJlJMmgVssRiRgUCmky2X+44XoPH/89frX5c0xM5DYEGN3Idh807pQr3RZBG5udxxbD\nW2C36oKv7Wfo87Xj8XjQ9QqzsxM37We4UUymPzT3UVEUXnzxh7zxxgiZTJVoVOKpp7p47rkv4PF4\n1h3r8Xjw+x0Eg1GamjwUi0UCgQC6XsYwEjuyIG9mCb/77ii5XI59++pZXFzk4sVB3ntvhvFxnXK5\nQCj0EwKBY4yNZZibG6dSiaHrGg6HA1GcIxA4yPzCN9Zd5/OHFkj6R3EYVUKhOOXy4kdaatt9+LGj\nOG1s9i62GG6D3U66NwyDZLJAJpMnnZ5E08DlAtPMY5qFTd+z3dSHv/zLv+Y73zlDudyIIDQwMpLl\n0qV3qFSq/MqvrG99Issy+/cHefHF18hmQxiGjCgqhMO5TZP6t8uNlrCi6ORyU6RSCb7znStoGuTz\nKSqVFkKhXh566CtMTJzj0qUf4/fPk05HKBaLwCSiGMflaqZY+g5cW3+dA93voBYmoTBIc3OJRGKK\njg7XR45/ret2q5GwdhSnjc3exBbDbbDbSfeiKKJpJebmCoRCB/B66ygW0+Rys/j9pQ3Hr099CKGq\nFXy+MDdLfUgmk/zVX71JOn2MurpPIklRKpU0qdQb/MVfvM4v/uKzGxZ9XddZWJhmfn6RWs2Hw1Fi\n374Kut71oXPZyh7gWks4EjmEqk5w9aqL4WGTXK6DaLQbRRnE5epkaSnI6OhlRFGgVouxsJCmUtER\nhDiQwulspFj639ad/x3XRZ5v/LcUkiF8vihNTTG6up7g6tUFOjoCNx3X7Tz02Pt6NjZ7E1sMt8h2\nc+5uBcMwcDplPB4/yWSSSiWFJJkEAn6cTnnD8SupD6qaXrasNGTZRUtLYNM9vbGxMaamdFyuLrLZ\nFJXKDJLkxeXqYmrqLGNjY+vEUFEU3nprEIfjEY4dO4DT6UDXa6RSw7z55hUeeujITd22AwOz5PNV\ngkHppoW6DcMgk1GWk9IDjI+PUyi0Uir5cLs1/P4jFApVPB4v0M7k5GlEcQ7TPIrTCbruAwr8a/3T\n/J762XXn7tj/74FXaW0K4HA0EQ63Eo/X0dxcTzhcBxQ2fD4rAj48PMrly9Vbeui5FWtyr3IvdCax\nsdkpbDHcItvNubsVRFFE16tUKgKBQIhw2IlpGpTLGXS9uuF4j8dDIjHF2bNJBKEFQfBgmirDwxfo\n71c2pD4IgkCpVCKXu0y16sMwBEQR3O4i4XARVVVJp9Ori9va8mp+fx2apuH1uhAE8abl1c6fv8RL\nL10mlXJSrTpxu0tcu5ZA13VOnlwfvSmKIpFIkFSqxOLiBKlUCcOIYJolvN4goigDAbLZaRyORkql\nFJDC7Y7i9ZpUq0Uq1V/a8Ln4fX9Ci+QlFgvxhS88RU/P5zGMGtWqSiAQxuPxkki8t3rPcrkc589f\nYmamRLlsMjg4QnPzo+zfH8PhcGyp0tCD1DLpQZqrzYOD/c3dImtz7gKB/QCoqsjAwNhNc+62i2EY\nCIKDSmWYmZkRqlUHbneNeNxEFDdPZUgkZllaqqOu7jCyHKNcTpFMXmRhIbXh2IaGBkqladLpegTh\nAFY7oQrF4jimOcbAwALj4/rq4hYOB6jVNBYWZqlWS6t7mG53FpfrxpJrlqXw6quneO89jVyuAV13\n43RWCYWSeDyn6OhoQxAETNNc/XngQDOyXGN4eJR8fha3uwOnM0kuVyGfd1AszgJXEAQZQahhGBrl\n8hhBZCrVX113fbfrhwhCiZBcIBSSaGiI4vUKDA+foVAQUZQaPp8Dv79GLKaiaRrnz1/i1VfPcvWq\nJZSRiISixJiedhAMTtLd3Ql8dKWhB6ll0oM0V5sHB1sMt0G1qnHt2hlKpQEMw4Moqvh8Kk1NjTty\nflEUWVycYmmplVDoSWS5hUJhioWFN4jFpjccn06nKZd9RCJOksk3qVREJMkgGnWjqr4Nlls2QMs3\nNAAAIABJREFUm0VRNKCEaZYBL1AGChSLIuVyCx0dD68ubn19ZSSpyODgBRoaPkkg0EihsMDk5AWO\nHy9scAOm02lef32AiYke3G43DodMpQLptEmh8HNCoSDptMbSUhqfz8nBg51UKgnOn08wM+Mhn09R\nKHyPfL5ErWZiRcKUgASm+RKm6UQUoWb8tjXsNTgdv4ns+QThcIS6ugjB4CIOh5MLF65x6dIVfL4+\notGDpNNXKZUGeOQRP5lMnkxGQ1GacDg8zM/nGRycxjCSNDXFmZqC1lars8eHpUc8SC2THqS52jxY\n2GK4RVRVZWZmnmIxjCiewOOpR9MWKRbPMjMzuyNu0mKxSKnkpr6+F7fbQTY7RLWqIooxxseHOH36\nLJ/61NPrXFHZbBpN66Gt7QgOh0StViGTuUwm8/MN5x8eHkZVHYhiD4bxEOAHCkAFw5imUCjhdkur\ni9vo6Hmi0TDxeAXTvEyxOI6upwmFMkSj4Q3nT6fTTEwkqNU+jiw/jtMZRtezZLMpxsYyjIyILC7W\nUyg0Ui7Pc+HCZRYXR0gkZnC5elCUOUolB7WaAHQBUSCD1XdRQBSz1Ix/v+6aPvkbGMYQ8XCFtrYi\nkqTgdE4SCOwjGu0nm11CEMLk8yKqehHDiOB2n0RR8szOxikUVFKpQVyukwQCj+PxPMb8/EvMzIyg\naX6OHWujVDK2nGy/lvuxZdKDNFebBwvxbg9gr1AsFpmZKdPQ8CRHjjxOV1c7R448TkPDk8zMWJGD\nt4u1J+cnGm3A5XLidIYIhQ5QV3cYt7uVM2eyXL58vayXx+PB5XJQrXoxzSCmGQCCVKteXC7HhkVJ\nVVUMw4thtGC5SEuAC2jHMNxomrJ6rN8fJp/XiESaePrpx+jsdOFwDGKas4RCdSws1Dh16gy6ft1d\nWqlUMAwBhyOMZXWK1GoudF1GEGRGR7Ok0xFCoZO43SeYnQ0xNRXAME5QV/cVRPEYDsfK2PoQhENA\nG/AJTL6xiRB+HcNIEgweJBJ5hLq6Q0ARt9vA6WygWKxw8WKSYjGGrkfJZqvU1XXT1fUUyaSGrruJ\nRA6RSOQwzTpkuQFZrqOu7mHi8TCFwkUWF9/HMAbp7w9uKdl+Lfdjy6QHaa42Dxa2ZbhFNE1DkvzU\nam6qVavmZKVSRhDcSJIfVVVv+xqhUIjW1ghLS0mWloqYZgS3W0cUKwQCrTQ3P8TIyNyqK0oURbq6\n2kinFxkdfZ9azYvDUSYSydLV1b7h/PF4HKgAs1jPQV5AAeYwzSrh8PWn/WIxSzDoAlx4vS2AQbHo\npKPjEILgoly+wpUrVUKh6/tE4XCYeDxKNptHUS5gGA5MU0UUMzidPsbHCwSDfjStQKFQQxB8GEYU\naECSImiaSq22D1CBJgShEdM0MPnCunm0eV+n5B0iHjiJaUJjYx2ZzM8wzRqRCMzNZQkGPUCcSiWL\n211PtVqlXHaRzztwOhM4HCJutwNw4nbLVKsFSqU8tZqKYajU17fT21vgM585RFNTk51sv8yDNFeb\nBwtbDLeIJVQhMpkspumlULCCSUKhLJFIaLlE2O0Ri8Xo72/gz/7sDMViBI8nvBxAMsUjj3TS2NhO\nIjG36oqyntIFfD4Br9dDrebA4fAgisKmT+mSJCEIVvNfaAJiQBIYA9KUywrVamXN4tYEwLvvXuba\ntSQez2HAQbmcpLe3g2jUt26fqLm5mePHG/jRj85QLscwDAkoo2kj1NdL6LpJPj+NprWSyeTwekuI\noomuK2haAV1XcDiOA1eASWrGJ4HedXPwen4Xr+cEkXALDocTlyuMIKh4PDJ+fxQwUJQa2WwNUTRw\nu2WKxWFqtSCVSppcboZqdZFjx3x0d+/j3LlxgkEIh0tkMqdR1Sz793vo6IjR3NzxkUK4woOUbP8g\nzdXmwcEWwy0Si8X42Mea+eEPR4lGI/h8cUqlJdLpUT72seYdyyk7cKCTlpYxFhdnURQDj0ekvt6k\nqWnfpq4oUYR4PEgg0IJpOhBFg3y+jCgqG86dz+dxOLzUagKCUMI0Hcs/wemUKJUukkhI6xY3TdM4\nffosV68O4nAU8flEDh1qpK3tUwAb9omCQYNarYCutwBBADStwPz8u6hqF4YxgiiKOJ0q9fUxarVF\nqlUviYSJYWQwzRTgwuRX1o39L4R3+A35m0SCPkxzimCwiXI5R7lcweOpEQo1US73US5P4fG4MIwa\n09ML1GoRnM4MhjGGyzVGPp8EJHp6fpH6+iANDYsoygxTU1kEIcy+fWHa2trx+8v09tbj9Xq3lE/3\nICXbP0hztXlwsMVwGzz33BeAlzl16nUWF0X8foPPfW7f8uu3j6IojI/nOHz4cSqVa4yP56mr20dj\nYz2jo5MIQp7HH4+vLjyqqhKJNJPJZLl69WUUxcTnE+jt3Uc02rIhmEGSJCQpjmnGgSVMcxFBEIB6\nPJ4GPvGJPj72sYfWLW6XLw+iaa309tbjdvfi8bhQlCkmJ69RV1eP12stjufPX+LcuXHeeCMBVPH7\npxCEGLo+Qzo9i2k2I4qNQCOapqNp7zIzM0pDw3Hc7jCFwjzVaoY39Gd4nN9a97lEwr9NNKrSITfQ\n0dGCqgqI4jTj48O4XA5keT9+fyt+fxRBmFsWNQ/T0yMYhpOGhjqqVR8ORwsuVxWncx6/fwEoc+iQ\ni2q1H10XKRYlMpky585d4Mtf7qS391HOn7+0rXy6B0kYHqS52tz/2GK4DTweD1/72lf59KfTZDKZ\nLXeI2CqqqjI0NM7ISJhS6SAeT43FxSyp1FWam+f50pc+T2dn+2pi/Eru4+ioiGG04XCY1Goio6Np\nQqE8Xu+T687f0dFBOKyzuGjgdB5YzvUDXb9AOKzT09OzoQLNyEiKhoY+XK4SQ0MlXK5mBKGNa9fO\n0Nm5xOOPxxkbm+TMmTz5fCOZTD2SdASXK4ogzDI3l8Y0DwExDMOPIAwjim4MI0StJuN2NxKL7Sed\nTpNM/ZsNn0nPgW/y5METPPbYAUqlD2hoaCSdLjE/n8DprFFXtw9BMEgm53G5nHR3t5LNLuF0mjQ0\ntJPPq9TXd+F0FgiFNPz+ENHoIJ/97BHC4TDf+c4kxWIHfX0P4XRKFApZUqkrzM3NceHCZQYGbq0S\njY2Nzd7CFsNbIBqN7kqpLcMwGB6eYmwshmkG0TRwOsNomg7MAPDKK2dXrZSWFpmZmTlGR71IUhCX\nK4im5alUUjQ2bqxYE4lE6OmJUyy+S7U6S60WQhRzyPIkPT3xDXNaG0YvSX6KxTGWlkbQNFDVOQ4f\njtHZ2c4rr5wlEjkE5KnVDNzuw7jdTsbG/pxCIQA0Am5gark3ogfLhdpLsahxOODjg7H/et21+0/8\nDg0ND/GF3i6OHu1hcvIUfn8YQfDR1CTzxBNdTE8X8fkeRpZDvPvuRdzubtxuJ4FAlkjEw/z8BxhG\nBperSDgcpbm5g3DYWN0LTKVSqxV2gkFr7rLsw+USGRu7QjA4STz+pJ1PZ2PzAGCL4T2EqqrMz2cY\nG5ujWnVjmjKCoOB2LyII03zwQZbOzkdWrZS33z7FtWsz+HyfwjTblwtpK/h8OvPzlzYk3YuiSGdn\nO4lEBUWJoet+nE4Xslyls1PaMB4rdUPn8uULFApuKhUQBAgEFI4e7eDRR/spl8urglmr1QgGXczM\nvIWqpikU5oAerFSJw1gCGMMK2KkCQZaSf8eK4VnD//GHb/PppQ46OqI4HG4mJn7E3JxGMHiMbDaM\naRaYm1uit1dEUWaRJC/799dz6dIwgqDQ19dAY2MLDz00iq67OXLkGXy+CJpWplSaXt0LtDCAG6vp\n6Oh6BVUV8PvtfDobmwcBWwzvIcrlMpOTcxQK+xHFCoLgwDQrqGqOycksstyyzkqZn28hkagRjUZx\nOoNUqwZudwDT3Ec+f3ZD7qNhGPh8MU6c6KNUclIslggEfMjyEXy+gQ3jkWUZQchx8eIM9fUfW96f\nnGV29go9PVbXB9M0V/POAoEwspyjWEyjqmHgEJYIpoEUsA/YD0wBFUz+zrrrPfelUzQ1jfH5ns8S\njao0N8P4eJbBwQz5fJzOTpn29iNUqyoLCxeR5TE++Uk3MzODeL01OjpmgBqyLGEYBb761aOYJkxM\nzKEocxuiHqPRKG1tMhMTl3C53Hi9YcrlLKnUJTo6AsRi/l1v1GtjY3NvYIvhPUQ6nSaTSQFOHI5O\nTDOAKBao1QYpFqsYxvr6pJFIHMMQSCTmaGw8RCAQQ1GSJJNLNDVVNyzYoigSDgdYWtJQFINKRcDh\n0JAknUgkuGE8iqIAYY4eraNYXKBQWMDng6NH9wNWcXJZlnE687z55vep1UKMjU2g648ArUACOLD8\n8xpW/qADk9/bcK3Pf+5H1EfKHD9+EE2rkEzOoqqtGEYXhYKKLB8jk8nj94/S3HwUXe9lZuYKra1N\nHDsWXbbUrPqwN0Y49vVtHvUoyzLPPPMwL754iXz+PQoFH6ZZIhbL85nPfAyn02nn09nYPCDYYngP\nkc/nqdUkDKNIpfIB1u2pYZoVwAoSgeuBG5pWJhRyommg6+Nks1M4HDV8vip1dcENC7bVyb7MxYvv\nkEgE0DQ3LleVhoYCBw5szKdTVZVq1cHRo49jmgaVilVsQBBEEon3mJmZ4Y//+P/ltddmmJ+fpVQC\nVfUDESz3o4nlA3VjFROtYPI7667R2/bfIXgVfqHzWQ4f7iEajZNIDFCrVSmX/YyPJ1lYyOD356ir\nC5BILBKPl7FcmwawMarxxnl8WNTj8eNHcTqdDAzMkc+XCQa99PV1rcmZs/PpbGweBGwxvIcIBoOI\nooCuJ7BExAQEoIbbXaVWWySdTqxaKbncGH19DUxPF1lYGKBSAa9XpL5eo7u7ZdNrvPPOW0xOxjDN\nNgQhhK7nmZyc5t13R4BfW3fs2tJbshxYfX3FVfgHf/BveemlBNWq1RFC1/1YFW4krGR5CUuw8pis\nb7wL8Mmn/glPdMZ45JFWQqEohlFAEAp0dtb46U9VpqbmUFUftZqPublx8vkgDQ05mpsnyefH2L9f\nvu1Apus5c92bWo92Pp2NzYOBLYb3ENbe3yKG0YRh9AIeoIIoXkWSSjzySIRq9bqV8uijEVKpOi5e\nvEwuJ1CtulFVDZfLwOF4aMPCPTIywpUrGapVP7XaOUwTBEHE4ahw5UqGsbEx+vr6Vo+XZZn29iAv\nvfRjFCWIIPioVrM4nYtEoyVeeOEdKpUMlks0COSwAmNGlscuAClM/sG6cfy72J8z+LVhfvn4E5w8\n2b2a3J/P5xkfn+LKlTneeusiqZRBKHQEt/sQ+fwCS0uj6PoAvb0isZjBM888vGPi9GFCZ4ugjc39\njy2Gt8Budfgul8sYRgDDKAITgAwoGEYR0wzS2dlOV1fXqpVimiZ/9Ef/ifl5L05nL7IcpFYrMD9/\nlcHBwQ3nn56eJpnMUanIQAhLsBQ0LcvSUoGpqal1YgggigLgRlVdLC1lyWSSpFLnGRp6m0rFAJ4A\nWrDclnFgEave6Rgm//OGMfy9X/pnfPnLH+PLj/83GIZBtVoln88TjUYZGhphYKBKudyMooioqogo\n+qira6S+3ksiMYpppujtFXjyyYc5cuSQ3W39LnI/fPb3wxxsdgZbDLfBbnf4zuVyKEoZ6Ab6ud7C\n6DSKMkE+n1/3Szs8PMz4eBFRfBhd76JaDSKKeURRZWLiHLOzs3R3d6+e3zAMKpUU8DHgaazozgXg\nNSqVgeVqNNdRFIXh4RShUCfT0wuMj6fRdTcDA0tUKvuxLMJnsQJj3sHqMFEHpDcVwr//m/+IYwce\nIpl084d/+B0SCRWnM0ok4qC/vx4Ik8vFGB6eIJNxIYohTHOaXG6MffviBAI9mOY8zzxzjN7eXi5f\nHrS7rd8F7odO9/fDHGx2Fvuub4OVDt+y3IEsy2hamTNnptmpiiTZbJZKxY1lYQlAfvlnnEpFIptd\n3zYnk8mQSuk4HEcJhQ4hCCamKVIsOkgmP9hwfD6fxxKrg1i9DEuAb/nf75PL5dYdr6oqIyNzJBJ+\npqehXG5haSmxbFn2YHW9KGAFxwSBJT5OB2/fECQje7+BLL/Gb0S+TFfXZ3n//Z/w9tseNK2X+voY\nuZzO0NA5fL538HieIp3OUKtpy9eJ4/GoRCIHUJRZ/P4AkUjE7rZ+F7kfPvv7YQ42O4sthltEURSu\nXk2QywWZmclQqWSQJAgEXAwNLe5IRZJCoYBhGFhBJ2EsyzANGBhGjUKhsO54j8eDIFiJ56apY91O\nHVVNIMvicvTodTKZDJaAubBaOPm43tPQQyqVWne8YRhkMnl0XWR+fonZ2SLZ7DzgwMobLCz/CWJF\niv7TDXMS+C18YhuS5Kax8SCi6ODcuXHgk0SjPZhmhmCwntlZjatX3yYev4Ys9yBJ9VSrAxQKUQzD\nj6ZJ+HzT9PXFKZfLDAzMEYkc39PVYfaii+5+6HR/P8zBZuexxXCLrFhJqVSESKSNSMRPuVxkenoc\nRdmZTvemaS7/LYxlHUpYwhO+4f8totEodXU6udwCongQl2sfmjaPpl2grk7f0FaqsbERS/xWOsn7\ngZX9yRL79u1bd7woikQiMun0BOPjP0dRDi2P623gSSx36yHAjcmj694r8MLyecs4HPOYpsDExAL5\nfIZkUica7UGWoyhKAbdbIhjsIp/XcLvdBAL9NDUdZGbmAsXiCKZ5iWi0htO5iMvVyKuvXuXKlQkO\nHYoRCtXhcFhf471SHWYvu+juh07398McbHaee/s37x5ixUpyuXyrdSxdrijF4iKZTH5HrhGPx3G5\nHGhaEZjHsrjyQBGXy7HcnPc6Ho+Hjo4OFCWEqmbQ9RJeb5VIJERHR8eGX+jW1lZcLgNNmwdGub4n\nOY/LZdDa2rrh/K2tDbzxxvdQlMtAAMuKdGDta/Zg8okN8xD4CtAMZHE4BHy+GF5vE1evGsjyApXK\nPJqWRNO8OJ3gdLpQ1QVE0UU83ofbDeBh37428vkapjlFKLRAINBNd/dTuN0+xsa8DAwkcLkGOXDA\ncmvtleowe9lFtzbdZq9W5rkf5mCz89hiuEWuW0kLFAqR1dJdur5ANCrvyDUaGxuRJNC0DFbJMhdW\nlKblkg0Gg6sdK1Y63R840EU262Z+fpFKBSQJ9u2T6enp2nB+QRCIRhtIJKawRNZKwocsXm+QoaFh\njh8/vmqdyLLM+Ph5Tp+ewhLmMFbu4z5AwGR9VwyRv4WJgiS5CQRqFIsZJKmfYPBhdL3E3JwHv18H\nyiwtvYzP109LSyv5/Bi53M+pq3MRDEaJxaI4HA5qtTjJpBOX6yodHZ00Nz+zunj19nZw9uwU166N\n0tjYiqZV9kR1mL3uorsfOt3fD3Ow2XlsMdwiHo+HAwfamZmBYnGQRKKKKOrU1zvp6GjfsV8gp1PD\nSk8IYu3vlYEEgqDw5ptXuHq1tOpW6+xsx+XSyGQyFItBNM2FpmlkMnmczsCGMUUiERyOHA5HD7Va\nHyupG4JwDlFUmJwUuXz5unWSTCb5L//lPXK5KFbgTSegYPKvNoxb4I+BIuGwm1AohNcrYRhBTLNx\nOWrUS6Ggk04XCQRM4vFLiGIKVW2iVqtw+LCKJB0jmZyjWpUQBBnTVAgEFjh0qBVZrltXNLujox1N\nq3DlyhVmZt4kHg/uieow94OL7n7odH8/zMFmZ9kTYigIwj8HvoIV9lgG3gX+mWma1+7UGGRZpre3\nnlwuja4LFAoCxSLk89N0dIRxuVy3fY1cLoemgSBoy/uDElbOXgFVlVHVdhoaHl91q6nqVQYHr5LJ\ntBKJHMfjqUNV02Qyb26aZ2gYBuWytpzHmMDKNcxjmkV03cTjaeDcuXFE0SSfz/Pd736f8+eTmOZ+\nrK9KCpP/ft05/wde4f8UBnGIF4jHEzz//H/FwkKa6ek009MJNM1JMHiYaLSDajVHNvs6klTkd3/3\nlykWnaRSBerqAjz8cAeVSoW/+ZsRstlFDENGFBXCYZ3PfKaf+fnKOreWplWQZYETJ1r4zGf6iUaj\n97yIwP3horsfOt3fD3Ow2Vn2hBhiRWv8X8BprDH/78ArgiAcMk2z/KHv3EGOHDnE0NAPOHduCbd7\nH9Gol3C4i1xOWGdRbZeVqMJMJoOue4GTWJGeOaAeK0hFx+Px4XZLq4voBx/8lPl5gXC4gUzmNAsL\nCl6vTCTSwNLSInNzc3R1XXeXTk1NUa2GMc0DWMEz5vLPbiqVMd566xUmJgaoVGSy2TKLiylMUwI8\nmPzhhnELPA4E8ftMYrESX/3qLxONBnE46nC5kpw+fRWnU8Hh8KDrFTweJ263Rrms0NHRRl9fH+Vy\nGdM0EQQBl8uFJEmcOzdOOm25nx9+uI8jRw5x+fIgZ86MoOsayWSC8fFp0ukEBw96WFpK09DQsGF8\n9yL3k4vufhCQ+2EONjvDnhBD0zQ/v/bfgiD8GpYvsR8rtPGOUK1WcTjqeOqpfny+IJLkRZK8pNOJ\nW9rvuTGq8Nq1UTStimlexApu8QKzQA5ddyMIxup7rUW0RjK5yMLCWQqFPIahIYoeMhkfbW05VFVF\nURTS6TSCIJDL5VDVCpaBfQSoYQXDXETTXuSNN36Kqh5F19vQtKGVK91ECD+NwyFSX7/A4cMf5+mn\nv0JbWycvv/wyb731l2QyDlTVC7yHpk1Rq/Xj93vwevMIQohKpYLT6WRqanZ1/i6XjiDkqNVETHOl\n0LfFilvr1VdfYWjIJBLZz8mTvcTjIc6cGWcvBJ+sYLvobGzuPfaEGG7CSiRH+k5edGW/JxQKsXah\nvtX9nhujCicmNAzj/8FyXx7CcpNWgIuY5jlcrut5g8XiSjf3CyST7Vj7eQ1YLtUhTHOGyckZvv/9\nd5iYyOF0Osjnh6nVcsAQVhBM3fLffwJMkcnEsAqEF4Epfo3f4j/wq+vGLPA08GlgP7GYhKYlWVw0\n+cEPXsI0NQYHr1Eu70cUH8Kyakto2mWy2R9RX/9FdN1JLOYjHA5z5sx5Bgaqq/O/cOEt3nxzkkik\ngcbGAyQSBSYmBqjVdPr7H6anp4uBgTkaGrpoaGhFkqyGxKLo2BPBJyvYLjobm3uPPSeGglUz7FvA\n26ZpXrmT13Y4HCwsjHHmTAJB8OHzuWhvbyIcjm57v2ezqMJcbglLAGexxHYlKX4KqDI8fIne3oOr\nbjWfr0o2a2IJ4WGs6FAVMEmlRvmTP/kxitKJ19uN06kzNTWEldB/BWvPcAJrTzKPFbBTxvpK1GPy\n5xvGLPB/Ywn1PKJoUK12YhjHWFxMoutRDGMaRYkCXwYMRDEBdGIYcXT9ZarVBWCYtrYA7703w+Dg\nME1NR9i/vw5NqzI2lqRaPUalIhEOH0bXqywsXORnP7vI4cMHUVUVw5BobGzF7ZZWx7WXgk/WYoug\njc29w54TQ+DbWCv/xz/qwK9//evLVtx1nn/+eZ5//vlbuvDw8CijowWGhio4HCKyrDE8fIqeHpGv\nfvXktha2zaIKR0YuYd2SfcDDWJbVEpYwXmFk5EdMTcVxOKocOdLI6dNZdD2Ite+XwxI1AD+m6WNo\nyOChhz6L319HuZzAEtUfY7lH38JyxfYBw1gVaTSs3ME/WDdWgf+wfFx4+bgsfn8vTuejVKsVisUE\nshxBUdJYYp6iVvMBPgShiFWlJgO8yLFj/Xzxi7+FJPnRdSezsxAMDhIO17GwUCEWOwQsYpoGwWAU\nXe9lauoK6XSaSCSypqVUiGq1iiRJlEp7J/jExsZm93jhhRd44YUX1r12Y5nJm7GnxFAQhD8GPg88\naZrm/Ecd/81vfpMTJ07syLUVReGVV04zMqKTSoGiLAEVZDlNfb2Lrq6/ta3zbRZVWC4rWI1wH1n+\no2NZfSLwBg5HgVqtQKkkMDKSZmpqBqtlko61v7hSUUYDRByOenQ9z9TUBLncHAsLS1iW3etYlWTK\nwEUs67AOk+9vGKfAFSzxLGKJbRlop1w2AReCkEdVpzCMdgyjH0u465fHPIbT+Ri6fg6nc4Fnn/0c\nn/jEb9DY2IaqqkSjdZRKHubmFvB6/YBBtZrG5wOXa8Xyu97EV5Zl9u8P8uKLr6EocQQhgGkWkOUl\nvvSlXlsMbWwecDYzds6ePUt/f/9HvnfPiOGyED4HfNI0zak7ff10Os0bb1xkaekxwuGPEY+HUNUl\ncrkPuHbtNPl8nkAg8NEnWmazqEJJ8mAFtOSxXJgreYZ5QCSVcjIyEkEQAszMFBgYyGIJXwx4HKvD\nfAZL3HQ0LcP8fArDCJBOt5PJ6MAHQAeWGHZhtV+awuT3141P4FtY+5WHl893DTiPZYEG0fUa5fI0\nPl8GUXRSq9Vjmi4st24Jy0KcRtPeAM7jcGSYnTU5d26KAwd0OjraaW4OMjCQIZ3O0d0tEAjoTE+/\nSSx2ElGEQiFBKnXphia+AtYDQAFLJEtAlRsq1dnY2Nhsiz0hhoIgfBt4HvgSUBIEYSWOPmeapnon\nxpBOp0kkyphmC6oqUyppOBxhRLGdROItMpkMzc3N2zrnjVGFPl8Wa89vFishfkUMZ4EKtVorgcDD\neL1WXdRs9kdY+4phrGo1LP+MAi7K5SVU1U0qNcvMzCC1WgrLBVvECpR5ApO/t2FcAhew0jteBL6P\n5aqNYll7JxHFFKY5hiim0fUYDkcIXS8CWSyBmsESqRGsAJ0MTmecbLbG1atpFhZK1Go1urs7yeeT\nzM5Oo6oeDh924PUu4fVOs7iYwzRLxGL51Sa+iqIwMZHj2LFn8fmCVCplJMlLqZRnYmKQvr69tWdo\nY2Nz77AnxBD4B1j+t9dveP3XgT+9EwMQBIFarUqplMXhAEmKUqmkKRaz+HzVWzqn0+mkp6eLeNyy\nemZnO/jzP38Fy/qKYQnXPFZ6ZZpwuGNdXdRwuBVL/Axgevnv2vK/BSIREa93icnJH2BZgg1YIjsJ\naJj8+rrxfJm3+QEvYLlmwQrWTSy/91mghiCEEYQChjGGLIOivI9punC7ZQzDia5Xl8dcpIsFAAAc\npElEQVQRAXqBNhyOJKL4Lvv3P0Im4ySRqHH69BVCIYlQSOPjH3+MAwe6cLmeYHR0goGBOfL5MsGg\nl76+rtWUg7X7rG63hCR5l++NuCcDaGxsbO4d9oQYmqYp3u0xhEIhQiE31eoihjFIqSQgiuByLRIK\nuTd0iPgoNutccObMWSy3pQNL90WsZwAH0EIiMcDS0iiyHMHniyIITqx0yxQr3SMsF+IgUKCpqcZr\nr/1HrNu8H2v/bRqT/7xhPAK/j+U+zWNZcwksC68Rq0D3BeAZnM5marXLgIJpLuFy6bjdQTweNx5P\nG+PjCUxTBnKIYiMQolYrYpoe6uo6iMVExsevMjFxnlxO57HHetZ1a7BSDro3TTm4H6q32NjY3Jvs\nCTG8F/D7/Rw61EouN4qiZDEMCcMoIssZDh9u3XQh/rB+dZt1LhgfV5b/t57rQSsBLDGsMDw8wMsv\nfxdJMvH5yszPz2JZYO8DV4H25ffMAxKvv/5DVPUkK/VNIY65iSEt8NdY1uIAVsRpCMuC/DzWfmE3\nsACkcDhcmOY4xv/f3p1Ht3mddx7/PtjBBVwlURJNSRQl0bI2k9psx5YlxYnTmSRNp6e24knS4yTT\nTJvl2NNm4jaddNrOnCwn9iRpPZO6p+NxJlbGzmLHSSeuvDRuZGlka7UkS6YWSlxEihsIkgCI7c4f\nF7S4SDZISQBhPJ9zeCS8fAE87wGBH+5973tvqpxYbC4+XyMuVzHDw8eJRt9ApA8oQuQm3O61JJNh\nXK4IxqRIpeLU1a3H5Sqmt7eV7dvX0tDQMKWeK11ykO3ZW/JxvUGl1MxoGGbI5/NRXz+f1tYQHR0u\nwmEnIj5cLgceT2rC3KTvtl7dlVYuqKhYCvwCOIC9lGEhttW3D7vA720EgyuIx08TDndSUuLEzibT\niG0Rkr6PEzhEJFKODTE/O7mfe9k84ZiEP8CGqQMblm9hg3gp9rpDsMEo2K7PKKnUWVKpwzid1dTW\n3kE8XszwcBEi5bjdv8HpTJBK1VNUtIbKygWEQn2EwwHASSIRJxTqZ3DwPMuXz532OVbIzuwt+bze\noFJqZvSdPQ0OhxuRUjyeany+eXi9RYicp6vrKMeOnWDz5g3A5Vt9r756lMHBQTZubL7iygULFizC\nnu9bBDRhg+kCdmBKN6Wl9bhcSdzuAF7vdhKJ/djBNYuxl1WMhdlFLl1m0YTh76cci/BP2KCrwA52\n6Uw/5zJgbbqOFmwLtRM4CBTh9dolpebP/302bryL48dP4HYXIbKRSOQ45eUpwuEwIm8Sj/fj8QyR\nSvXg9xcRiXQyNBRmzpwetm1bM6PWVjZmb8nn9QaVUjOjYZihaDTK0FCCSMRJcfF8vN5qjInhcs0B\nqnjttbdYvnwpPp9vQqsvkYjT13eRt97q5eDB85w5009jYw0eT3LKua+BgQ6gDliHnU2mD3secDVw\nhlQqgsPhQcRFZWUjfX2tiPRijKT3F+wlGW1ACC+1RCcFofAosBt7Uf8INmzbsK3Djdgu0SrsAB4v\n8H+xXbVB3O4oDQ1raGvz4nRWcfLkWSIRL04nxGKdGBNizhzo7e0nlXLicsXw+ZIUFfWzdGkJq1ZB\ndXWCm2666apbcter6zLf1xtUSs2MhmGGUqkU/f1BAoFG5sy5kUQihtvto7Ozj/b2Dl5/PUJJyW7q\n6ysYHk6yYIFt9bW0vMGePUcZGvIwPFyM0xmktTXIihU+hocnnvvq72/FhlIYO6I0hW2ZhYAEo6ND\nFBcvJZFI0t19mmi0E7toRyf2XN9e7CUQ5zGcmnIMwmPYlmAQO0Anhr00I4HtXm3ABl8ce+mFB3tZ\nRQdu93ICgWJWrlxDPB4kGGxjeLgCj6eSRALi8Q7mznWxeXMzL7/cTjgcIRAox+WKUlKS4J57bud3\nfmfrrD//9l5Yb1ApNX0ahhkaW+n+6NE3CAbB660hFDpNOHyeioqFVFevxO+v5/jxUwwPnyMQWEVR\nUSkHD+7n/Hkf4XCAeNyHz1fMwEAXPt8A27Ytoa3tTc6fH8XpjFFensCO4DwGbMWO5OzGXq/Xyuho\nMyMj7fT2/jMDAwexXaLl2O7MXmxLrgTDrgm1C/uwL/UC4Cg2XEuxrb+l2Gsb38IGsWADuATbckxS\nWlpDZeUy6upK+NCH3k843M3Ro+3EYlHc7koikSAuVwc1NfNxOCpZudJHcbHg93sIBBYyd+4KSkuT\nsz4IQUesKlWoNAwz5PP5KC314vMl6e5uIRTqIxrtQSRFKhXhhhuqqKmpw+PxMjzcRnf3MZzOCo4f\nP0NHx2LC4VR68Eyc4uI4yWQ7O3Z8kEgkQnt7FxcvDnLiRDs2iMLYgOvGnveLYVd/eJ6Wlh9hQ2s+\ntgU3NitdEHOZ1ayEh4FtXFoBYxA7e8s67CCZkvTjRLGrYa3HXlc4gp2q7RzV1XMpK3OxYUM9Ho+H\nhoY7gCN0dJygpGSEqqoRysp8FBW5iUQ62bbtQ8ybN4dIZJjS0nJ8Pj/d3XvyolX1XlpvUCmVOQ3D\naUgkkgwOltDXByMjF4jFkng8pZSU9NHdHeLUqTMsXFjDnDm1LFni4fDho5w9e5yBgVqgFoejnKGh\nYRyOPmKx0zz77C94440Uvb2D9PZGaGsLYltvYAfGXMR2YcKlwTRrsQNmqrGXTLwJ9EwJwlv4R/bS\nhn2Jf53e2oq9kL4BO1imDXtt4WrsbDdD2POJScCPSAclJWXU1UVxu/uJRh28/vpJurrOMW/eAm6/\n/Ubmzl1AaWkFIyMhBgdfw+VyEwoNsXdvD0NDEUpL/dTU+Fi4MJk3QaLrDSpVeDQMMxSNRjl4sIX2\n9nmkUgswJkIi0YUxHuJxL8bUcPLkCKHQUerqnGzc2Izf7yESCZFMevF4luJyBUgmQ0Qipzh37hw/\n/akPv38jFy5ECIcricWc2FbhcuwsMDHswJgT2DlFb8ReRjGAHVyzEMOPp9QqfBx77nAlNjzBhmYS\nOzjGjz0fOIrtig1gw3YJcBqX6wQVFTfg9S6ivPwgt956J1VVSwkGY4RCfUQiZ/H7HdTWfoqysmqG\nh4OEwx1s3tzI4cNHeOaZpwmFKkil/DgcUQKBfj772aa8CUNdb1CpwqNhmKGenh6OHesgmVwKhEkm\nQzidQiJxnnPnukkmE4i4aWnZx2232csGgsEgiYQTY4KEw6+TSvkQCSNymlgsxpkzLmpqnAwOQjy+\njEszyBzHBlUptiV3CtsKjKX3cQFrMXxySp3CeeyKF7/Bdn92Y1ubUcYG19iu0hbsucMW7KCaFFCK\n1zuHykrD0qV3ITLE8PBR6us30NCwhtHRCKOjEYaGVtHZ+RtisaN0d3vfbjnV1y/i+99/ivb2YcLh\nMoxxIpIkFBrm1Vff5Hd/d/Z3k46X7yGokwYolTkNwwz19PQQCg0QjbaRTK4ikViBwzGCyMtEo2c4\nfvxpmpo2EggIS5YsAuxiwLFYjHj8HGOXSRjTjjEhoJFIZCPd3UV0d/cTi/0S+3LEsSM8F6Zvj2Av\nogcbbCUM8hABvBPqE/ZgV6z/QPq+o9hu1T7s7DIebKCewrY8b+DS5NtngE7c7gTFxSPU1S2nutrF\nwEALFRVuKirmA+D1+vF6/fh8xaRSi7jzzmVvf9D6/X5aWlrYvfstwuE78HiacTjKSKUGCYf3s3v3\nK3R0dFx2xhl1bemkAUpNn74zMuR2u0mlIB4vwe2uxgZUEU5nAyLnqaoqo6GhjuJiz9tLORUVFRGL\ntWHPya3DtsR6sINfuojHYWhoHrGYwbbWStKPW4/t0nQCt2LDKwh0YPjjKbUJf4ft4oxhR6P2pPf3\nYFuEDuwMMjHsaNHa9P/HlkAqBlKUlg7i9Z7H4RjA7Q6yZo0DkRXE45EJzzc2srKysnJCi6Orq4uL\nF5PAKoqKVuN0ekkmR4nFRrl48WW6u7s1DLNAJw1Qavo0DDNUWVlJcXEJQ0Pz8XhWYIyTaHSUVCpM\ncXEAEQ/B4FnWrq19OyA6Ojqw5+KqsZcuCHbwymLgBPF4GKfzPDaU6rAB6MGO5hxbucK28mpYzIVJ\n5weFH2ODrwc77VoQ28V6PL3HVuwcoy8Ad2EH5QS5NNVaOXYQzUXgLJWV81i3rp6tW++iuDhAItFH\nSUkXw8Nt9Pd7pzGy0smlyzQcXGrZqutNJw1QamY0DDPk9/tZvXoZkcgQo6MtGFOMyxXB6+2jrMyD\n19vDpk1NE0Yc9vf3I1KNMRuxobMAO0BmETCYXhbqDDY0lmIDcBAbbmMz0JzGsA0bbJcI/wk7ndog\nl84PCjb8DHA79lygE6hEJIXLBfF4Kv0IReOeN4Hf38rWrStYtqya0tIwfn+YhoYqGhubOXGiJaOR\nlTU1Ncyd66Cn5wjRaClOZwXJ5AAiR5gzx8G8efOm3EddWzppgFIzo2GYocrKSm65ZSnhcJTu7mHA\nj8/nRSTGnDlF3HfflrfnJh2zePFiPJ4I8fhFbAuxiWTyJPYcXoSysgWMjASJRLqwLahtwN8BZ7Fd\npb50EF4ifBTbinRhW3rnsC27SmzX5wpst6qXoqJyEok3SKXaqa2dj9sdorOzlWi0iFSqHGPseUWP\n5wxr1nj59Ke3snJlI/F4fMKgi0xHVi5cuJAtWxp56aXTxGJJjCnD7R6kuLiVLVsaZzQxt5oenTRA\nqZnRMMxQUVER27evZ3j4EGfOdNHff4FUapRAYIiPfWwDt912y5T7NDU1sWyZm5Mn95BI1JFKjU2K\nfQSfL4rfL3i9YWKxLqLRdhyO95FK2VliDN+e8njC/dgL4Xuw5wB7cbl6KClZjDE1hELdGNMGGMrK\nFjBvXhxjBmhsrGfDhhupqirh4sV6fvGLQ3R0vEQi4aCkJM7KleU8+OB9bNq0YcpzjslkRGJRURH3\n3fdhYrFdnDvXTzQaxOdLsWhRFffdd5d+EGeBThqg1MxoGE7DunWrcblcHDvWSW/vED4frFu3iObm\nmy87Sq+6upovfenjfOtbP+PChU6i0db0Ukdh6uoqqKvrYenSBXR2Cq+8coDh4T5GR90Ynp/wOIv5\nY87RClxE5AZ8vkp8vj7uvns93/jGX/Pkk0+za9cpent9DA6ew+G4QHW1n6qqYbZsaeT++z+B0+nE\n7/fjdru5++6D7N59lGBwmNraGjZtarxmF5SvX38zPp+PgwfP0tc3RFVVKTffvEQvWM8inTRAqekT\nY0yua7jmRKQJ2L9//36ampqu+eNHIplfjB2NRnn22V/yq18d5tSpVhwOHw0NN7Bp002sWDGXpqa1\nJBIJfvKTn/OZz35qyv2djiYCAS+NjYu56aZaysvnU17u5/bbV3PbbZvfDuGOjg66urqoqakBePv/\nV+qanM4xzMT1fnz17vQ1UAoOHDhAc3MzQLMx5sCV9tMwzJL+/n4GBgbS53T8Uz+gZOp9XnrxJQKB\nABUVFVRUVOD3+/XDTSmlpiHTMNRu0iyprKyksrJy6i8uE4Kkv59smzR4BnQAhFJKXQ+OXBdQ0N4h\nCJVSSmWPhmEu7GdqEBo0CJVSKke0mzTbtDWolFKzjoZhNl2uNaiUUirnNAyzQVuDSik1q+k5w+tt\nchCG0CBUSqlZRluGM3Do0CFaW1tpaGhg1apVl99JW4NKKZU3NAyn4cKFCzz44J+yd+8Io6NFeL1h\nNm8u5nvf+xbV1dWXdpwchJuAvdmsVCml1HRoN+k0PPjgn/LiixUkk/cRCHyOZPITvPhiBV/4wp/Y\nHYTLD5LRIFRKqVlNW4YZOnToEHv2DBKPL2d09Dz9/edwOgWHYw579rRqt6hSSuUxDcMMtba20tMT\nJJFwY8xKjJmLyEX+jRlhZ/ChiTtrCCqlVF7RMMyQ1+slFhslkajD6bwZ8BJPrJ66owahUkrlHT1n\nmCG324397hAhleoikSye8PsXX3hRg1AppfKUhmGGIpEILpcHOE3KrJzwO7/vLmKxWG4KU0opddW0\nmzRDNTU1JJNngEEcHCHFMzj4bQyduJP9zJ07N9clKqWUmiFtGWYoEAgQjweBRRjcCO/H4AUWEY8P\nUFZWlusSlVJKzZCGYYYee+wxoAzYAxwHBoCj6dtlPP7447krTiml1FXJqzAUkT8SkbMiEhGRvSKy\nIVvP/corrwBngRpsKApQmb59ll27dmWrFKWUUtdY3oShiNwDfBv4GnAzcBh4XkSq3/GO18hrr70G\nVAGdQAwoBUbTt6vYt29fNspQSil1HeRNGAIPAN83xjxhjDkBfA4IA/dnr4RR4EZgM3A7sDF9ezR7\nJSillLrm8mI0qYi4gWbgv45tM8YYEXkBuCV7lczHhuBvA4uBVuAZoA1oyV4ZSimlrql8aRlWA06g\ne9L2buxJuyypBe4GVmO7SVenb9dmrwSllFLXXF60DGfqgQcemHLJw44dO9ixY8cMH9EFuCdtG5uZ\nRimlVC7t3LmTnTt3Ttg2ODiY0X3z5VO8F0gC8yZtnwd0XelOjzzyCE1NTdewjAvAvwAl2AZpV/r2\nhWv4HEoppWbico2dAwcO0Nzc/K73zYswNMbERWQ/sB34OYCISPr2d7NUAyJO4GVgEJvD3cAB4DjG\n6MSkSimVr/IiDNMeBh5Ph+I+7OjSIuDx7JWQAnYBHdhrDQexF+CnsleCUkqpay5vwtAY81T6msK/\nxDbLDgEfNMb0ZLEGAGyjdOI2pZRS+StvwhDAGPMo8OgsqCPXJSillLqG8uXSCqWUUuq60TBUSilV\n8DQMlVJKFTwNQ6WUUgVPw1AppVTB0zBUSilV8DQMlVJKFTwNQ6WUUgVPw1AppVTB0zBUSilV8DQM\nlVJKFTwNQ6WUUgVPw1AppVTB0zCcoZ07d+a6hKuW78eQ7/VD/h9DvtcP+X8M+V4/zI5j0DCcodnw\n4l2tfD+GfK8f8v8Y8r1+yP9jyPf6YXYcg4ahUkqpgqdhqJRSquBpGCqllCp4rlwXcJ34AN58883r\n9gSDg4McOHDguj1+NuT7MeR7/ZD/x5Dv9UP+H0O+1w/X9xjG5YDvnfYTY8x1KSCXROTjwA9zXYdS\nSqlZ4z5jzJNX+uV7NQyrgA8CrUA0t9UopZTKIR+wGHjeGNN3pZ3ek2GolFJKTYcOoFFKKVXwNAyV\nUkoVPA1DpZRSBU/DUCmlVMHTMJwBEfkjETkrIhER2SsiG3JdUyZE5CER2SciIRHpFpGficjyXNd1\nNUTkKyKSEpGHc11LpkRkgYj8QER6RSQsIodFpCnXdWVKRBwi8lciciZd/ykR+Wqu67oSEbldRH4u\nIh3pv5WPXGafvxSRzvTx7BKRhlzUeiXvdAwi4hKRb4jIEREZTu/zv0Rkfi5rHi+T12Dcvv8jvc8X\ns1mjhuE0icg9wLeBrwE3A4eB50WkOqeFZeZ24HvAJuD9gBv4JxHx57SqGUp/Cfl32NcgL4hIObAb\nGMVe/nMj8B+AgVzWNU1fAf4A+EOgEfgy8GUR+XxOq7qyYuAQtt4pw+dF5D8Cn8f+LW0ERrDvaU82\ni3wX73QMRcA64D9jP5M+BqwAns1mge/iHV+DMSLyMeznU0eW6rrEGKM/0/gB9gLfGXdbgHbgy7mu\nbQbHUg2kgPflupYZ1F4CnAS2AS8DD+e6pgzr/jrw61zXcZXH8Bzw2KRtPwaeyHVtGdSeAj4yaVsn\n8MC42wEgAvxeruvN9Bgus896IAnU5rreTOsHFgLnsV8QzwJfzGZd2jKcBhFxA83Ai2PbjH0VXwBu\nyVVdV6Ec+y2tP9eFzMDfAs8ZY17KdSHT9GHgdRF5Kt1VfUBEPpProqbpVWC7iCwDEJG1wG3AP+a0\nqhkQkSVADRPf0yHg/5Gf7+kxY+/tYK4LyYSICPAE8E1jzPWbR/MdvFfnJr1eqgEn0D1peze2WyJv\npP/4/hvwG2PM8VzXMx0ici+2W2h9rmuZgXrg32O72v8LtlvuuyIyaoz5QU4ry9zXsa2nEyKSxJ5u\n+TNjzI9yW9aM1GBD43Lv6Zrsl3P1RMSLfY2eNMYM57qeDH0FiBlj/iZXBWgYFq5HgZXYb/R5Q0Rq\nsSH+fmNMPNf1zIAD2GeM+fP07cMisgr4HJAvYXgP8HHgXuA49ovJd0SkM48C/T1JRFzA09iA/8Mc\nl5MREWkGvog935kz2k06Pb3Yfvh5k7bPA7qyX87MiMjfAL8F3GmMuZDreqapGZgDHBCRuIjEgS3A\nl0Qklm7xzmYXgMndQG8CdTmoZaa+CXzdGPO0MeaYMeaHwCPAQzmuaya6sOf98/o9DROC8AbgA3nU\nKnwf9j3dNu49vQh4WETOZKsIDcNpSLdE9gPbx7alP3y3Y8+jzHrpIPwosNUYcz7X9czAC8BqbGtk\nbfrndeB/A2vT53Bns91M7VJfAZzLQS0zVYT9Ujheijz8PDHGnMWG3vj3dAA7ojEv3tMwIQjrge3G\nmHwanfwEsIZL7+e12EFN38SOuM4K7SadvoeBx0VkP7APeAD74fB4LovKhIg8CuwAPgKMiMjYt+FB\nY0xerO5hjBnBds29TURGgL5cnXifpkeA3SLyEPAU9kP3M8Bnc1rV9DwHfFVE2oFjQBP2ffD3Oa3q\nCkSkGGjAtgAB6tODfvqNMW3Ybvevisgp7Eo3f4UdIT5rLk14p2PA9jb8BPsF8V8D7nHv7f7ZcDoh\ng9dgYNL+caDLGNOStSJzPcw2H3+wffGt2OHXe4D1ua4pw7pT2G/0k38+mevarvK4XiJPLq1I1/tb\nwBEgjA2T+3Nd0zTrL8Z+KTyLvSavBXuNmyvXtV2h3i1X+Nv/h3H7/AW2NRIGngcacl13pseA7VKc\n/Lux23fkuvZMX4NJ+58hy5dW6BJOSimlCl7e9fErpZRS15qGoVJKqYKnYaiUUqrgaRgqpZQqeBqG\nSimlCp6GoVJKqYKnYaiUUqrgaRgqpZQqeBqGSimlCp6GoVLqXYnIyyLycK7rUOp60TBUapYRkc0i\nkhCR56Z5v/8pIj+9XnUp9V6mYajU7PNp4LvAHSKSl6utK5VvNAyVmkXSS93cA/x34JfA70/6/UoR\neU5EBkUkJCK/FpElIvI14FPAR0UkJSJJEblDRLakbwfGPcba9La69O1KEXlSRNpFZEREjojIvVk7\naKVmAQ1DpWaXe4A3jV3H7YfYViIAIrIAeAW7dNidwM3AY9h1Sb+FXR/xV9hV2udzaXHayy1NM36b\nD7tA8oeAm4DvA0+IyPprdVBKzXa6uK9Ss8v9wA/S//8VEBCRO4wxrwCfB4LADmPM2Erzp8fuKCIR\nwGOM6Rm37V2f0BjTiV2fcMzfisjdwO9hQ1Kp9zxtGSo1S4jICmAj8COAdOA9xaXW4VrgX8YF4bV6\nXoeI/Hm6e7RPRIaADwB11/J5lJrNtGWo1OzxacAJXJjUohsVkS9gu0enK5X+d/wDuift82XgC8CX\ngKPY1eu/A3hm8HxK5SUNQ6VmARFxAp8AHgR2Tfr1M8C9wBHgkyLivELrMIYN0/F6sEE4HxhMb7t5\n0j63As8aY3amaxFgOXBsZkejVP7RblKlZocPA+XAPxhjjo//AX6KbTV+DygD/o+INItIg4j8WxFZ\nln6MVmCNiCwXkSoRcQGngDbgL9L7/yts4I7XAtwlIreIyI3YATTzrvcBKzWbaBgqNTvcD+wyxgxd\n5nc/AdYDC4GtQDHwz9jBLZ8B4un9HgNOprdfBG41xiSwrcpG4DDwJ8CfTXr8vwYOYAfsvARcAH42\naZ/LjUhV6j1DjNG/caWUUoVNW4ZKKaUKnoahUkqpgqdhqJRSquBpGCqllCp4GoZKKaUKnoahUkqp\ngqdhqJRSquBpGCqllCp4GoZKKaUKnoahUkqpgqdhqJRSquD9fyA0tzOvCGGKAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "ax = predictionsPD.plot(kind='scatter', figsize = (5,5), x='label', y='prediction', color='blue', alpha = 0.25, label='Actual vs. predicted');\n", + "fit = np.polyfit(predictionsPD['label'], predictionsPD['prediction'], deg=1)\n", + "ax.set_title('Actual vs. Predicted Tip Amounts ($)')\n", + "ax.set_xlabel(\"Actual\"); ax.set_ylabel(\"Predicted\");\n", + "ax.plot(predictionsPD['label'], fit[0] * predictionsPD['label'] + fit[1], color='magenta')\n", + "plt.axis([-1, 15, -1, 15])\n", + "plt.show(ax)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load a saved pipeline model and evaluate it on test data set" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ROC on test data = 0.9841004305669758" + ] + } + ], + "source": [ + "val savedModel = PipelineModel.load(logRegDirfilename)\n", + "val predictions = savedModel.transform(testData)\n", + "val evaluator = new BinaryClassificationEvaluator().setLabelCol(\"tipped\").setRawPredictionCol(\"probability\").setMetricName(\"areaUnderROC\")\n", + "val ROC = evaluator.evaluate(predictions)\n", + "\n", + "println(\"ROC on test data = \" + ROC)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hyper-parameter tuning: Train a random forest model using cross-validation" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "//## DEFINE RANDOM FOREST MODELS\n", + "val regFormula = new RFormula().setFormula(\"tip_amount ~ pickup_hour + weekday + passenger_count + trip_time_in_secs + trip_distance + fare_amount + vendorVec + paymentVec + rateVec + TrafficTimeBinsVec\").setFeaturesCol(\"features\").setLabelCol(\"label\")\n", + "val featureIndexer = new VectorIndexer().setInputCol(\"features\").setOutputCol(\"indexedFeatures\").setMaxCategories(32)\n", + "val randForest = new RandomForestRegressor().setLabelCol(\"label\").setFeaturesCol(\"indexedFeatures\").setNumTrees(20).setSeed(1234).setMaxDepth(6).setMaxBins(100).setImpurity(\"variance\");\n", + "\n", + "\n", + "//## DEFINE MODELING PIPELINE, INCLUDING FORMULA, FEATURE TRANSFORMATIONS, AND ESTIMATOR\n", + "val pipeline = new Pipeline().setStages(Array(regFormula, featureIndexer, randForest))\n", + "\n", + "//## DEFINE PARAMETER GRID FOR RANDOM FOREST\n", + "val paramGrid = new ParamGridBuilder().addGrid(randForest.numTrees, Array(10, 25, 50)).addGrid(randForest.numTrees, Array(3, 5, 7)).build()\n", + "\n", + "//## DEFINE CROSS VALIDATION\n", + "val RegEval = new RegressionEvaluator().setMetricName(\"rmse\")\n", + "val cv = new CrossValidator().setEstimator(pipeline).setEvaluator(RegEval).setEstimatorParamMaps(paramGrid).setNumFolds(3)\n", + "\n", + "//## TRAIN MODEL USING CV\n", + "val cvModel = cv.fit(trainData)\n", + "\n", + "//## PREDICT AND EVALUATE TEST DATA SET\n", + "val predictions = cvModel.transform(testData)\n", + "val evaluator = new RegressionEvaluator().setLabelCol(\"label\").setPredictionCol(\"prediction\").setMetricName( \"r2\")\n", + "val r2 = evaluator.evaluate(predictions)\n", + "println(\"R-sqr on test data = \" + r2)\n", + "\n", + "//## SAVE THE BEST MODEL\n", + "val datestamp = Calendar.getInstance().getTime().toString.replaceAll(\" \", \".\").replaceAll(\":\", \"_\");\n", + "val CVDirfilename = modelDir.concat(\"CV_RandomForestRegressionModel_\").concat(datestamp)\n", + "cvModel.save(CVDirfilename);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load independent validation and evaluate performance of saved pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "encodedFinalValid: org.apache.spark.sql.DataFrame = [vendor_id: string, rate_code: int ... 19 more fields]" + ] + } + ], + "source": [ + "// Read IN TAXI TRAIN DATA\n", + "val taxi_valid_df = sqlContext.read.format(\"com.databricks.spark.csv\").option(\"header\", \"True\").option(\"inferSchema\", \"True\").load(taxi_valid_file)\n", + "\n", + "// CLEAN DATA BY DROPPING OR FILTERING SOME FIELDS\n", + "val taxi_df_valid_cleaned = (taxi_valid_df.drop(taxi_valid_df.col(\"medallion\"))\n", + " .drop(taxi_valid_df.col(\"hack_license\")).drop(taxi_valid_df.col(\"store_and_fwd_flag\"))\n", + " .drop(taxi_valid_df.col(\"pickup_datetime\")).drop(taxi_valid_df.col(\"dropoff_datetime\"))\n", + " .drop(taxi_valid_df.col(\"pickup_longitude\")).drop(taxi_valid_df.col(\"pickup_latitude\"))\n", + " .drop(taxi_valid_df.col(\"dropoff_longitude\")).drop(taxi_valid_df.col(\"dropoff_latitude\"))\n", + " .drop(taxi_valid_df.col(\"surcharge\")).drop(taxi_valid_df.col(\"mta_tax\"))\n", + " .drop(taxi_valid_df.col(\"direct_distance\")).drop(taxi_valid_df.col(\"tolls_amount\"))\n", + " .drop(taxi_valid_df.col(\"total_amount\")).drop(taxi_valid_df.col(\"tip_class\"))\n", + " .filter(\"passenger_count > 0 and passenger_count < 8 AND payment_type in ('CSH', 'CRD') AND tip_amount >= 0 AND tip_amount < 30 AND fare_amount >= 1 AND fare_amount < 150 AND trip_distance > 0 AND trip_distance < 100 AND trip_time_in_secs > 30 AND trip_time_in_secs < 7200\"));\n", + "\n", + "//## REGISTER DATA-FRAME AS A TEMP-TABLE IN SQL-CONTEXT\n", + "taxi_df_valid_cleaned.createOrReplaceTempView(\"taxi_valid\")\n", + "\n", + "/* CREATE FOUR BUCKETS FOR TRAFFIC TIMES */\n", + "val sqlStatement = \"\"\"\n", + " SELECT *,\n", + " CASE\n", + " WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN \"Night\" \n", + " WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN \"AMRush\" \n", + " WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN \"Afternoon\"\n", + " WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN \"PMRush\"\n", + " END as TrafficTimeBins\n", + " FROM taxi_valid \n", + "\"\"\"\n", + "val taxi_df_valid_with_newFeatures = sqlContext.sql(sqlStatement)\n", + "val encodedFinalValid = featTransformPipeline.fit(taxi_df_train_with_newFeatures).transform(taxi_df_valid_with_newFeatures);" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "R-sqr on test data = 0.7792023043885935" + ] + } + ], + "source": [ + "val savedModel = CrossValidatorModel.load(CVDirfilename)\n", + "val predictions = savedModel.transform(testData)\n", + "val r2 = evaluator.evaluate(predictions)\n", + "println(\"R-sqr on test data = \" + r2)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Spark", + "language": "", + "name": "sparkkernel" + }, + "language_info": { + "codemirror_mode": "text/x-scala", + "mimetype": "text/x-scala", + "name": "scala", + "pygments_lexer": "scala" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} \ No newline at end of file diff --git a/Misc/Spark/pySpark/Readme.md b/Misc/Spark/pySpark/Readme.md index 11c248c2..11dddc06 100644 --- a/Misc/Spark/pySpark/Readme.md +++ b/Misc/Spark/pySpark/Readme.md @@ -1 +1,95 @@ -This folder contains pySpark notebooks for published data science walk-throughs in Spark using HDInsight cluters. +# pySpark Jupyter notebooks for data science on HDInsight Spark 1.6 and Spark 2.0 clusters +
+ +## Description: +The Spark 1.6 and Spark 2.0 folders in this directory contain pySpark notebooks that show how to use HDInsight Spark to complete common data science tasks on these respective cluster versions of Spark. + +For documentation that walks you through these tasks, see the following topics: + + +>**Overview:**
+[https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-overview](https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-overview) + +>**Exploration, and modeling**:
+[https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-data-exploration-modeling](https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-data-exploration-modeling)

+[https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-advanced-data-exploration-modeling](https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-advanced-data-exploration-modeling) + +>**Model operationalization and consumption for scoring:**
+[https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-model-consumption](https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-model-consumption) + +
+> The descriptions above are for code that runs on Spark 1.6. However, we now have notebooks for Spark 2.0 as well that can be run on the Jupyter notebook servers of Azure Spark HDInsight clusters. Links to these two sets of notebooks are provided below. +> +
+ +------------------------------------------------------------------------------------------------------------ +## Spark 1.6 Notebooks (to be run in the pyspark kernel of Jupyter notebook server): ## + +NOTE: Tested on Spark 1.6.4 HDInsight clusters. + +### Wrangling, exploration and modeling + +> **[pySpark-machine-learning-data-science-spark-data-exploration-modeling.ipynb](https://github.com/Azure/Azure-MachineLearning-DataScience/blob/master/Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-data-exploration-modeling.ipynb)** + +Provides information on how to perform data exploration, modeling, and scoring with several different algorithms. +
+ + +> **[pySpark-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb](https://github.com/Azure/Azure-MachineLearning-DataScience/blob/master/Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb)** + +Adds model development using hyperparameter tuning and cross-validation to the topics covered in the first notebook +
+ +### Operationalization of a model and model consumption for scoring + +> **[pySpark-machine-learning-data-science-spark-model-consumption.ipynb](https://github.com/Azure/Azure-MachineLearning-DataScience/blob/master/Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-model-consumption.ipynb)** + +Shows how to operationalize a saved model using python on HDInsight clusters. + +
+ +-------------------------------------------------------------------------------------------------------------------- +## Spark 2.0 Notebooks (to be run in the pySpark3 kernel of Jupyter notebook server): + +
+NOTES: + +1. Currently tested on Spark 2.0.2 HDInsight clusters +2. In addition to the NYC Taxi trip and fare dataset which we used for the Spark 1.6 notebooks, for Spark 2.0 we've used the Airline On-time departure dataset (below). This dataset shows: (i) how to integrate weather features in the model, and (ii) how to deal with large number of categorical features in modeling (e.g. the distinct number of airports). +3. We provide a python file on how to consume ML models trained in Spark 2.0.2. +
+
+ +### Wrangling, exploration and modeling + +> **[Spark2.0-pySpark3_NYC_Taxi_Tip_Regression.ipynb](https://github.com/Azure/Azure-MachineLearning-DataScience/blob/master/Misc/Spark/pySpark/Spark2.0/Spark2.0_pySpark3_NYC_Taxi_Tip_Regression.ipynb)** + +This file shows how to perform data wrangling (Spark SQL and dataframe operations), exploration, modeling and scoring using the NYC Taxi trip and fare data-set (see above links). + +> **[Spark2.0-pySpark3_Airline_Departure_Delay_Classification.ipynb](https://github.com/Azure/Azure-MachineLearning-DataScience/blob/master/Misc/Spark/pySpark/Spark2.0/Spark2.0_pySpark3_Airline_Departure_Delay_Classification.ipynb)** + +This file shows how to perform data wrangling (Spark SQL and dataframe operations), exploration, modeling and scoring using the well-known Airline On-time departure dataset from 2011 and 2012. We integrated the airline dataset with the airport weather data (e.g. windspeed, temperature, altitude etc.) prior to modeling, so these weather features can be included in the model. + +See the following links for information about airline on-time departure dataset and weather dataset: + +- Airline on-time departure data: [http://www.transtats.bts.gov/ONTIME/](http://www.transtats.bts.gov/ONTIME/) + + +- Airport weather data: [https://www.ncdc.noaa.gov/](https://www.ncdc.noaa.gov/) +
+
+ +> **[Spark2.0-pySpark3-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb](https://github.com/Azure/Azure-MachineLearning-DataScience/blob/master/Misc/Spark/pySpark/Spark2.0/Spark2.0-pySpark3-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb)** + +The Spark 2.0 notebooks on the NYC taxi and airline flight delay data-sets can take 10 minutes or more to run (depending on the size of your HDI cluster). We have created this notebook which shows many aspects of the data exploration, visualization and ML model training in a notebook that takes less time to run with a down-sampled NYC data set, where the taxi and fare files have been pre-joined. This notebook takes a much shorter time to finish (typically 2-3 minutes), and may be a good starting point for quickly exploring the code we have provided for Spark 2.0. + + +### Operationalization of a model and model consumption for scoring + +Please see [the description above for Spark 1.6](https://github.com/Azure/Azure-MachineLearning-DataScience/blob/master/Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-model-consumption.ipynb). +
+The Python code file provided in the notebook for Spark 1.6 needs to be replaced with [this file](https://github.com/Azure/Azure-MachineLearning-DataScience/blob/master/Misc/Spark/Python/Spark2.0_ConsumeRFCV_NYCReg.py), to use it with Spark 2.0. +
+ + +-------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Misc/Spark/pySpark/pySpark-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb b/Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb similarity index 100% rename from Misc/Spark/pySpark/pySpark-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb rename to Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb diff --git a/Misc/Spark/pySpark/pySpark-machine-learning-data-science-spark-data-exploration-modeling.ipynb b/Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-data-exploration-modeling.ipynb similarity index 100% rename from Misc/Spark/pySpark/pySpark-machine-learning-data-science-spark-data-exploration-modeling.ipynb rename to Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-data-exploration-modeling.ipynb diff --git a/Misc/Spark/pySpark/pySpark-machine-learning-data-science-spark-model-consumption.ipynb b/Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-model-consumption.ipynb similarity index 100% rename from Misc/Spark/pySpark/pySpark-machine-learning-data-science-spark-model-consumption.ipynb rename to Misc/Spark/pySpark/Spark1.6/pySpark-machine-learning-data-science-spark-model-consumption.ipynb diff --git a/Misc/Spark/pySpark/Spark2.0/Spark2.0-pySpark3-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb b/Misc/Spark/pySpark/Spark2.0/Spark2.0-pySpark3-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb new file mode 100644 index 00000000..fe71eedf --- /dev/null +++ b/Misc/Spark/pySpark/Spark2.0/Spark2.0-pySpark3-machine-learning-data-science-spark-advanced-data-exploration-modeling.ipynb @@ -0,0 +1,800 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exploration and Modeling of 2013 NYC Taxi Trip and Fare Dataset on Spark 2.0 HDInsight Clusters (pySpark3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Last updated:\n", + "Jan 08, 2016" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---------------------------------\n", + "### Here we show some features and capabilities of Spark's MLlib toolkit using the NYC taxi trip and fare data-set from 2013 (about 40 Gb uncompressed). We take a 0.1% sample of this data-set (about 170K rows, 35 Mb) to to show MLlib's modeling features for binary classification and regression problems using this data-set. We have shown relevant plots in Python.\n", + "\n", + "### We have sampled the data in order for the runs to finish quickly. The same code will on the full data-set.\n", + "\n", + "----------------------------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### OBJECTIVE: Show use of Spark MLlib's functions for featurization and ML tasks.\n", + "\n", + "### We address two learning problems:\n", + "#### 1. Binary classification: Prediction of tip or no-tip (1/0) for a taxi trip [Using regularized regression]\n", + "#### 2. Regression problem: Prediction of the tip amonut ($) [Using random forest]\n", + "\n", + "#### We have shown the following steps:\n", + "1. Data ingestion & cleanup\n", + "2. Data exploration and plotting\n", + "3. Data preparation (featurizing/transformation), \n", + "4. Modeling (using incl. hyperparameter tuning with cross-validation), prediction, model persistance\n", + "5. Model evaluation on an independent validation data-set." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Introductory material\n", + "\n", + "NYC 2013 Taxi data:\n", + "http://www.andresmh.com/nyctaxitrips/\n", + "https://azure.microsoft.com/en-us/documentation/articles/machine-learning-data-science-process-hive-walkthrough/\n", + "\n", + "An earlier version of this walkthrough was published in a set of notebooks to run on Spark 1.6 HDInsight clusters: https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-overview\n", + "\n", + "----------------------------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set directory paths and location of training, validation files, as well as model location in blob storage\n", + "NOTE: The blob storage attached to the HDI cluster is referenced as: wasb:/// (Windows Azure Storage Blob). Other blob storage accounts are referenced as: wasb://" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# 1. Location of training data\n", + "taxi_train_file_loc = \"wasb://mllibwalkthroughs@cdspsparksamples.blob.core.windows.net/Data/NYCTaxi/JoinedTaxiTripFare.Point1Pct.Train.csv\"\n", + "taxi_valid_file_loc = \"wasb://mllibwalkthroughs@cdspsparksamples.blob.core.windows.net/Data/NYCTaxi/JoinedTaxiTripFare.Point1Pct.Valid.csv\"\n", + "\n", + "# 2. Set model storage directory path. This is where models will be saved.\n", + "modelDir = \"wasb:///user/remoteuser/NYCTaxi/Models/\"; # The last backslash is needed;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set SQL context and import necessary libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from pyspark import SparkConf\n", + "from pyspark import SparkContext\n", + "from pyspark.sql import SQLContext\n", + "from pyspark.sql.functions import UserDefinedFunction\n", + "from pyspark.sql.types import *\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import datetime\n", + "\n", + "sqlContext = SQLContext(sc)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Data ingestion & cleanup: Read in joined 0.1% taxi trip and fare file (as csv), format and clean data, and create data-frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The taxi trip and fare files were joined based on the instructions provided in: \n", + "\"https://azure.microsoft.com/en-us/documentation/articles/machine-learning-data-science-process-hive-walkthrough/\"\n", + "\n", + "A 0.1% sample of the joined data-set was taken to show the ML examples in this walkthrough." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "## READ IN DATA FRAME FROM CSV\n", + "taxi_train_df = spark.read.csv(path=taxi_train_file_loc, header=True, inferSchema=True)\n", + "\n", + "## CREATE A CLEANED DATA-FRAME BY DROPPING SOME UN-NECESSARY COLUMNS & FILTERING FOR UNDESIRED VALUES OR OUTLIERS\n", + "taxi_df_train_cleaned = taxi_train_df.drop('medallion').drop('hack_license').drop('store_and_fwd_flag').drop('pickup_datetime')\\\n", + " .drop('dropoff_datetime').drop('pickup_longitude').drop('pickup_latitude').drop('dropoff_latitude')\\\n", + " .drop('dropoff_longitude').drop('tip_class').drop('total_amount').drop('tolls_amount').drop('mta_tax')\\\n", + " .drop('direct_distance').drop('surcharge')\\\n", + " .filter(\"passenger_count > 0 and passenger_count < 8 AND payment_type in ('CSH', 'CRD') AND tip_amount >= 0 AND tip_amount < 30 AND fare_amount >= 1 AND fare_amount < 150 AND trip_distance > 0 AND trip_distance < 100 AND trip_time_in_secs > 30 AND trip_time_in_secs < 7200\" )\n", + "\n", + "## REGISTER DATA-FRAME AS A TEMP-TABLE IN SQL-CONTEXT\n", + "taxi_df_train_cleaned.createOrReplaceTempView(\"taxi_train\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Data exploration & visualization: Plotting of target variables and features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### First, summarize data using SQL, this outputs a Spark data frame. If the data-set is too large, it can be sampled" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%sql -q -o sqlResultsPD\n", + "SELECT fare_amount, passenger_count, tip_amount, tipped FROM taxi_train WHERE passenger_count > 0 AND passenger_count < 7 AND fare_amount > 0 AND fare_amount < 200 AND payment_type in ('CSH', 'CRD') AND tip_amount > 0 AND tip_amount < 25" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Plot histogram of tip amount, relationship between tip amount vs. other features" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n83z27NnU1dXxxBNPcPjhh7Pxxhszfvx4jjzySF577bVOr1uxYgVXXHEFdXV11NXVFWoMkD0rkqQh\n4YADDuDPf/4z11xzDRdccAFvfetbiQg23XTTLserzJo1i4033pjTTz+dRx99lEsuuYTFixdzxx13\n9Pi4Dz74IPfddx8NDQ1sscUWPPnkk1xyySXstttuPPLII2y44Yad6p9wwglsttlmnHHGGdx33318\n97vfZfz48dxzzz1sueWWnHXWWdx88818/etf513vehczZ85c89r99tuPO++8k6OOOortt9+eefPm\ncdJJJ/H000+vFVp6ouO8HHjggUybNo2zzz6b5uZmvve97zFp0iTOOussAH70ox/xH//xH+y0004c\nffTRAPzjP/5jr4/XXwwrkqQh4Z3vfCczZszgmmuuYf/99+/RJaQNN9yQ+fPnM2rUKACmTJnCySef\nzE033cS+++7bo+Puu+++HHDAAZ3K9ttvP973vvdx3XXXccghh3Tattlmm/Hzn/8cgGOPPZbHHnuM\nc889l+OOO46LLroIgE996lNMnTqV73//+2vCyg033MAdd9zBV7/6Vb7whS8AcNxxx3HggQdywQUX\nMGvWLLbaaqsetblcfX093/nOd9Y8b21t5bLLLlsTVg4++GCOOeYYpk2bxsEHH1zVMfqTl4EkScPW\n0UcfvSaoQPbLf9SoUdx888093sfo0aPX/HvVqlW8+OKLTJs2jfHjx9Pc3NypbkSsdflkp512AuhU\nXldXxw477MDChQvXlN1yyy2st956nHDCCZ1e/7nPfY729nZuueWWHre5vE3HHHNMp7Kdd96ZF154\ngb/97W9V7XOgGVYkScNSRLD11p3Hx2y00UZsttlmPPnkkz3ez2uvvcapp57KlClTGD16NBMmTGDi\nxIm0tbXR1ta2Vv3yHp9x48YB8La3vW2t8mXLlq15/tRTT7H55puz0UYbdarXcefs8jEyvVHepo03\n3hig0/GLzMtAkiR1Y9asWVx55ZU0Njbyvve9j3HjxhERfPKTn6S9vX2t+qU9Oesq72p2U3e6Gp9T\nqS3ralM1xx8MhhVJ0pDRm4XfUko89thj7LrrrmvKli9fzjPPPMM+++zT4/1cd911HH744Zxzzjlr\nylauXNnrWUXrsuWWWzJ//nyWL1/eqXelpaVlzXb4e69I+fH70vMCvTu3A83LQJKkIaPjl3hPg8J3\nvvMdVq1ateb5JZdcwurVq9l77717fMxRo0at1Wtx4YUXsnr16h7voyf23ntvVq1atWYQboc5c+ZQ\nV1fHXnvtBcBb3vIWJkyYwF133dWp3sUXX9ynwLHRRhvVPIDVij0rkjTCLFn4+JA9fn19PSkl/vu/\n/5uDDjqI9ddfn/3226/L+q+//jq77747Bx54IH/605/41re+xc4779zjmUCQzQb64Q9/yNixY9lu\nu+249957mT9/PhMmTFirbl8uq+y3337stttufOlLX2LRokVrpi7feOONNDY2dpoJdNRRR3H22Wfz\nqU99ih122IG77rqLxx57rE/Hr6+v5/bbb2fOnDlsvvnmbLXVVuy4445V76+WDCuSNEJMmDCBMWPG\ncMFJswa7KWsWV+utHXbYga985St8+9vfZt68ebS3t7No0SIiYq1ehYjgoosu4qqrruK0007jjTfe\n4JBDDuGCCy7o1TEvvPBC1ltvPa6++mpee+01PvjBD3L77bez5557Vjxmb5TWjwhuvPFGTj31VH78\n4x9zxRVXMHXqVL7+9a/T2NjY6XWnnnoqra2tzJ07l5/85Cfsvffe3HLLLUycOLHq3pXzzjuPY445\nhi9/+cu8+uqrHHbYYYUJKzFUBtcMhoiYATz00EMPMWPGjKr309zcTH19PededyvT3vHuqvez8I+/\n46QDPkJf2yNpeOv4mVPpZ8VQv5FhT1155ZUceeSRPPjgg/687Cfdfc5KtwP1KaXmtSr0gj0rkjSC\nTJkypV9DgtQfDCuSpGFpXVcOli9fvs5F0TbddFPv5lwAhhVJ0rC0rrEbX//61zn99NO7ff2iRYvs\niSoAw4okadg57LDDOOyww9ZZZ+edd+62zuTJk2vZLFXJsCJJGpGmTp3K1KlTB7sZ6gEvxEmSpEIz\nrEiSpEIzrEiSpEJzzIokDVMdN8CT+sNAfr4MK5I0zHQsqz9z5szBboqGuWpvm9BbhhVJGmamTJlC\nS0tLIZbV1/DW37dN6GBYkaRhyGX1NZw4wFaSJBWaYUWSJBWaYUWSJBWaYUWSJBVaIcJKROwcET+L\niL9GRHtEfLRCnTMi4umIWBERv4iIrcu2j46IiyOiNSJeiYi5ETGxrM7GEXFVRLRFxLKI+F5EbNTf\n70+SJFWvEGEF2Aj4DfBpIJVvjIiTgVnA0cCOwHJgXkRsUFLtfGAf4ABgF2Bz4LqyXV0NTAd2z+vu\nAlxayzciSZJqqxBTl1NKtwK3AkREVKhyInBmSummvM6hwFLgY8C1ETEWOBI4KKV0Z17nCKAlInZM\nKT0QEdOBPYH6lNLDeZ0TgJ9HxH+llJ7t33cpSZKqUZSelS5FxFbAZGB+R1lK6WXgfuD9edEOZMGr\ntM6jwOKSOu8DlnUEldztZD05O/VX+yVJUt8UPqyQBZVE1pNSamm+DWAS8HoeYrqqMxl4rnRjSmk1\n8GJJHUmSVDCFuAxUdI2NjYwbN65TWUNDAw0NDYPUIkmSiqOpqYmmpqZOZW1tbTXb/1AIK88CQdZ7\nUtq7Mgl4uKTOBhExtqx3ZVK+raNO+eygUcAmJXUqmjNnDjNmzKj6DUiSNJxV+gO+ubmZ+vr6muy/\n8JeBUkqLyMLE7h1l+YDanYB78qKHgFVldbYBpgD35kX3AuMj4r0lu9+dLAjd31/tlyRJfVOInpV8\nrZOtyYIDwLSI2B54MaX0F7JpyadExOPAk8CZwBLgBsgG3EbEZcB5EbEMeAW4EFiQUnogr/OniJgH\nfDcijgM2AL4JNDkTSJKk4ipEWCGbzXMH2UDaBHwjL78SODKldE5EjCFbE2U8cDewV0rp9ZJ9NAKr\ngbnAaLKp0MeXHedg4CKyWUDted0T++MNSZKk2ihEWMnXRun2klRKaTYwu5vtK4ET8kdXdV4CZlbV\nSEmSNCgKP2ZFkiSNbIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYV\nSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJU\naIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYV\nSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJUaIYVSZJU\naIYVSZJUaIYVSZJUaIYVSZJUaEMirEREXUScGRELI2JFRDweEadUqHdGRDyd1/lFRGxdtn10RFwc\nEa0R8UpEzI2IiQP3TiRJUm8NibACfAE4Bvg0sC3weeDzETGro0JEnAzMAo4GdgSWA/MiYoOS/ZwP\n7AMcAOwCbA5cNxBvQJIkVWe9wW5AD70fuCGldGv+fHFEHEwWSjqcCJyZUroJICIOBZYCHwOujYix\n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\nAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\nAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "%matplotlib inline\n", + "## %%local creates a pandas data-frame on the head node memory, from spark data-frame, \n", + "## which can then be used for plotting. Here, sampling data is a good idea, depending on the memory of the head node\n", + "\n", + "# TIP BY PAYMENT TYPE AND PASSENGER COUNT\n", + "ax1 = sqlResultsPD[['tip_amount']].plot(kind='hist', bins=25, facecolor='lightblue')\n", + "ax1.set_title('Tip amount distribution')\n", + "ax1.set_xlabel('Tip Amount ($)'); ax1.set_ylabel('Counts');\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()\n", + "\n", + "# TIP BY PASSENGER COUNT\n", + "ax2 = sqlResultsPD.boxplot(column=['tip_amount'], by=['passenger_count'])\n", + "ax2.set_title('Tip amount by Passenger count')\n", + "ax2.set_xlabel('Passenger count'); ax2.set_ylabel('Tip Amount ($)');\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()\n", + "\n", + "# TIP AMOUNT BY FARE AMOUNT, POINTS ARE SCALED BY PASSENGER COUNT\n", + "ax = sqlResultsPD.plot(kind='scatter', x= 'fare_amount', y = 'tip_amount', c='blue', alpha = 0.10, s=2.5*(sqlResultsPD.passenger_count))\n", + "ax.set_title('Tip amount by Fare amount')\n", + "ax.set_xlabel('Fare Amount ($)'); ax.set_ylabel('Tip Amount ($)');\n", + "plt.axis([-2, 80, -2, 20])\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Feature engineering, transformation and data preparation for modeling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create a new feature by binning hours into traffic time buckets using Spark SQL" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "### CREATE FOUR BUCKETS FOR TRAFFIC TIMES\n", + "sqlStatement = \"\"\" SELECT *, CASE\n", + " WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN \"Night\" \n", + " WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN \"AMRush\" \n", + " WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN \"Afternoon\"\n", + " WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN \"PMRush\"\n", + " END as TrafficTimeBins\n", + " FROM taxi_train \n", + "\"\"\"\n", + "taxi_df_train_with_newFeatures = spark.sql(sqlStatement)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Indexing and one-hot encoding of categorical features\n", + "Here we only transform some variables to show examples, which are character strings. Other variables, such as week-day, which are represented by numerical valies, can also be indexed as categorical variables.\n", + "\n", + "For indexing, we used stringIndexer, and for one-hot encoding, we used OneHotEncoder functions from MLlib." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from pyspark.ml import Pipeline\n", + "from pyspark.ml.feature import OneHotEncoder, StringIndexer, VectorIndexer\n", + "\n", + "# DEFINE THE TRANSFORMATIONS THAT NEEDS TO BE APPLIED TO SOME OF THE FEATURES\n", + "sI1 = StringIndexer(inputCol=\"vendor_id\", outputCol=\"vendorIndex\"); en1 = OneHotEncoder(dropLast=False, inputCol=\"vendorIndex\", outputCol=\"vendorVec\");\n", + "sI2 = StringIndexer(inputCol=\"rate_code\", outputCol=\"rateIndex\"); en2 = OneHotEncoder(dropLast=False, inputCol=\"rateIndex\", outputCol=\"rateVec\");\n", + "sI3 = StringIndexer(inputCol=\"payment_type\", outputCol=\"paymentIndex\"); en3 = OneHotEncoder(dropLast=False, inputCol=\"paymentIndex\", outputCol=\"paymentVec\");\n", + "sI4 = StringIndexer(inputCol=\"TrafficTimeBins\", outputCol=\"TrafficTimeBinsIndex\"); en4 = OneHotEncoder(dropLast=False, inputCol=\"TrafficTimeBinsIndex\", outputCol=\"TrafficTimeBinsVec\");\n", + "\n", + "# APPLY TRANSFORMATIONS\n", + "encodedFinal = Pipeline(stages=[sI1, en1, sI2, en2, sI3, en3, sI4, en4]).fit(taxi_df_train_with_newFeatures).transform(taxi_df_train_with_newFeatures);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create a random sampling of the data, as needed (50% is used here). This can save some time while training models. Then, split into train/test." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "31492" + ] + } + ], + "source": [ + "trainingFraction = 0.5; testingFraction = (1-trainingFraction);\n", + "seed = 1234;\n", + "encodedFinalSampled = encodedFinal.sample(False, 0.5, seed=seed)\n", + "\n", + "# SPLIT SAMPLED DATA-FRAME INTO TRAIN/TEST, WITH A RANDOM COLUMN ADDED FOR DOING CV (SHOWN LATER)\n", + "trainData, testData = encodedFinalSampled.randomSplit([trainingFraction, testingFraction], seed=seed);\n", + "\n", + "# CACHE DATA FRAMES IN MEMORY\n", + "trainData.cache(); trainData.count();\n", + "testData.cache(); testData.count();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Binary classification model training: Predicting tip or no tip (target: tipped = 1/0)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "### Create logistic regression model, save modle, and evaluate performance of model on test data set" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create logistic regression model using RFormula and LogisticRegression functions" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Area under ROC = 0.9859906352003691" + ] + } + ], + "source": [ + "from pyspark.ml.classification import LogisticRegression\n", + "from pyspark.mllib.evaluation import BinaryClassificationMetrics\n", + "from pyspark.ml.feature import RFormula\n", + "from sklearn.metrics import roc_curve,auc\n", + "\n", + "## TRAIN USING PIPELINE FORMULA + LOGISTIC REGRESSION ESTIMATOR\n", + "logReg = LogisticRegression(maxIter=10, regParam=0.3, elasticNetParam=0.8)\n", + "\n", + "## DEFINE TRAINING FORMULA\n", + "classFormula = RFormula(formula=\"tipped ~ pickup_hour + weekday + passenger_count + trip_time_in_secs + trip_distance + fare_amount + vendorVec + paymentVec + rateVec + TrafficTimeBinsVec\")\n", + "\n", + "## TRAIN PIPELINE MODEL\n", + "model = Pipeline(stages=[classFormula, logReg]).fit(trainData)\n", + "\n", + "## SAVE MODEL\n", + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"logisticRegModel_\" + datestamp;\n", + "logRegDirfilename = modelDir + fileName;\n", + "model.save(logRegDirfilename)\n", + "\n", + "## PREDICT ON TEST DATA AND EVALUATE\n", + "predictions = model.transform(testData)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "metrics = BinaryClassificationMetrics(predictionAndLabels)\n", + "print(\"Area under ROC = %s\" % metrics.areaUnderROC)\n", + "\n", + "## REGISTER PREDICTION RESULTS TABLE\n", + "predictions.select(\"label\",\"probability\").createOrReplaceTempView(\"tmp_results\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Plot ROC curve from prediction result on test data" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%sql -q -o predictions_pddf\n", + "SELECT * from tmp_results" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n3niDzp07B12SiEjoha6Z1uVkumXLFvr160dBQQE5OTmkp6cHXZKIiKBzpnXGpk2b6NmzJ2bG6tWr\n1UhFRJJI6JppXU2meXl5HHvsseTk5NC+ffugyxERkRLqaGuJXV1Npj/60Y948803MbOgSxERkTKU\nTOsQNVIRkeQUumZaV5OpiIgkr9A107qcTEVEJDmFrpkmezL9z3/+E3QJIiJSTaHLacmcTOfOncvN\nN9/MO++8w7HHHht0OUlr69at7Ny5M+gyRCRJtWjRotb/hiZxa0mMZG2mM2fO5Oqrr+aKK67g6KOP\nDrqcpLV161Y6duzIt99+G3QpIpKkGjduzEcffVSrDTVJW0viJFszdXemTZvGLbfcQlZWFnfeeadm\n7VZi586dfPvttyxYsED3JBaRA3z00UeMHTuWnTt3qpkmUjKdM3V3srKymDFjBtOmTePGG28MuqQ6\no2PHjnTp0iXoMkREgBA202RJpkVFRVx55ZXMmTOHmTNn8otf/CLokkREJEZJ0lpqT7Ik0/nz5/Pg\ngw8yb948Jk2aFHQ5IiJSA6FrpsmSTCdMmMCJJ55I3759gy5FRERqSNeZBqRBgwZqpCIi9UTommmy\nJFMRSR5r167l0EMP5f/9v/8XdClSRmFhIcceeywPPfRQ0KVUKnTNNCV0z1jqiscff5yUlJT9Hw0a\nNODoo49mwoQJbN++vcL1fv/739O7d2+OOOIIDjvsME455RRuv/32Sq/Fff755xk8eDAtW7bk0EMP\npV27dowePZrXX389EU8t6d10001cfPHFHHPMMUGXkhReeuklunbtSqNGjWjfvj233nor+/btq9K6\nX3zxBRMmTKBVq1Y0btyYrl278uyzz5a77LJly+jRoweHHXYYRx55JKNGjeKf//xnqWUOOeQQrr76\naqZNm0Z+fn6Nn1uiqLWIJBEzY9q0aSxYsICHH36YwYMHs2DBAvr06XPAH5KioiJGjx7N+PHjMTNu\nu+027r//fn784x9z2223ceaZZ7Jjx44D9jFhwgRGjhzJF198wTXXXMPDDz/MFVdcwebNm+nfvz9r\n1qypraebFN59912WL1/O5MmTgy4lKbz66quMGDGCI488kgceeIARI0Ywbdo0rrrqqoOu+/XXX9O9\ne3eef/55/vu//5t7772Xww8/nAsvvJCnnnqq1LKvvPIK5557LoWFhfz2t7/l2muvZdWqVfTs2ZMv\nv/yy1LITJkxg586dPPnkk3F9rnHl7qH4ALoAvn79eq8t27Zt83vuuceLiopqbZ/13fr16722f4+1\n5bHHHvOUlJQDnltWVpanpKT4M888U2r8zjvvdDPz66+//oBtvfLKK56amuqDBw8uNX733Xe7mfk1\n11xTbg0LFizwd955p4bPpGa++eabWt3fVVdd5R06dIjrNr/99tu4bq82derUybt06eL79u3bP3bT\nTTd5amqqb9q0qdJ1Z8yY4SkpKf7GG2/sHysqKvLTTz/d27Zt6wUFBaX2k56e7oWFhfvH3nvvPU9N\nTfVrr732gG0PGTLEe/fufdD6D/Y3ovhxoIvHs8fEc2PJ/FHbzXTz5s1+3HHH+THHHOM7duyolX2G\nQRib6dKlS93M/K677to/lpeX50ceeaR37Nix1B+9kiZOnOgpKSn+5z//ef86Rx11lJ988sk1eoFX\nVFTks2bN8s6dO3taWpq3bNnSBw0atL/uLVu2uJn5448/fsC6Zua33Xbb/u9//etfu5n5hx9+6JmZ\nmX7EEUd4ly5d/J577nEz861btx6wjaysLG/YsKHv3r17/9iaNWt84MCB3qxZM2/cuLH37t3b//Sn\nP1Xp+bRv394nTpx4wPiLL77o5513nrdt29YPPfRQP/744/32228/4Ofdu3dv79y5s69fv9579uzp\njRs39qlTp+5//A9/+IP37NnTDzvsMG/atKmfd955/sEHH5TaxsaNG/2nP/2pH3fccZ6WluatW7f2\niRMn+pdfflml5xAvH374oZuZP/TQQ6XGt2/f7mbmd9xxR6XrDx061Fu1anXA+D333OMpKSm+fPly\nd3f/6quvKnwh+MMf/tCPPvroA8Znz57tqampvmvXrkprCKqZ6jBvAmzatImePXtiZqxevZoWLVoE\nXZLUYZs3bwbgiCOO2D/25ptvsmvXLsaMGUNKBRMBLrnkEtydV155Zf86X331FWPGjKnRLSsnTpzI\n1KlTad++PTNmzOBXv/oVjRo1iunwcHEdo0aNYu/evUyfPp1LL72UCy+8EDPj6aefPmCdZ555hkGD\nBtGsWTMAVq5cSe/evcnNzeXWW29l+vTp7Nmzh3POOYd169ZVuv/t27ezdevWcu+m9dhjj9G0aVOu\nueYaZs+ezWmnncYtt9zCr371qwOew86dOxk8eDBdunTh/vvv3z9T//e//z3nn38+TZs2ZcaMGdxy\nyy189NFH9OzZk61bt+7fxrJly9i8eTMTJ07kgQceIDMzk6eeeorzzjuvSj/HL7/8skofBzvn+Je/\n/AUzo2vXrqXG27Rpw9FHH81f/vKXStf/7rvvaNSo0QHjjRs3xt1Zv379/uWACpfdvn07X3zxRanx\nrl27UlRUxFtvvVVpDYGJZ2dO5g9qKZm+9957/r3vfc87derk27dvT+i+wigMyXTlypW+c+dO/9e/\n/uXPPvusf+973/PGjRv7tm3b9i97//33e0pKir/44osVbm/Xrl1uZn7BBRe4e+SV/cHWOZiVK1e6\nmZVKXmVVJ5neeuutbmY+duzYA5Y9++yzvVu3bqXG1q5d62bmCxcu3D+Wnp5+wOHsvXv3+nHHHecD\nBw6s9PmsWLHCzcyXLl16wGN79+49YGzy5MnepEkTz8/P3z/Wp08fT0lJ8Xnz5pVaNjc314844gif\nPHlyqfEvvvjCmzdv7pdffnml+3rqqac8JSXF33zzzUqfg3vk53qwj5SUlHJ/JyUVJ8h//etfBzx2\n+umn+9lnn13p+ldddZUfcsghBxxRuOiiizwlJcWvuuoqd48c3TjiiCN8wIABpZbbuXOnN2nSxFNS\nUnzDhg2lHvvss8/czPzuu++utIagkqkuFImjtWvXMmjQIDp06MBrr72mRBqwb7+Fjz9O/H5OOgka\nN47Pttydfv36lRr7/ve/z5NPPknbtm33j3399dcANG3atMJtFT9W/B65xZ8rW+dgnnvuOVJSUrjl\nllti3kZZZsbll19+wPjo0aOZOnUqmzdv5vvf/z4AixcvJi0tjaFDhwKRyUOffvopN998c6lJK8U/\nxwULFlS67y+//BIzK5X6ix166KH7v87NzeW7776jR48ezJ07l48//pjOnTuXWvanP/1pqfWXLVvG\nnj17uOiii0rVZmacccYZpWZOl9zXd999R25uLmeccQbuzoYNG+jevXulz2P58uWVPl7s5JNPrvTx\nvLy8A+oplpaWtv/fXUUmTZrEQw89xKhRo5g5cyatWrVi8eLFvPDCC6W2X/w7nzFjBjfccAMTJ05k\nz549XH/99RQUFJRatljx7yhZ335RzTROcnJyOP/88+ncuTNLly6lefPmQZcUeh9/DGWOViXE+vUQ\nr3vumxlz5szhxBNPZM+ePfzv//4vOTk5NGzYsNRyxQ2xsj9uZRvu4YcfftB1DuYf//gHbdu2jfu/\n7+JmWdKoUaO4+uqrWbx4MVlZWQA8++yznHvuuTRp0gSATz/9FIgc0i5PSkoKe/bs2X9IuCIeOXpV\nyocffsiNN97I66+/vv+FCER+R3v27Cm1bLt27TikzEXsn376Ke5e7s1ZzKxUTbt27eLWW29l8eLF\npQ5vlrev8pxzzjkHXaYqig+7Fh+GLWnv3r3lHpYtqXPnzixatIjJkyfTo0cP3J02bdpw//33M3ny\n5P2/N4Df/OY3fPnll9x9993cddddmBkZGRlMnDiRhx9+uNSy8H+/o2R9Vy010zjJy8ujR48ePP30\n0wf8I5BgnHRSpNHVxn7iqVu3bvvP4Q0bNowePXowZswYNm3aRONoBO7YsSPuzsaNG/entLI2btwI\nQKdOnaJ1noS78/7771e4TjxU9MeuqKiownXK+yPdpk0bevbsydNPP01WVhZvv/02W7du5e677z5g\nm/feey+nnnpquduu7P/jUUcdhbuza9euUuN79uyhV69eNG/enGnTpnHccceRlpbG+vXrycrKOuC5\nlFd/UVERZsaCBQto1arVAY+XbL6jRo1izZo1XHfddZx66qk0adKEoqIiBg4cWOnPrdjnn39+0GUA\nmjVrRlpaWoWPt2nTBoDPPvuMdu3alXrss88+44wzzjjoPn7yk58wdOhQ3nvvPfbt20eXLl32p/D0\n9PT9yzVo0IC5c+dyxx138Mknn9CqVStOOOGE/fMATjjhhFLbLf4dJesRPzXTOBk4cCAZGRlJ+6op\njBo3jl9iDEpKSgrTp0+nb9++PPDAA1x33XUA9OjRg+bNm/Pkk09y4403lvvv7vHHH8fMOP/88/ev\nc8QRR7Bo0SJuuOGGmP6tHn/88bz22mvs3r27wnRafDhu9+7dpcbLXoxfFaNHj2bKlCl8+umnLF68\nmMMOO2z/8ymuByLpO5Z0dlL0lVDxJK9ib7zxBrt27eLFF18sdYj173//e5W3ffzxx+PutGzZstLa\ndu/ezcqVK7n99ttLvQ3j3/72tyrvq02bNphZuQm7mJnx6KOPVpjiAX70ox/h7qxbt47TTjtt//hn\nn33Gv/71rypfi3vIIYeUmsS0bNkyzIz+/fsfsGzLli1p2bIlEHkBsmrVKs4888z9LxyLFf+OkvV9\njDWbN47USCURevfuzemnn86sWbP2z8Zs1KgR1157LR9//DE33HDDAessXbqUxx9/nEGDBnH66afv\n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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "## PLOT ROC CURVE AFTER CONVERTING PREDICTIONS TO A PANDAS DATA FRAME\n", + "from sklearn.metrics import roc_curve,auc\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "labels = predictions_pddf[\"label\"]\n", + "prob = []\n", + "for dv in predictions_pddf[\"probability\"]:\n", + " prob.append(list(dv.values())[1][1])\n", + " \n", + "fpr, tpr, thresholds = roc_curve(labels, prob, pos_label=1);\n", + "roc_auc = auc(fpr, tpr)\n", + "\n", + "plt.figure(figsize=(5,5))\n", + "plt.plot(fpr, tpr, label='ROC curve (area = %0.2f)' % roc_auc)\n", + "plt.plot([0, 1], [0, 1], 'k--')\n", + "plt.xlim([0.0, 1.0]); plt.ylim([0.0, 1.05]);\n", + "plt.xlabel('False Positive Rate'); plt.ylabel('True Positive Rate');\n", + "plt.title('ROC Curve'); plt.legend(loc=\"lower right\");\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train a regression model: Predict the amount of tip paid for taxi trips" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Train a random forest regression model using the Pipeline function, save, and evaluate on test data set" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RMSE = 0.9658080190088756\n", + "R-sqr = 0.6847518421441956" + ] + } + ], + "source": [ + "from pyspark.ml.regression import RandomForestRegressor\n", + "from pyspark.mllib.evaluation import RegressionMetrics\n", + "\n", + "## DEFINE REGRESSION FURMULA\n", + "regFormula = RFormula(formula=\"tip_amount ~ paymentIndex + vendorIndex + rateIndex + TrafficTimeBinsIndex + pickup_hour + weekday + passenger_count + trip_time_in_secs + trip_distance + fare_amount\")\n", + "\n", + "## DEFINE INDEXER FOR CATEGORIAL VARIABLES\n", + "featureIndexer = VectorIndexer(inputCol=\"features\", outputCol=\"indexedFeatures\", maxCategories=32)\n", + "\n", + "## DEFINE RANDOM FOREST ESTIMATOR\n", + "randForest = RandomForestRegressor(featuresCol = 'indexedFeatures', labelCol = 'label', numTrees=20, \n", + " featureSubsetStrategy=\"auto\",impurity='variance', maxDepth=6, maxBins=100)\n", + "\n", + "## Fit model, with formula and other transformations\n", + "model = Pipeline(stages=[regFormula, featureIndexer, randForest]).fit(trainData)\n", + "\n", + "## SAVE MODEL\n", + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"RandomForestRegressionModel_\" + datestamp;\n", + "randForestDirfilename = modelDir + fileName;\n", + "model.save(randForestDirfilename)\n", + "\n", + "## PREDICT ON TEST DATA AND EVALUATE\n", + "predictions = model.transform(testData)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "testMetrics = RegressionMetrics(predictionAndLabels)\n", + "print(\"RMSE = %s\" % testMetrics.rootMeanSquaredError)\n", + "print(\"R-sqr = %s\" % testMetrics.r2)\n", + "\n", + "## PLOC ACTUALS VS. PREDICTIONS\n", + "predictions.select(\"label\",\"prediction\").createOrReplaceTempView(\"tmp_results\");" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%%sql -q -o predictionsPD\n", + "SELECT * from tmp_results" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "import numpy as np\n", + "\n", + "ax = predictionsPD.plot(kind='scatter', figsize = (5,5), x='label', y='prediction', color='blue', alpha = 0.25, label='Actual vs. predicted');\n", + "fit = np.polyfit(predictionsPD['label'], predictionsPD['prediction'], deg=1)\n", + "ax.set_title('Actual vs. Predicted Tip Amounts ($)')\n", + "ax.set_xlabel(\"Actual\"); ax.set_ylabel(\"Predicted\");\n", + "ax.plot(predictionsPD['label'], fit[0] * predictionsPD['label'] + fit[1], color='magenta')\n", + "plt.axis([-1, 15, -1, 15])\n", + "plt.show(ax)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load a saved pipeline model and evaluate it on test data set" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Area under ROC = 0.9859906352003691" + ] + } + ], + "source": [ + "from pyspark.ml import PipelineModel\n", + "\n", + "savedModel = PipelineModel.load(logRegDirfilename)\n", + "\n", + "predictions = savedModel.transform(testData)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "\n", + "metrics = BinaryClassificationMetrics(predictionAndLabels)\n", + "print(\"Area under ROC = %s\" % metrics.areaUnderROC)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hyper-parameter tuning: Train a random forest model using cross-validation" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "R-squared on test data = 0.755346" + ] + } + ], + "source": [ + "from pyspark.ml.tuning import CrossValidator, ParamGridBuilder\n", + "from pyspark.ml.evaluation import RegressionEvaluator\n", + "\n", + "## DEFINE RANDOM FOREST MODELS\n", + "randForest = RandomForestRegressor(featuresCol = 'indexedFeatures', labelCol = 'label', \n", + " featureSubsetStrategy=\"auto\",impurity='variance', maxBins=100)\n", + "\n", + "## DEFINE MODELING PIPELINE, INCLUDING FORMULA, FEATURE TRANSFORMATIONS, AND ESTIMATOR\n", + "pipeline = Pipeline(stages=[regFormula, featureIndexer, randForest])\n", + "\n", + "## DEFINE PARAMETER GRID FOR RANDOM FOREST\n", + "paramGrid = ParamGridBuilder() \\\n", + " .addGrid(randForest.numTrees, [10, 25, 50]) \\\n", + " .addGrid(randForest.numTrees, [3, 5, 7]) \\\n", + " .build()\n", + "\n", + "## DEFINE CROSS VALIDATION\n", + "crossval = CrossValidator(estimator=pipeline,\n", + " estimatorParamMaps=paramGrid,\n", + " evaluator=RegressionEvaluator(metricName=\"rmse\"),\n", + " numFolds=3)\n", + "\n", + "## TRAIN MODEL USING CV\n", + "cvModel = crossval.fit(trainData)\n", + "\n", + "## PREDICT AND EVALUATE TEST DATA SET\n", + "predictions = cvModel.transform(testData)\n", + "evaluator = RegressionEvaluator(labelCol=\"label\", predictionCol=\"prediction\", metricName=\"r2\")\n", + "r2 = evaluator.evaluate(predictions)\n", + "print(\"R-squared on test data = %g\" % r2)\n", + "\n", + "## SAVE THE BEST MODEL\n", + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"CV_RandomForestRegressionModel_\" + datestamp;\n", + "CVDirfilename = modelDir + fileName;\n", + "cvModel.bestModel.save(CVDirfilename);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load and transform an independent validation data-set, and evaluate the saved pipeline model" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "## READ IN DATA FRAME FROM CSV\n", + "taxi_valid_df = spark.read.csv(path=taxi_valid_file_loc, header=True, inferSchema=True)\n", + "\n", + "## CREATE A CLEANED DATA-FRAME BY DROPPING SOME UN-NECESSARY COLUMNS & FILTERING FOR UNDESIRED VALUES OR OUTLIERS\n", + "taxi_df_valid_cleaned = taxi_valid_df.drop('medallion').drop('hack_license').drop('store_and_fwd_flag').drop('pickup_datetime')\\\n", + " .drop('dropoff_datetime').drop('pickup_longitude').drop('pickup_latitude').drop('dropoff_latitude')\\\n", + " .drop('dropoff_longitude').drop('tip_class').drop('total_amount').drop('tolls_amount').drop('mta_tax')\\\n", + " .drop('direct_distance').drop('surcharge')\\\n", + " .filter(\"passenger_count > 0 and passenger_count < 8 AND payment_type in ('CSH', 'CRD') AND tip_amount >= 0 AND tip_amount < 30 AND fare_amount >= 1 AND fare_amount < 150 AND trip_distance > 0 AND trip_distance < 100 AND trip_time_in_secs > 30 AND trip_time_in_secs < 7200\" )\n", + "\n", + "## REGISTER DATA-FRAME AS A TEMP-TABLE IN SQL-CONTEXT\n", + "taxi_df_valid_cleaned.createOrReplaceTempView(\"taxi_valid\")\n", + "\n", + "### CREATE FOUR BUCKETS FOR TRAFFIC TIMES\n", + "sqlStatement = \"\"\" SELECT *, CASE\n", + " WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN \"Night\" \n", + " WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN \"AMRush\" \n", + " WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN \"Afternoon\"\n", + " WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN \"PMRush\"\n", + " END as TrafficTimeBins\n", + " FROM taxi_valid\n", + "\"\"\"\n", + "taxi_df_valid_with_newFeatures = spark.sql(sqlStatement)\n", + "\n", + "## APPLY THE SAME TRANSFORATION ON THIS DATA AS ORIGINAL TRAINING DATA\n", + "encodedFinalValid = Pipeline(stages=[sI1, en1, sI2, en2, sI3, en3, sI4, en4]).fit(taxi_df_train_with_newFeatures).transform(taxi_df_valid_with_newFeatures)" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "R-squared on test data = 0.772587" + ] + } + ], + "source": [ + "## LOAD SAVED MODEL, SCORE VALIDATION DATA, AND EVALUATE\n", + "savedModel = PipelineModel.load(CVDirfilename)\n", + "predictions = savedModel.transform(encodedFinalValid)\n", + "r2 = evaluator.evaluate(predictions)\n", + "print(\"R-squared on test data = %g\" % r2)" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "celltoolbar": "Raw Cell Format", + "kernelspec": { + "display_name": "PySpark3", + "language": "", + "name": "pyspark3kernel" + }, + "language_info": { + "codemirror_mode": { + "name": "python", + "version": 3 + }, + "mimetype": "text/x-python", + "name": "pyspark3", + "pygments_lexer": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/Misc/Spark/pySpark/Spark2.0/Spark2.0_pySpark3_Airline_Departure_Delay_Classification.ipynb b/Misc/Spark/pySpark/Spark2.0/Spark2.0_pySpark3_Airline_Departure_Delay_Classification.ipynb new file mode 100644 index 00000000..538475f5 --- /dev/null +++ b/Misc/Spark/pySpark/Spark2.0/Spark2.0_pySpark3_Airline_Departure_Delay_Classification.ipynb @@ -0,0 +1,1252 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Wrangling, Exploration and Modeling of Airline Departure Delay (2011/2012) Dataset on Spark 2.0 HDInsight Clusters (pySpark3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Last updated:\n", + "February 07, 2016" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---------------------------------\n", + "### Here we show key features and capabilities of Spark's MLlib toolkit using the Airline On-Time Performance Dataset from the Research and Innovative Technology Administration of the Bureau of Transportation Statistics (see below). This dataset spans 26 years, 1988 through 2012, and is fairly large: over 148 million records, or 14 GB of raw information. We used a part of the data, that from 2011 (training) and 2012 (testing), to evaluate the performance of binary classification models to predict if certain flight departures were delayed by 15 mins or not. \n", + "\n", + "### The weather conditions (e.g. windspeed, humidity, precipitation) at origin and destination airports was integrated with the original airline data-set in order to incorporate the weather features in the models.\n", + "\n", + "### This notebook takes about 15 mins to run on a 2 worker-node cluster(D12_V2).\n", + "\n", + "### The nobebook runs in the PySpark3 kernel in Jupyter.\n", + "\n", + "----------------------------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### OBJECTIVE: Show use of Spark MLlib's functions for featurization and ML tasks.\n", + "\n", + "### The learning task is to predict whether flight departures were delayed by 15 mins or not (binary classification), based on airport (origin and destination airports) and weather (e.g. windspeed, humidity, temperature) features.\n", + "\n", + "#### We have shown the following steps:\n", + "1. Data ingestion, joining, and wrangling.\n", + "2. Data exploration and plotting.\n", + "3. Data preparation (featurizing/transformation).\n", + "4. Modeling (using incl. hyperparameter tuning with cross-validation), prediction, model persistance.\n", + "5. Model evaluation on an independent validation data-set.\n", + "\n", + "Through the above steps we highlight Spark SQL, as well as, MLlib's modeling and transformation functions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Introductory material\n", + "\n", + "Airline On-Time Performance Dataset from the Research and Innovative Technology Administration of the Bureau of Transportation Statistics (http://www.transtats.bts.gov/DL_SelectFields.asp?Table_ID=236&DB_Short_Name=On-Time).\n", + "\n", + "The combined dataset can be obtained from the Revolution Analytics website (http://packages.revolutionanalytics.com/datasets/AirOnTime87to12). \n", + "\n", + "An interesting feature of the airline data-set is that there are several features (e.g. airport ids, flight numbers etc.) which have many (>100) categorical features.\n", + "\n", + "----------------------------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set directory paths and location of training, validation files, as well as model location in blob storage\n", + "NOTE: The blob storage attached to the HDI cluster is referenced as: wasb:/// (Windows Azure Storage Blob). Other blob storage accounts are referenced as: wasb://" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting Spark application\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
20application_1485304066110_0069pyspark3idleLinkLinkâś”
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SparkSession available as 'spark'.\n" + ] + } + ], + "source": [ + "# 1. Location of training data: contains Dec 2013 trip and fare data from NYC \n", + "air_file_loc = \"wasb://data@cdspsparksamples.blob.core.windows.net/Airline/AirlineSubsetCsv\"\n", + "weather_file_loc = \"wasb://data@cdspsparksamples.blob.core.windows.net/Airline/WeatherSubsetCsv\"\n", + "\n", + "# 3. Set model storage directory path. This is where models will be saved.\n", + "modelDir = \"wasb:///user/remoteuser/Airline/Models/\"; # The last backslash is needed;\n", + "\n", + "# 4. Set data storage path. This is where data is sotred on the blob attached to the cluster.\n", + "dataDir = \"wasb:///HdiSamples/HdiSamples/Airline/\"; # The last backslash is needed;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set SQL context and import necessary libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from pyspark import SparkConf\n", + "from pyspark import SparkContext\n", + "from pyspark.sql import SQLContext\n", + "from pyspark.sql.functions import UserDefinedFunction\n", + "from pyspark.sql.types import *\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import datetime\n", + "\n", + "sqlContext = SQLContext(sc)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data ingestion and wrangling using Spark SQL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Data import and registering as tables" + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "## READ IN AIR DATA FRAME FROM CSV\n", + "air = spark.read.csv(path=air_file_loc, header=True, inferSchema=True)\n", + "\n", + "## READ IN WEATHER DATA FRAME FROM CSV\n", + "weather = spark.read.csv(path=weather_file_loc, header=True, inferSchema=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 140, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- YEAR: integer (nullable = true)\n", + " |-- MONTH: integer (nullable = true)\n", + " |-- DAY_OF_MONTH: integer (nullable = true)\n", + " |-- DAY_OF_WEEK: integer (nullable = true)\n", + " |-- FL_DATE: timestamp (nullable = true)\n", + " |-- UNIQUE_CARRIER: string (nullable = true)\n", + " |-- TAIL_NUM: string (nullable = true)\n", + " |-- FL_NUM: integer (nullable = true)\n", + " |-- ORIGIN_AIRPORT_ID: integer (nullable = true)\n", + " |-- ORIGIN: string (nullable = true)\n", + " |-- ORIGIN_STATE_ABR: string (nullable = true)\n", + " |-- DEST_AIRPORT_ID: integer (nullable = true)\n", + " |-- DEST: string (nullable = true)\n", + " |-- DEST_STATE_ABR: string (nullable = true)\n", + " |-- CRS_DEP_TIME: integer (nullable = true)\n", + " |-- DEP_TIME: string (nullable = true)\n", + " |-- DEP_DELAY: string (nullable = true)\n", + " |-- DEP_DELAY_NEW: string (nullable = true)\n", + " |-- DEP_DEL15: string (nullable = true)\n", + " |-- DEP_DELAY_GROUP: string (nullable = true)\n", + " |-- TAXI_OUT: double (nullable = true)\n", + " |-- WHEELS_OFF: integer (nullable = true)\n", + " |-- WHEELS_ON: integer (nullable = true)\n", + " |-- TAXI_IN: double (nullable = true)\n", + " |-- CRS_ARR_TIME: integer (nullable = true)\n", + " |-- ARR_TIME: string (nullable = true)\n", + " |-- ARR_DELAY: string (nullable = true)\n", + " |-- ARR_DELAY_NEW: string (nullable = true)\n", + " |-- ARR_DEL15: string (nullable = true)\n", + " |-- ARR_DELAY_GROUP: string (nullable = true)\n", + " |-- CANCELLED: double (nullable = true)\n", + " |-- CANCELLATION_CODE: string (nullable = true)\n", + " |-- DIVERTED: double (nullable = true)\n", + " |-- CRS_ELAPSED_TIME: string (nullable = true)\n", + " |-- ACTUAL_ELAPSED_TIME: string (nullable = true)\n", + " |-- AIR_TIME: double (nullable = true)\n", + " |-- FLIGHTS: double (nullable = true)\n", + " |-- DISTANCE: double (nullable = true)\n", + " |-- DISTANCE_GROUP: integer (nullable = true)\n", + " |-- CARRIER_DELAY: double (nullable = true)\n", + " |-- WEATHER_DELAY: double (nullable = true)\n", + " |-- NAS_DELAY: double (nullable = true)\n", + " |-- SECURITY_DELAY: double (nullable = true)\n", + " |-- LATE_AIRCRAFT_DELAY: double (nullable = true)\n", + " |-- _c44: string (nullable = true)\n", + "\n", + "root\n", + " |-- Visibility: string (nullable = true)\n", + " |-- DryBulbCelsius: string (nullable = true)\n", + " |-- DewPointCelsius: string (nullable = true)\n", + " |-- RelativeHumidity: string (nullable = true)\n", + " |-- WindSpeed: string (nullable = true)\n", + " |-- Altimeter: string (nullable = true)\n", + " |-- AdjustedYear: double (nullable = true)\n", + " |-- AdjustedMonth: double (nullable = true)\n", + " |-- AdjustedDay: integer (nullable = true)\n", + " |-- AdjustedHour: integer (nullable = true)\n", + " |-- AirportID: integer (nullable = true)" + ] + } + ], + "source": [ + "## CHECK SCHEMA OF TRIP AND FARE TABLES\n", + "air.printSchema()\n", + "weather.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Using Spark SQL to join, clean and featurize data" + ] + }, + { + "cell_type": "code", + "execution_count": 141, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "## REGISTER DATA-FRAMEs AS A TEMP-TABLEs IN SQL-CONTEXT\n", + "air.createOrReplaceTempView(\"airline\")\n", + "weather.createOrReplaceTempView(\"weather\")" + ] + }, + { + "cell_type": "code", + "execution_count": 466, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "## USING SQL: CLEAN AND MERGE AIR AND WEATHER DATA-SETS TO CREATE A JOINED DATA-FRAME\n", + "\n", + "# COUNT FLIGHTS BY AIRPORT\n", + "spark.sql(\"SELECT ORIGIN, COUNT(*) as CTORIGIN FROM airline GROUP BY ORIGIN\").createOrReplaceTempView(\"countOrigin\")\n", + "spark.sql(\"SELECT DEST, COUNT(*) as CTDEST FROM airline GROUP BY DEST\").createOrReplaceTempView(\"countDest\")\n", + "\n", + "## CLEAN AIRLINE DATA WITH QUERY, FILTER FOR AIRPORTS WHICH HAVE VERY FEW FLIGHTS (<100)\n", + "sqlStatement = \"\"\"SELECT ARR_DEL15 as ArrDel15, YEAR as Year,\n", + " MONTH as Month, DAY_OF_MONTH as DayOfMonth, DAY_OF_WEEK as DayOfWeek,\n", + " UNIQUE_CARRIER as Carrier, ORIGIN_AIRPORT_ID as OriginAirportID, ORIGIN,\n", + " DEST_AIRPORT_ID as DestAirportID, DEST, floor(CRS_DEP_TIME/100) as CRSDepTime,\n", + " floor(CRS_ARR_TIME/100) as CRSArrTime\n", + " FROM airline\n", + " WHERE ARR_DEL15 in ('0.0', '1.0')\n", + " AND ORIGIN IN (SELECT DISTINCT ORIGIN FROM countOrigin where CTORIGIN > 100)\n", + " AND DEST IN (SELECT DISTINCT DEST FROM countDest where CTDEST > 100) \"\"\"\n", + "airCleaned = spark.sql(sqlStatement)\n", + "\n", + "# REGISTER CLEANED AIR DATASET\n", + "airCleaned.createOrReplaceTempView(\"airCleaned\")\n", + "\n", + "## CLEAN WEATHER DATA WITH QUERY\n", + "sqlStatement = \"\"\"SELECT AdjustedYear, AdjustedMonth, AdjustedDay, AdjustedHour, AirportID,\n", + " avg(Visibility) as Visibility, avg(DryBulbCelsius) as DryBulbCelsius, avg(DewPointCelsius) as DewPointCelsius,\n", + " avg(RelativeHumidity) as RelativeHumidity, avg(WindSpeed) as WindSpeed, avg(Altimeter) as Altimeter\n", + " FROM weather\n", + " GROUP BY AdjustedYear, AdjustedMonth, AdjustedDay, AdjustedHour, AirportID\"\"\"\n", + "weatherCleaned = spark.sql(sqlStatement)\n", + "\n", + "# REGISTER CLEANED AIR DATASET\n", + "weatherCleaned.createOrReplaceTempView(\"weatherCleaned\")" + ] + }, + { + "cell_type": "code", + "execution_count": 467, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# CREATE JOINED DATA SET AND REGISTER TABLE\n", + "sqlStatement = \"\"\"SELECT a.ArrDel15, a.Year, a.Month, a.DayOfMonth, a.DayOfWeek, a.Carrier, a.OriginAirportID, \\\n", + " a.ORIGIN, a.DestAirportID, a.DEST, a.CRSDepTime, b.Visibility as VisibilityOrigin, \\\n", + " b.DryBulbCelsius as DryBulbCelsiusOrigin, b.DewPointCelsius as DewPointCelsiusOrigin,\n", + " b.RelativeHumidity as RelativeHumidityOrigin, b.WindSpeed as WindSpeedOrigin, \\\n", + " b.Altimeter as AltimeterOrigin, c.Visibility as VisibilityDest, \\\n", + " c.DryBulbCelsius as DryBulbCelsiusDest, c.DewPointCelsius as DewPointCelsiusDest,\n", + " c.RelativeHumidity as RelativeHumidityDest, c.WindSpeed as WindSpeedDest, \\\n", + " c.Altimeter as AltimeterDest\n", + " FROM airCleaned a, weatherCleaned b, weatherCleaned c\n", + " WHERE a.Year = b.AdjustedYear and a.Year = c.AdjustedYear\n", + " and a.Month = b.AdjustedMonth and a.Month = c.AdjustedMonth\n", + " and a.DayofMonth = b.AdjustedDay and a.DayofMonth = c.AdjustedDay\n", + " and a.CRSDepTime= b.AdjustedHour and a.CRSDepTime = c.AdjustedHour\n", + " and a.OriginAirportID = b.AirportID and a.DestAirportID = c.AirportID\"\"\"\n", + "\n", + "# SEVERAL COLUMNS CONTAIN NULL VALUES, IT IS IMPORTANT TO FILTER FOR THOSE, OTHERWISE SOME TRANSFORMATIONS\n", + "# WILL HAVE ERRORS LATER. HWERE WE SHOW HOW TO FILTER A DATA-FRAME USING SQL STATEMENT\n", + "joined = spark.sql(sqlStatement).filter(\"VisibilityOrigin is not NULL and DryBulbCelsiusOrigin is not NULL \\\n", + " and DewPointCelsiusOrigin is not NULL and RelativeHumidityOrigin is not NULL \\\n", + " and WindSpeedOrigin is not NULL and AltimeterOrigin is not NULL \\\n", + " and VisibilityDest is not NULL and DryBulbCelsiusDest is not NULL \\\n", + " and DewPointCelsiusDest is not NULL and RelativeHumidityDest is not NULL \\\n", + " and WindSpeedDest is not NULL and AltimeterDest is not NULL \\\n", + " and ORIGIN is not NULL and DEST is not NULL \\\n", + " and OriginAirportID is not NULL and DestAirportID is not NULL \\\n", + " and CRSDepTime is not NULL and Year is not NULL and Month is not NULL \\\n", + " and DayOfMonth is not NULL and DayOfWeek is not NULL and Carrier is not NULL\")\n", + "\n", + "# REGISTER JOINED\n", + "joined.createOrReplaceTempView(\"joined\")" + ] + }, + { + "cell_type": "code", + "execution_count": 468, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+--------------------+-----------+\n", + "| tableName|isTemporary|\n", + "+--------------------+-----------+\n", + "| hivesampletable| false|\n", + "| aircleaned| true|\n", + "| airline| true|\n", + "| countdest| true|\n", + "| countorigin| true|\n", + "| joined| true|\n", + "|originairportidtrain| true|\n", + "| testdatafilt| true|\n", + "| testdatapartition| true|\n", + "|testdatapartition...| true|\n", + "| testpartition| true|\n", + "| testpartitionfilt| true|\n", + "| tmp_results| true|\n", + "| train| true|\n", + "| traindatapartition| true|\n", + "| trainpartition| true|\n", + "| trainpartitionfilt| true|\n", + "| transformedtestfilt| true|\n", + "|transformedtrainfilt| true|\n", + "| weather| true|\n", + "+--------------------+-----------+\n", + "only showing top 20 rows" + ] + } + ], + "source": [ + "## SHOW WHICH TABLES ARE REGISTERED IN SQL-CONTEXT\n", + "spark.sql(\"show tables\").show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Split data by year, 2011 for training and 2012 for validation" + ] + }, + { + "cell_type": "code", + "execution_count": 469, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# CREATE TRAINING DATA AND VALIDATION DATA\n", + "sqlStatement = \"\"\"SELECT * from joined WHERE Year = 2011\"\"\"\n", + "train = spark.sql(sqlStatement)\n", + "\n", + "# REGISTER JOINED\n", + "sqlStatement = \"\"\"SELECT * from joined WHERE Year = 2012\"\"\"\n", + "validation = spark.sql(sqlStatement)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Save in blob" + ] + }, + { + "cell_type": "code", + "execution_count": 470, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# SAVE JOINED DATA IN BLOB\n", + "trainfilename = dataDir + \"TrainData\";\n", + "train.write.mode(\"overwrite\").parquet(trainfilename)\n", + "\n", + "validfilename = dataDir + \"ValidationData\";\n", + "validation.write.mode(\"overwrite\").parquet(validfilename)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "----------------------------------\n", + "## Data ingestion: Read in the training data from parquet file" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "## READ IN DATA FRAME FROM CSV\n", + "trainfilename = dataDir + \"TrainData\";\n", + "train_df = spark.read.parquet(trainfilename)\n", + "\n", + "## PERSIST AND MATERIALIZE DF IN MEMORY \n", + "train_df.persist()\n", + "train_df.count()\n", + "\n", + "## REGISTER DATA-FRAME AS A TEMP-TABLE IN SQL-CONTEXT\n", + "train_df.createOrReplaceTempView(\"train\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- ArrDel15: string (nullable = true)\n", + " |-- Year: integer (nullable = true)\n", + " |-- Month: integer (nullable = true)\n", + " |-- DayOfMonth: integer (nullable = true)\n", + " |-- DayOfWeek: integer (nullable = true)\n", + " |-- Carrier: string (nullable = true)\n", + " |-- OriginAirportID: integer (nullable = true)\n", + " |-- ORIGIN: string (nullable = true)\n", + " |-- DestAirportID: integer (nullable = true)\n", + " |-- DEST: string (nullable = true)\n", + " |-- CRSDepTime: long (nullable = true)\n", + " |-- VisibilityOrigin: double (nullable = true)\n", + " |-- DryBulbCelsiusOrigin: double (nullable = true)\n", + " |-- DewPointCelsiusOrigin: double (nullable = true)\n", + " |-- RelativeHumidityOrigin: double (nullable = true)\n", + " |-- WindSpeedOrigin: double (nullable = true)\n", + " |-- AltimeterOrigin: double (nullable = true)\n", + " |-- VisibilityDest: double (nullable = true)\n", + " |-- DryBulbCelsiusDest: double (nullable = true)\n", + " |-- DewPointCelsiusDest: double (nullable = true)\n", + " |-- RelativeHumidityDest: double (nullable = true)\n", + " |-- WindSpeedDest: double (nullable = true)\n", + " |-- AltimeterDest: double (nullable = true)" + ] + } + ], + "source": [ + "train_df.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "----------------------------------\n", + "## Data exploration & visualization: Plotting of target variables and features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### First, summarize data using SQL, this outputs a Spark data frame. If the data-set is too large, it can be sampled\n", + "NOTE: -m sample indicates that the datafame is being randomly sampled, -r 0.5 indicates 50% of rows are sampled, and -n -1 indicates all the rows are returned after sampling, prior to the spark dataframe being returned to local memory of head-node as pandas dataframe." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%sql -q -o sqlResultsPD -m sample -r 0.5 -n -1\n", + "SELECT ArrDel15, WindSpeedDest, WindSpeedOrigin FROM train" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Plot histogram of tip amount, relationship between tip amount vs. other features" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nI53QWOtVqA/Y5loV0qyYZRbdG33tRHr26dsibVi2eBE3XnB2Xoue7bDDDuyyyy5Mnz69RvmsWbMI\nIXDSSSdtVDd9+nQkccghhwDQrl27xh1APVx//fWsW7eOBQsW0LNnzxp1Ld071hiDBg3ihBNOqH78\ni1/8ggULFnDkkUfy3e9+l1deeYXu3bsXfL+t+XZJTmisWiE+YBvzAWn2RdSzT1/67LVvSzcjL4MG\nDeJ//ud/+OSTT+jQoQMAM2bMYO+99+boo49m1KhRNeKzE5revXtz+OGH84c//AGAe+65h5EjRzJ9\n+nQmTZrEfffdx7p16xg6dCi3334722+/fY3tXXnlldx22228//77HHTQQUyYMGGjNi5evJiePXtu\nlMwAG12a6d27N/vuuy+jRo3il7/8Ja+++ip9+vThyiuv5Pjjj68RW1lZyWWXXcYjjzzCypUr2Wmn\nnTjjjDO44IILalwmCiFw4403cscdd/DGG2+wzTbbcNxxx3H11VfTpUuXBh9PXfbZZx9uuOEGTj75\nZCZOnMi4ceOq69555x0uvfRSnnrqKVavXk3fvn0577zzGDlyZI1tTJgwgdtuu40lS5bQoUMHdt11\nV8477zyGDx/O5ZdfzuWXX44kevfuDcRLYkuWLGkVn/dOaGwjxfwBa2bNZ9CgQdx3333Mnj2bwYMH\nAzGhGThwIAcffDCrV6/mX//6F3vvvTcQL1fssccebLvttkDu8S4Ao0aNYrvttmPs2LG8+eabjB8/\nnrPPPpuysrLqmF//+tdcddVVDBs2jKOPPpq5c+cydOhQPvvssxrb2nnnnZk6dSrPPffcJi/DSOK1\n115j+PDhnHXWWZx66qncddddnHTSSUyZMoUhQ4YA8NFHHzF48GDeffddzjrrLHbaaSdmzpzJRRdd\nxPLly7n++uurt3nmmWfyxz/+kdNOO43Ro0ezZMkSJkyYwPz585kxYwZt27Zt0PFsyne/+11OP/10\nnn766eqEZuXKlRx44IG0bduWc845h65du/KXv/yF008/nTVr1nDOOecAcPvttzN69Gi+973vce65\n5/Lxxx/z0ksvMXv2bIYPH84JJ5zAa6+9xoMPPsiNN95YnWB++ctfblAbm4oTGjMzy8ugQYMIITB9\n+nQGDx5MVVUVs2fPZuTIkfTp04fu3bszffp09t57bz788EMWLFjA6aefvsntfvnLX2by5MnVj6uq\nqpgwYQJr1qyhU6dOVFRUcO211/Ltb3+bxx57rDru0ksv5Te/+U2NbZ1zzjncd999DBkyhK997Wt8\n4xvf4LDDDuPII49kyy233Gjfr7/+Oo888kj1uKDTTjuNPfbYgwsvvJB//OMfAFx33XUsWbKE+fPn\n06dPHwDOOOMMdthhB/7rv/6L8847j6985StMnz6dO++8k7KyMr7//e9X7+Owww7jqKOO4uGHH2b4\n8OENOp5N2WKLLdhtt9144403qssuvvhiQgjMnz+/ulfozDPP5OSTT2bs2LH85Cc/oUOHDjz11FPs\nvffePPjggzm3vc8++9C/f38efPBBjj322FbRK5PmadtmZpaXfv36sf3221ePlZk/fz7r1q1j4MCB\nAAwcOJAZM2YAsXemqqqqejBxbSRx5pln1ig79NBDqaqq4q233gLg2Wef5bPPPtvokta555670fb2\n3HNP5s+fzw9/+EPeeustbrrpJo477ji6d+/OHXfcsVH8jjvuWGOQc6dOnTjllFOYN28eK1euBGDS\npEkceuihbLPNNvzf//1f9c+QIUNYv349L7zwAhBngnXp0oUhQ4bUiNt///3Zeuutee655wB45pln\n6n089bH11luzZs2a6sePPPII3/72t6mqqqrRjqFDh7J69Wrmzp0LQJcuXVi2bFl14lZs3ENjZmZ5\nGzhwINOmTQPi5aZu3bqxyy67VNfdfPPN1XWSNpnQAOy00041HmcuUa1atQqgOrHp27fmWL+uXbtW\nx6b17duXe+65hxACr7zyCk888QTXXHMNP/nJT+jTpw+HH354jdhsu+22GwBvvvkm3bp14/XXX2fB\nggU5L7VIqk58Fi1axOrVq+nWrVudcUuXLm3Q8WzKhx9+SKdOnQB47733WL16Nf/93//NbbfdVmc7\nLrzwQqZOncoBBxxA3759GTp0KCeffHJ1gtraOaExM7O8DRo0iCeeeIIFCxYwc+bMGn/8Bg4cyC9/\n+UveffddZsyYwY477lg9mLQumXElaSGERs+wkcRee+3FXnvtxUEHHcRhhx3G/fffXyOhqY8NGzZw\n5JFHcuGFF+ZsUyYB2rBhA927d+eBBx7IGdcUY0/Wr1/Pa6+9xj777FPdBoARI0bwox/9KOdz9t03\njpncY489WLhwIU888QSTJ0/mkUce4fe//z2XXXYZl112WcHbWmhOaMzMLG+ZHpdp06YxY8YMxowZ\nU11XUlJChw4deO6555g9ezbHHHNM3vtJDyDeeeedgTjeJZ0gVVRUVPfibMqAAQMAePfdd2uUL1q0\naKPYhQsXAlTva9ddd+XDDz/c5CDjXXfdlalTpzJw4MDqWWC5FOJ4Mh5++GE++ugjvvnNbwIxaerU\nqRNVVVX1Sty23HJLTjrpJE466STWr1/P8ccfz1VXXcVFF11E+/btC74QYiF5DI2ZmeVtwIABdOjQ\ngfvvv5933nmnRg9N+/bt2X///bn55ptZt25dvS431ccRRxzBFltssdG05vHjx28UO336dNavX79R\n+ZNPPgnEXom0d955h0cffbT68QcffMC9997L/vvvX33p6Hvf+171irnZKisrqaqqqo5bv349V1xx\nxUZxVVVVVFZWNvh46vLPf/6Tc889l+23356f/exnALRp04YTTzyRP/3pT7z88ssbPSe9Fs/7779f\no26LLbagX79+hBCqZ1tttdVWAKxevbpBbWsO7qExM2tByxZv3CNQTPtu164dX//615k2bRpf+tKX\nKCkpqVE/cOBArrvuunqPn6ntslK6vGvXrpx//vlcffXVDBs2jG9961vMmzePyZMnb3QZ53e/+x1z\n5szhhBNOqL60MmfOHO699166du3K6NGja8Tvtttu/PjHP+bvf/873bt3584772TlypXcc8891TEX\nXHABjz/+OMOGDePUU0+lpKSEtWvX8tJLL/HII4/w5ptvst122zF48GB+8pOfcPXVVzN//nyGDh1K\nu3bteO2115g0aRI33XQTJ5xwQoOOJ+OFF17go48+qh7oO2PGDB5//HG23XZbHn300Rrjdq6++mr+\n9re/ceCBB3LGGWew55578v777zNnzhz++te/Vic1Q4cOpUePHhxyyCF0796dV155hZtvvplhw4ZV\nJzIlJSWEELj44osZPnw47dq14zvf+U7OGWPNzQmNmVkL6Nq1Kx07duTGC85u0XZ07Nix0ff+GTRo\nENOnT2fAgAEbrf57yCGHcP3119O5c2f222+/GnWSNrqEUdsljezyq666ii233JJbb72Vv/3tbxx0\n0EE8/fTTHHPMMTViL7nkEh544AGef/55HnjgAdatW8cOO+zAySefzKWXXlp9uSfjq1/9KhMmTOD8\n88/ntddeY5ddduGhhx7iiCOOqI7ZcssteeGFF/jNb37Dww8/zL333kvnzp3ZbbfduOKKK9hmm22q\n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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\ndghSBari30gz+xbwaeAzRTbXAA60FJS3RNtkE3TooYemHYKIiHRD6gmImX0SuBj4N3d/L+14JBsK\n24SIiEi2VMMjmFHAR4FGM3vPzN4DDgHONLP1hDsdBgwqeN0gYEVHBz7iiCOora3NW0aPHs2NN96Y\nt99tt91GbW1tu9effvrpzJ07N6+ssbGR2tpaVq1alVc+bdo0Zs6cmVe2bNkyamtraW5uziuvq6tj\nypQpeWVr1qyhtra2Xevw+vr6osMWH3vssZv0deTOm/XryNF16Dr68nWMHj2aAw44oO3v8Lx58zjw\nwAOpqamhtraW+vr6TFxHX/l59NR1XHbZZXmfr8OGDePoo49ud4xSzN27vHNvMLOtgJ0Liq8AmoAL\n3L3JzF4BLnT3i6LXbEtITE5w9+uLHHMk0NDQ0MDIkSN7NX5JR11dHZMnT047DBFJYMSIEfzrX/9K\nOwzpBY2NjYwaNQpglLs3drRv6o9g3H01sCheZmargdfcvSkquhg4x8yWELrhng+8BNxUwVCliij5\nEMmuoUOHph2CVIHUE5AS8m7LuPssMxsIzAG2B+4DDtcYICIiItlUlQmIu3+pSNl0YHrFgxERkR6l\nyegEqqMRqkjZChtmiUh27L///mmHIFVACYhk0tlnn512CCKSkOqvgBIQyajZs2enHYKIJKT6K6AE\nRDJqyJAhaYcgIgmp/gooAREREZEUKAERERGRilMCIplUOGyxiGSH6q+AEhDJqDVr1qQdgogkpPor\noAREMmrGjBlphyAiCan+CigBkYzq379/2iGISEJbb7112iFIFVACIpm0bt26tEMQkYRWr16ddghS\nBZSAiIiISMUpAREREZGKUwIimdC/f3/MrG0B8tbVJkSkem299dYd1l+1Cdk0bZ52ACJd0dramrdu\nZrh7StGISDnefffdvHXVXwHdAREREZEUKAERERGRilMCIiIiIhWnBERERCpqwIABaYcgVUAJiGRC\nvMV8sVb0uTIRqT6TJ0+mpqambVm7dm3e+uTJk9MOUVKgXjCSCYUt5tWKXiQ76urqqKura1uvqalh\nxYoVKUYk1UB3QERERKTilICIiIhIxSkBERGRilIjVAElIJJR8+fPTzsEEUlozpw5aYcgVUAJiGTS\nmDFj0g5BRBJ67bXX0g5BqoASEBERqaj6+vq0Q5AqoAREMknjfohk18svv5x2CFIFlICIiEhFPfvs\n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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "## %%local creates a pandas data-frame on the head node memory, from spark data-frame, \n", + "## which can then be used for plotting. Here, sampling data is a good idea, depending on the memory of the head node\n", + "\n", + "# TIP BY PAYMENT TYPE AND PASSENGER COUNT\n", + "ax1 = sqlResultsPD[['WindSpeedDest']].plot(kind='hist', bins=25, facecolor='lightblue')\n", + "ax1.set_title('WindSpeed @ Destination distribution')\n", + "ax1.set_xlabel('WindSpeedDest'); ax1.set_ylabel('Counts');\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()\n", + "\n", + "# TIP BY PASSENGER COUNT\n", + "ax2 = sqlResultsPD.boxplot(column=['WindSpeedDest'], by=['ArrDel15'])\n", + "ax2.set_title('WindSpeed Destination')\n", + "ax2.set_xlabel('ArrDel15'); ax2.set_ylabel('WindSpeed');\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "----------------------------------\n", + "## Feature engineering, transformation and data preparation for modeling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Split data into train/test. Training fraction will be used to create model, and testing fraction will be used to evaluate model." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "trainingFraction = 0.75; testingFraction = (1-trainingFraction);\n", + "seed = 1234;\n", + "\n", + "# SPLIT SAMPLED DATA-FRAME INTO TRAIN/TEST, WITH A RANDOM COLUMN ADDED FOR DOING CV (SHOWN LATER)\n", + "trainPartition, testPartition = train_df.randomSplit([trainingFraction, testingFraction], seed=seed);\n", + "\n", + "# CACHE DATA FRAMES IN MEMORY\n", + "trainPartition.persist(); trainPartition.count()\n", + "testPartition.persist(); testPartition.count()\n", + "\n", + "trainPartition.createOrReplaceTempView(\"TrainPartition\")\n", + "testPartition.createOrReplaceTempView(\"TestPartition\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Filter out null values, and filter test data by categories of features to ensure transformations trained on test data will fit the the test data" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "## EXAMPLES BELOW ALSO SHOW HOW TO USE SQL DIRECTLY ON DATAFRAMES\n", + "trainPartitionFilt = trainPartition.filter(\"ArrDel15 is not NULL and DayOfMonth is not NULL and DayOfWeek is not NULL \\\n", + " and Carrier is not NULL and OriginAirportID is not NULL and DestAirportID is not NULL \\\n", + " and CRSDepTime is not NULL and VisibilityOrigin is not NULL and DryBulbCelsiusOrigin is not NULL \\\n", + " and DewPointCelsiusOrigin is not NULL and RelativeHumidityOrigin is not NULL \\\n", + " and WindSpeedOrigin is not NULL and AltimeterOrigin is not NULL \\\n", + " and VisibilityDest is not NULL and DryBulbCelsiusDest is not NULL \\\n", + " and DewPointCelsiusDest is not NULL and RelativeHumidityDest is not NULL \\\n", + " and WindSpeedDest is not NULL and AltimeterDest is not NULL \")\n", + "trainPartitionFilt.persist(); trainPartitionFilt.count()\n", + "trainPartitionFilt.createOrReplaceTempView(\"TrainPartitionFilt\")\n", + "\n", + "testPartitionFilt = testPartition.filter(\"ArrDel15 is not NULL and DayOfMonth is not NULL and DayOfWeek is not NULL \\\n", + " and Carrier is not NULL and OriginAirportID is not NULL and DestAirportID is not NULL \\\n", + " and CRSDepTime is not NULL and VisibilityOrigin is not NULL and DryBulbCelsiusOrigin is not NULL \\\n", + " and DewPointCelsiusOrigin is not NULL and RelativeHumidityOrigin is not NULL \\\n", + " and WindSpeedOrigin is not NULL and AltimeterOrigin is not NULL \\\n", + " and VisibilityDest is not NULL and DryBulbCelsiusDest is not NULL \\\n", + " and DewPointCelsiusDest is not NULL and RelativeHumidityDest is not NULL \\\n", + " and WindSpeedDest is not NULL and AltimeterDest is not NULL\") \\\n", + " .filter(\"OriginAirportID IN (SELECT distinct OriginAirportID FROM TrainPartitionFilt) \\\n", + " AND ORIGIN IN (SELECT distinct ORIGIN FROM TrainPartitionFilt) \\\n", + " AND DestAirportID IN (SELECT distinct DestAirportID FROM TrainPartitionFilt) \\\n", + " AND DEST IN (SELECT distinct DEST FROM TrainPartitionFilt) \\\n", + " AND Carrier IN (SELECT distinct Carrier FROM TrainPartitionFilt) \\\n", + " AND CRSDepTime IN (SELECT distinct CRSDepTime FROM TrainPartitionFilt) \\\n", + " AND DayOfMonth in (SELECT distinct DayOfMonth FROM TrainPartitionFilt) \\\n", + " AND DayOfWeek in (SELECT distinct DayOfWeek FROM TrainPartitionFilt)\")\n", + "testPartitionFilt.persist(); testPartitionFilt.count()\n", + "testPartitionFilt.createOrReplaceTempView(\"TestPartitionFilt\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Indexing features using pipeline transformations" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "213808" + ] + } + ], + "source": [ + "# TRANSFORM SOME FEATURES BASED ON MLLIB TRANSFORMATION FUNCTIONS\n", + "from pyspark.ml import Pipeline\n", + "from pyspark.ml.feature import StringIndexer, VectorIndexer, Bucketizer, Binarizer\n", + "\n", + "sI0 = StringIndexer(inputCol = 'ArrDel15', outputCol = 'ArrDel15_ind'); bin0 = Binarizer(inputCol = 'ArrDel15_ind', outputCol = 'ArrDel15_bin', threshold = 0.5);\n", + "sI1 = StringIndexer(inputCol=\"Carrier\", outputCol=\"Carrier_ind\");\n", + "transformPipeline = Pipeline(stages=[sI0, bin0, sI1]);\n", + "\n", + "transformedTrain = transformPipeline.fit(trainPartition).transform(trainPartitionFilt)\n", + "transformedTest = transformPipeline.fit(trainPartition).transform(testPartitionFilt)\n", + "\n", + "transformedTrain.persist(); transformedTrain.count();\n", + "transformedTest.persist(); transformedTest.count();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "----------------------------------\n", + "## Train a regression model: Predict the amount of tip paid for taxi trips" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define the training formula and transformations that's to be applied to all training pipelines" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from pyspark.ml.feature import RFormula\n", + "\n", + "## DEFINE REGRESSION FURMULA\n", + "regFormula = RFormula(formula=\"ArrDel15_ind ~ \\\n", + " DayOfMonth + DayOfWeek + Carrier_ind + OriginAirportID + DestAirportID + CRSDepTime \\\n", + " + VisibilityOrigin + DryBulbCelsiusOrigin + DewPointCelsiusOrigin \\\n", + " + RelativeHumidityOrigin + WindSpeedOrigin + AltimeterOrigin \\\n", + " + VisibilityDest + DryBulbCelsiusDest + DewPointCelsiusDest \\\n", + " + RelativeHumidityDest + WindSpeedDest + AltimeterDest\");\n", + "\n", + "## DEFINE INDEXER FOR CATEGORIAL VARIABLES\n", + "## NOTE: Some categorical features (such as origin and destination airports have > 240 categories, which is why \n", + "## maxCategories is set to 250)\n", + "featureIndexer = VectorIndexer(inputCol=\"features\", outputCol=\"indexedFeatures\", maxCategories=250)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Train Elastic Net classification model, and evaluate performance on test data" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Area under ROC = 0.6811543852577218" + ] + } + ], + "source": [ + "from pyspark.ml.classification import LogisticRegression\n", + "from pyspark.mllib.evaluation import BinaryClassificationMetrics\n", + "from sklearn.metrics import roc_curve,auc\n", + "\n", + "## DEFINE ELASTIC NET REGRESSOR\n", + "eNet = LogisticRegression(featuresCol=\"indexedFeatures\", maxIter=25, regParam=0.01, elasticNetParam=0.5)\n", + "\n", + "## TRAINING PIPELINE: Fit model, with formula and other transformations\n", + "model = Pipeline(stages=[regFormula, featureIndexer, eNet]).fit(transformedTrain)\n", + "\n", + "# SAVE MODEL\n", + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"logisticRegModel_\" + datestamp;\n", + "logRegDirfilename = modelDir + fileName;\n", + "model.save(logRegDirfilename)\n", + "\n", + "## Evaluate model on test set\n", + "predictions = model.transform(transformedTest)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "predictions.select(\"label\",\"probability\").createOrReplaceTempView(\"tmp_results\")\n", + "\n", + "metrics = BinaryClassificationMetrics(predictionAndLabels)\n", + "print(\"Area under ROC = %s\" % metrics.areaUnderROC)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Bring predictions to the local pandas dataframe for plotting using matplotlib\n", + "NOTE: -n -1 means all of the data from predictions_pddf are brought to the local dataframe [You cannot do this for large dataframes]" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%%sql -q -o predictions_pddf -n -1\n", + "SELECT label, probability from tmp_results" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nuophmJbb7ebhhx9mzpw5TJs2jccee6zoD2KMMaZEWDDNxfTpzqwwDzxQPPnPmzePl19+mblz53Lv\nvfcWz0GMMcaUCAumPuzfD/PmOTPDhIQUzzGGDh1K48aN6dixY/EcwBhjTImx+0x9GDUKqleHkSOL\n7xgVKlSwQGqMMZcIC6bZfPcdLFkCo0dDlSqBLo0xpiRs27aNihUr8tNPPwW6KCab8+fPExkZySuv\nvBLoovhlwTSb2bOdWWGGDQt0SUxZk5iYiMvlylwqVKhAvXr1GDp0KIcPH851vzfeeIMOHToQERFB\n5cqVad68Oc8//7zfe3HfeecdevToQc2aNalYsSJ169alb9++bNiwoTiqFvSeeeYZBg4cSH17+DYA\n7733HtHR0YSEhHDVVVeRkJDAhQsXLnr/o0eP8sADD1CvXj1CQkJo2LBhjrEh2T/vGUu5cuU4evRo\nZrry5cszYsQIxo8fz9mzZ4usjkXNrpl6+e03WLDAGXRUXNdKjfFHRHj++edp0KAB6enpbN26lfnz\n5/Ppp5/y7bffctlll2Wmdbvd9O/fn2XLltG+fXvGjRtHaGgomzZtYty4cSxbtoyPPvqImtnmDBw6\ndCiJiYlERUUxcuRIrrzySo4cOcI777zDLbfcwqeffsqf/vSnkq56wHz11VesW7eOrVu3BrooQeGD\nDz7gjjvu4Oabb2bWrFns3LmT8ePH88svvzB79uw89z948CBt2rTB5XLxl7/8hbp163L48GG2bduW\nI633591beHh4ltdDhw4lPj6exYsXc/fddxemesVHVcvEAkQBmpSUpLmZPFm1fHnVn37KNUm+HDp0\nSKdMmaJut7toMjSalJSkeb2PpdWCBQvU5XLlqFt8fLy6XC5dtmxZlvUTJkxQEdGnnnoqR16rVq3S\ncuXKaY8ePbKsnzx5soqIjhw50mcZFi5cqF988UUha1I4J0+eLNHjPfLII9qgQYMizfPUqVNFml9J\natasmUZFRemFCxcy1z3zzDNarlw53bNnT577d+/eXRs1aqQnTpzwmy63z3tubrvtNu3QoUOe6fL6\njsjYDkRpEcYYO83r4XY7I3jvvBOK4ml++/fvJyYmhhkzZnDs2LHCZ2jKrJiYGFSV77//PnNdeno6\nU6ZMoUmTJkyYMCHHPrfeeit33XUXH374YWaPID09nRdffJFmzZoxefJkn8caOHAgLVu29FseVWXG\njBk0b96ckJAQatWqRffu3dmxYwcAP/74Iy6Xi3/84x859nW5XDz33HOZrxMSEnC5XCQnJzNgwAAu\nv/xyYmJimDp1Ki6Xy+c1zFGjRlGxYkVSU1Mz133++ed069aN8PBwKleuTGxsLJ999pnfemRYuXIl\nN998c4717733Hj179qRu3bpUqlSJa665hvHjx+N2u7Oki42NpXnz5uzYsYP27dtTuXLlLA9g+eCD\nD2jfvj1VqlShatWq9OzZk127dmXJY+fOnQwdOpRGjRoREhJC7dq1ueeeezh+/PhF1aGoJCcnk5yc\nzP3334/L6wb74cOH43a7Wb58ud/99+zZw4cffsiTTz5JeHg4Z86c4fz583keNy0tLUe7Zte5c2c2\nb97Mr7/+enGVKWEWTD3eew/27IG//KXwee3Zs4eYmBhEhE2bNlGjRo3CZ2rKrH379gEQERGRuW7z\n5s2cOHGCAQMGZPnS8zZkyBBUlVWrVmXuc/z4cQYMGFCoR1YOGzaMxx9/nKuuuopJkyYxatQoQkJC\nCnSaNKMccXFxpKenM3HiRO677z7uvPNORISlS5fm2GfZsmV069aNatWqAbB+/Xo6dOhAWloaCQkJ\nTJw4kdTUVG6++Wa2b9/u9/iHDx/mwIEDPp+mtWDBAsLCwhg5ciQzZ86kZcuWjB07llGjRuWoQ0pK\nCj169CAqKooZM2ZkjtR/44036NmzJ2FhYUyaNImxY8eSnJxMTEwMBw4cyMxj7dq17Nu3j2HDhjFr\n1iz69+/PW2+9xa233npR7Xjs2LGLWvK65vjll18iIkRHR2dZX7t2berVq8eXX37pd/9169YhItSs\nWZNOnToREhJCSEgIPXr04Mcff8yRXlWJjY2latWqhIaG0qtXL/71r3/5zDs6Ohq3233RP5JKXFF2\nc4N5IY/TvJ07q950k89N+fL1119rrVq1tFmzZnr48OHCZ2iyKAunedevX68pKSl68OBBXb58udaq\nVUtDQ0P10KFDmWlnzJihLpdLV65cmWt+J06cUBHRPn36qKrqzJkz89wnL+vXr1cR0ccffzzXNPv3\n71cR0cTExBzbRETHjRuX+TohIUFFRAcNGpQjbZs2bfSmbH+U27ZtUxHRRYsWZa679tprc5zOTk9P\n16uvvlq7du3qtz4fffSRioiuXr06x7b09PQc6x588EGtUqWKnj17NnNdbGysulwunTt3bpa0aWlp\n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\nWrZsycaNGzOvIeZHzZo16dixIy+99BJpaWn07ds3y3YRYd68efTo0YPrrruOoUOHUrduXQ4dOsSG\nDRuoVq0aK1eu9HuMXr168e6772ZZ16ZNGyIiIhgyZAiPPPIIAAsXLszXj4GwsDBefvllhgwZQlRU\nFP369aNmzZocOHCA1atX065dO2bOnElYWBjt27dn0qRJnD17lrp167JmzRr2799/0e1VVNdMASZP\nnkyvXr3o3Lkz/fr1Y+fOncyePZv77ruPP/zhD5nptm3bRseOHUlISMgckHbZZZcxefJk7r77bmJi\nYhg8eDA//vgjM2fOpH379txxxx2Z+7dp04Ybb7yRli1bUq1aNZKSkpg/fz5XXXVVjtuPwPnB0LZt\nW5+DxYJCUQ4NDuYFH7fGPPGEani46rlzPkdQmyBUFm6N8VU3t9ut11xzjTZu3DjHE7USExM1JiZG\nw8PDNTQ0VK+//nodP36836fwrFixQrt166Y1atTQyy67TOvUqaNxcXH6ySef5FlOt9utU6dO1WbN\nmmmlSpX0iiuu0FtvvVW//PLLzDSnT5/W++67TyMiIrRatWrav39/TUlJUZfLpc8991xmuoSEBHW5\nXHrs2LFcjzdv3jx1uVwaHh6uZ86c8Znm66+/1j59+mjNmjU1JCREGzZsqP369dMNGzbkWZ8vv/xS\nXS6Xfvrpp1nWb9myRdu0aaOVK1fWevXq6ahRo3Tt2rXqcrmytFNsbKw2b9481/w/+eQT7d69u0ZE\nRGhoaKg2btxYhw0bpjt27MhMc/jwYe3du7defvnlGhERof369dOff/45R3uVlJUrV2pUVJSGhIRo\nZGSkPvvss3r+/PksaT7++ONcy7dkyRK98cYbNSQkRGvXrq2PPvqopqWlZUkzZswYjYqK0oiICK1Y\nsaI2aNBAH3roIT169GiO/FJTU7VixYo6f/78PMseqFtjRPP5S7G0EpEoICkpKYmoqCjOn4f69SEu\nzjnVezFUldTUVBupG0A7duwgOjqajPfRmKJwyy23UKdOHZ9PbTKBN336dKZMmcL333/v87Ydb3l9\nR2RsB6JVdUdRlbHMXjN9/334+We42Gcmqyrx8fG0bNnS57UZY0zpNWHCBJYuXWpTsAWh8+fPM336\ndMaMGZNnIA2kMnvNdNky+OMfL260p9vt5uGHH2bOnDlMmzaNypUrF38BjTElplWrVqSnpwe6GMaH\n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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "## PLOT ROC CURVE AFTER CONVERTING PREDICTIONS TO A PANDAS DATA FRAME\n", + "from sklearn.metrics import roc_curve,auc\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "labels = predictions_pddf[\"label\"]\n", + "prob = []\n", + "for dv in predictions_pddf[\"probability\"]:\n", + " prob.append(list(dv.values())[1][1])\n", + " \n", + "fpr, tpr, thresholds = roc_curve(labels, prob, pos_label=1);\n", + "roc_auc = auc(fpr, tpr)\n", + "\n", + "plt.figure(figsize=(5,5))\n", + "plt.plot(fpr, tpr, label='ROC curve (area = %0.2f)' % roc_auc)\n", + "plt.plot([0, 1], [0, 1], 'k--')\n", + "plt.xlim([0.0, 1.0]); plt.ylim([0.0, 1.05]);\n", + "plt.xlabel('False Positive Rate'); plt.ylabel('True Positive Rate');\n", + "plt.title('ROC Curve'); plt.legend(loc=\"lower right\");\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Train Gradient Boosting Tree binary classification model, and evaluate performance on test data" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Area under ROC = 0.7369255957114815" + ] + } + ], + "source": [ + "from pyspark.ml.regression import GBTRegressor\n", + "\n", + "## DEFINE GRADIENT BOOSTING TREE CLASSIFIER\n", + "gBT = GBTRegressor(featuresCol=\"indexedFeatures\", maxIter=10, maxBins = 250)\n", + "\n", + "## TRAINING PIPELINE: Fit model, with formula and other transformations\n", + "model = Pipeline(stages=[regFormula, featureIndexer, gBT]).fit(transformedTrain)\n", + "\n", + "# SAVE MODEL\n", + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"gbtModel_\" + datestamp;\n", + "gbtDirfilename = modelDir + fileName;\n", + "model.save(gbtDirfilename)\n", + "\n", + "## Evaluate model on test set\n", + "predictions = model.transform(transformedTest)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "\n", + "metrics = BinaryClassificationMetrics(predictionAndLabels)\n", + "print(\"Area under ROC = %s\" % metrics.areaUnderROC)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Train a random forest binary classification model using the Pipeline function, save, and evaluate on test data set" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Area under ROC = 0.7474663414748935" + ] + } + ], + "source": [ + "from pyspark.ml.classification import RandomForestClassifier\n", + "\n", + "## DEFINE RANDOM FOREST CLASSIFIER\n", + "randForest = RandomForestClassifier(featuresCol = 'indexedFeatures', labelCol = 'label', numTrees=20, \\\n", + " maxDepth=6, maxBins=250)\n", + "\n", + "## TRAINING PIPELINE: Fit model, with formula and other transformations\n", + "model = Pipeline(stages=[regFormula, featureIndexer, randForest]).fit(transformedTrain)\n", + "\n", + "# SAVE MODEL\n", + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"rfModel_\" + datestamp;\n", + "rfDirfilename = modelDir + fileName;\n", + "model.save(rfDirfilename)\n", + "\n", + "## Evaluate model on test set\n", + "predictions = model.transform(transformedTest)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "\n", + "metrics = BinaryClassificationMetrics(predictionAndLabels)\n", + "print(\"Area under ROC = %s\" % metrics.areaUnderROC)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "----------------------------------\n", + "## Hyper-parameter tuning: Train a random forest model using cross-validation" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Area under ROC = 0.755388760846736" + ] + } + ], + "source": [ + "from pyspark.ml.tuning import CrossValidator, ParamGridBuilder\n", + "from pyspark.ml.evaluation import BinaryClassificationEvaluator\n", + "\n", + "## DEFINE RANDOM FOREST MODELS\n", + "## DEFINE RANDOM FOREST CLASSIFIER\n", + "randForest = RandomForestClassifier(featuresCol = 'indexedFeatures', labelCol = 'label', numTrees=20, \\\n", + " maxDepth=6, maxBins=250)\n", + "\n", + "\n", + "## DEFINE MODELING PIPELINE, INCLUDING FORMULA, FEATURE TRANSFORMATIONS, AND ESTIMATOR\n", + "pipeline = Pipeline(stages=[regFormula, featureIndexer, randForest])\n", + "\n", + "## DEFINE PARAMETER GRID FOR RANDOM FOREST\n", + "paramGrid = ParamGridBuilder() \\\n", + " .addGrid(randForest.numTrees, [10, 25, 50]) \\\n", + " .addGrid(randForest.maxDepth, [3, 5, 7]) \\\n", + " .build()\n", + "\n", + "## DEFINE CROSS VALIDATION\n", + "crossval = CrossValidator(estimator=pipeline,\n", + " estimatorParamMaps=paramGrid,\n", + " evaluator=BinaryClassificationEvaluator(metricName=\"areaUnderROC\"),\n", + " numFolds=3)\n", + "\n", + "## TRAIN MODEL USING CV\n", + "cvModel = crossval.fit(transformedTrain)\n", + "\n", + "## Evaluate model on test set\n", + "predictions = cvModel.transform(transformedTest)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "metrics = BinaryClassificationMetrics(predictionAndLabels)\n", + "print(\"Area under ROC = %s\" % metrics.areaUnderROC)\n", + "\n", + "## SAVE THE BEST MODEL\n", + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"CV_RandomForestRegressionModel_\" + datestamp;\n", + "CVDirfilename = modelDir + fileName;\n", + "cvModel.bestModel.save(CVDirfilename);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "----------------------------------\n", + "## Load a saved pipeline model and evaluate it on test data set" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Area under ROC = 0.6811543852577218" + ] + } + ], + "source": [ + "from pyspark.ml import PipelineModel\n", + "\n", + "savedModel = PipelineModel.load(logRegDirfilename)\n", + "\n", + "## Evaluate model on test set\n", + "predictions = savedModel.transform(transformedTest)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "metrics = BinaryClassificationMetrics(predictionAndLabels)\n", + "print(\"Area under ROC = %s\" % metrics.areaUnderROC)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load and transform an independent validation data-set, and evaluate the saved pipeline model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Note that this validation data, by design, has a different format than the original trainig data. By grangling and transformations, we make the data format the same as the training data for the purpose of scoring." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- ArrDel15: string (nullable = true)\n", + " |-- Year: integer (nullable = true)\n", + " |-- Month: integer (nullable = true)\n", + " |-- DayOfMonth: integer (nullable = true)\n", + " |-- DayOfWeek: integer (nullable = true)\n", + " |-- Carrier: string (nullable = true)\n", + " |-- OriginAirportID: integer (nullable = true)\n", + " |-- ORIGIN: string (nullable = true)\n", + " |-- DestAirportID: integer (nullable = true)\n", + " |-- DEST: string (nullable = true)\n", + " |-- CRSDepTime: long (nullable = true)\n", + " |-- VisibilityOrigin: double (nullable = true)\n", + " |-- DryBulbCelsiusOrigin: double (nullable = true)\n", + " |-- DewPointCelsiusOrigin: double (nullable = true)\n", + " |-- RelativeHumidityOrigin: double (nullable = true)\n", + " |-- WindSpeedOrigin: double (nullable = true)\n", + " |-- AltimeterOrigin: double (nullable = true)\n", + " |-- VisibilityDest: double (nullable = true)\n", + " |-- DryBulbCelsiusDest: double (nullable = true)\n", + " |-- DewPointCelsiusDest: double (nullable = true)\n", + " |-- RelativeHumidityDest: double (nullable = true)\n", + " |-- WindSpeedDest: double (nullable = true)\n", + " |-- AltimeterDest: double (nullable = true)" + ] + } + ], + "source": [ + "## READ IN DATA FRAME FROM PARQUET\n", + "validfilename = dataDir + \"ValidationData\";\n", + "validPartition = spark.read.parquet(validfilename)\n", + "validPartition.persist(); validPartition.count()\n", + "validPartition.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### It is a good idea to filter the validation dataset for null values, as well as, categorical values not observed in the training data set. Otherwise, errors could be thrown at scoring time" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "validPartitionFilt = validPartition.filter(\"ArrDel15 is not NULL and DayOfMonth is not NULL and DayOfWeek is not NULL \\\n", + " and Carrier is not NULL and OriginAirportID is not NULL and DestAirportID is not NULL \\\n", + " and CRSDepTime is not NULL and VisibilityOrigin is not NULL and DryBulbCelsiusOrigin is not NULL \\\n", + " and DewPointCelsiusOrigin is not NULL and RelativeHumidityOrigin is not NULL \\\n", + " and WindSpeedOrigin is not NULL and AltimeterOrigin is not NULL \\\n", + " and VisibilityDest is not NULL and DryBulbCelsiusDest is not NULL \\\n", + " and DewPointCelsiusDest is not NULL and RelativeHumidityDest is not NULL \\\n", + " and WindSpeedDest is not NULL and AltimeterDest is not NULL\") \\\n", + " .filter(\"OriginAirportID IN (SELECT distinct OriginAirportID FROM TrainPartitionFilt) \\\n", + " AND ORIGIN IN (SELECT distinct ORIGIN FROM TrainPartitionFilt) \\\n", + " AND DestAirportID IN (SELECT distinct DestAirportID FROM TrainPartitionFilt) \\\n", + " AND DEST IN (SELECT distinct DEST FROM TrainPartitionFilt) \\\n", + " AND Carrier IN (SELECT distinct Carrier FROM TrainPartitionFilt) \\\n", + " AND CRSDepTime IN (SELECT distinct CRSDepTime FROM TrainPartitionFilt) \\\n", + " AND DayOfMonth in (SELECT distinct DayOfMonth FROM TrainPartitionFilt) \\\n", + " AND DayOfWeek in (SELECT distinct DayOfWeek FROM TrainPartitionFilt)\")\n", + "validPartitionFilt.persist(); validPartitionFilt.count()\n", + "validPartitionFilt.createOrReplaceTempView(\"ValidPartitionFilt\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### We need to apply the same transformation to the validation data as the ones that were applied to the training set, as in the transformPipeline function defined above" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "transformedValid = transformPipeline.fit(trainPartition).transform(validPartitionFilt)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Load saved model, score validation data and evaluate" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Area under ROC = 0.6944995086466054" + ] + } + ], + "source": [ + "savedModel = PipelineModel.load(logRegDirfilename)\n", + "predictions = savedModel.transform(transformedValid)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "metrics = BinaryClassificationMetrics(predictionAndLabels)\n", + "print(\"Area under ROC = %s\" % metrics.areaUnderROC)" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "celltoolbar": "Raw Cell Format", + "kernelspec": { + "display_name": "PySpark3", + "language": "", + "name": "pyspark3kernel" + }, + "language_info": { + "codemirror_mode": { + "name": "python", + "version": 3 + }, + "mimetype": "text/x-python", + "name": "pyspark3", + "pygments_lexer": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/Misc/Spark/pySpark/Spark2.0/Spark2.0_pySpark3_NYC_Taxi_Tip_Regression.ipynb b/Misc/Spark/pySpark/Spark2.0/Spark2.0_pySpark3_NYC_Taxi_Tip_Regression.ipynb new file mode 100644 index 00000000..846a2dc6 --- /dev/null +++ b/Misc/Spark/pySpark/Spark2.0/Spark2.0_pySpark3_NYC_Taxi_Tip_Regression.ipynb @@ -0,0 +1,1099 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exploration and Modeling of 2013 NYC Taxi Trip and Fare Dataset on Spark 2.0 HDInsight Clusters (pySpark3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Last updated:\n", + "February 07, 2016" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---------------------------------\n", + "### Here we show key features and capabilities of Spark's MLlib toolkit using the NYC taxi trip and fare data-set from 2013 (about 40 Gb uncompressed). We take about 10% of the sample of this data (from the month of December 2013, about 3.6 Gb) to predict the amount of tip paid for each taxi trip in NYC based on features such as trip distance, number of passengers, trip distance etc. We have shown relevant plots in Python.\n", + "\n", + "### We have sampled the data in order for the runs to finish quickly. The same code (with minor modifications) can be run on the full 2013 data-set.\n", + "\n", + "### This notebook takes about 15 mins to run on a 2 worker-node cluster(D12_V2).\n", + "\n", + "\n", + "### The nobebook runs in the PySpark3 kernel in Jupyter.\n", + "----------------------------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### OBJECTIVE: Show use of Spark MLlib's functions for featurization and ML tasks.\n", + "\n", + "### The learning task is to predict the amount of tip paid in (dollars) paid for each taxi trip in December 2013\n", + "\n", + "#### We have shown the following steps:\n", + "1. Data ingestion, joining, and wrangling.\n", + "2. Data exploration and plotting.\n", + "3. Data preparation (featurizing/transformation).\n", + "4. Modeling (using incl. hyperparameter tuning with cross-validation), prediction, model persistance.\n", + "5. Model evaluation on an independent validation data-set.\n", + "\n", + "Through the above steps we highlight Spark SQL, as well as, MLlib's modeling and transformation functions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Introductory material\n", + "\n", + "NYC 2013 Taxi data:\n", + "http://www.andresmh.com/nyctaxitrips/\n", + "https://azure.microsoft.com/en-us/documentation/articles/machine-learning-data-science-process-hive-walkthrough/\n", + "\n", + "An earlier version of this walkthrough was published in a set of notebooks to run on Spark 1.6 HDInsight clusters: https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-overview. That walkthrough has the detailed description of the NYC Taxi data set and its features. Therefore, we do not duplicate that information here.\n", + "\n", + "----------------------------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set directory paths and location of training, validation files, as well as model location in blob storage\n", + "NOTE: The blob storage attached to the HDI cluster is referenced as: wasb:/// (Windows Azure Storage Blob). Other blob storage accounts are referenced as: wasb://" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting Spark application\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
IDYARN Application IDKindStateSpark UIDriver logCurrent session?
21application_1485304066110_0070pyspark3idleLinkLinkâś”
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SparkSession available as 'spark'.\n" + ] + } + ], + "source": [ + "# 1. Location of training data: contains Dec 2013 trip and fare data from NYC \n", + "trip_file_loc = \"wasb://data@cdspsparksamples.blob.core.windows.net/NYCTaxi/KDD2016/trip_data_12.csv\"\n", + "fare_file_loc = \"wasb://data@cdspsparksamples.blob.core.windows.net/NYCTaxi/KDD2016/trip_fare_12.csv\"\n", + "\n", + "# 2. Location of the joined taxi+fare training file\n", + "taxi_valid_file_loc = \"wasb://mllibwalkthroughs@cdspsparksamples.blob.core.windows.net/Data/NYCTaxi/JoinedTaxiTripFare.Point1Pct.Valid.csv\"\n", + "\n", + "# 3. Set model storage directory path. This is where models will be saved.\n", + "modelDir = \"wasb:///user/remoteuser/NYCTaxi/Models/\"; # The last backslash is needed;\n", + "\n", + "# 4. Set data storage path. This is where data is sotred on the blob attached to the cluster.\n", + "dataDir = \"wasb:///HdiSamples/HdiSamples/NYCTaxi/\"; # The last backslash is needed;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set SQL context and import necessary libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from pyspark import SparkConf\n", + "from pyspark import SparkContext\n", + "from pyspark.sql import SQLContext\n", + "from pyspark.sql.functions import UserDefinedFunction\n", + "from pyspark.sql.types import *\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import datetime\n", + "\n", + "sqlContext = SQLContext(sc)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data ingestion and wrangling using Spark SQL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Data import and registering as tables" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "## READ IN TRIP DATA FRAME FROM CSV\n", + "trip = spark.read.csv(path=trip_file_loc, header=True, inferSchema=True)\n", + "\n", + "## READ IN FARE DATA FRAME FROM CSV\n", + "fare = spark.read.csv(path=fare_file_loc, header=True, inferSchema=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- medallion: string (nullable = true)\n", + " |-- hack_license: string (nullable = true)\n", + " |-- vendor_id: string (nullable = true)\n", + " |-- rate_code: integer (nullable = true)\n", + " |-- store_and_fwd_flag: string (nullable = true)\n", + " |-- pickup_datetime: timestamp (nullable = true)\n", + " |-- dropoff_datetime: timestamp (nullable = true)\n", + " |-- passenger_count: integer (nullable = true)\n", + " |-- trip_time_in_secs: integer (nullable = true)\n", + " |-- trip_distance: double (nullable = true)\n", + " |-- pickup_longitude: double (nullable = true)\n", + " |-- pickup_latitude: double (nullable = true)\n", + " |-- dropoff_longitude: double (nullable = true)\n", + " |-- dropoff_latitude: double (nullable = true)\n", + "\n", + "root\n", + " |-- medallion: string (nullable = true)\n", + " |-- hack_license: string (nullable = true)\n", + " |-- vendor_id: string (nullable = true)\n", + " |-- pickup_datetime: timestamp (nullable = true)\n", + " |-- payment_type: string (nullable = true)\n", + " |-- fare_amount: double (nullable = true)\n", + " |-- surcharge: double (nullable = true)\n", + " |-- mta_tax: double (nullable = true)\n", + " |-- tip_amount: double (nullable = true)\n", + " |-- tolls_amount: double (nullable = true)\n", + " |-- total_amount: double (nullable = true)" + ] + } + ], + "source": [ + "## CHECK SCHEMA OF TRIP AND FARE TABLES\n", + "trip.printSchema()\n", + "fare.printSchema()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "## REGISTER DATA-FRAMEs AS A TEMP-TABLEs IN SQL-CONTEXT\n", + "trip.createOrReplaceTempView(\"trip\")\n", + "fare.createOrReplaceTempView(\"fare\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Using Spark SQL to join, clean and featurize data" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "## USING SQL: MERGE TRIP AND FARE DATA-SETS TO CREATE A JOINED DATA-FRAME\n", + "## ELIMINATE SOME COLUMNS, AND FILTER ROWS WTIH VALUES OF SOME COLUMNS\n", + "sqlStatement = \"\"\"SELECT t.medallion, t.hack_license,\n", + " f.total_amount, f.tolls_amount,\n", + " hour(f.pickup_datetime) as pickup_hour, f.vendor_id, f.fare_amount, \n", + " f.surcharge, f.tip_amount, f.payment_type, t.rate_code, \n", + " t.passenger_count, t.trip_distance, t.trip_time_in_secs \n", + " FROM trip t, fare f \n", + " WHERE t.medallion = f.medallion AND t.hack_license = f.hack_license \n", + " AND t.pickup_datetime = f.pickup_datetime \n", + " AND t.passenger_count > 0 and t.passenger_count < 8 \n", + " AND f.tip_amount >= 0 AND f.tip_amount <= 25 \n", + " AND f.fare_amount >= 1 AND f.fare_amount <= 250 \n", + " AND f.tip_amount < f.fare_amount AND t.trip_distance > 0 \n", + " AND t.trip_distance <= 100 AND t.trip_time_in_secs >= 30 \n", + " AND t.trip_time_in_secs <= 7200 AND t.rate_code <= 5\n", + " AND f.payment_type in ('CSH','CRD')\"\"\"\n", + "trip_fareDF = spark.sql(sqlStatement)\n", + "\n", + "# REGISTER JOINED TRIP-FARE DF IN SQL-CONTEXT\n", + "trip_fareDF.createOrReplaceTempView(\"trip_fare\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+---------------+-----------+\n", + "| tableName|isTemporary|\n", + "+---------------+-----------+\n", + "|hivesampletable| false|\n", + "| fare| true|\n", + "| trip| true|\n", + "| trip_fare| true|\n", + "+---------------+-----------+" + ] + } + ], + "source": [ + "## SHOW WHICH TABLES ARE REGISTERED IN SQL-CONTEXT\n", + "spark.sql(\"show tables\").show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Sample data for creating and evaluating model & save in blob" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# SAMPLE 10% OF DATA, SPLIT INTO TRAIINING AND VALIDATION AND SAVE IN BLOB\n", + "trip_fare_featSampled = trip_fareDF.sample(False, 0.1, seed=1234)\n", + "trainfilename = dataDir + \"TrainData\";\n", + "trip_fare_featSampled.repartition(10).write.mode(\"overwrite\").parquet(trainfilename)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Data ingestion & cleanup: Read in the joined taxi trip and fare training file (as parquet), format and clean data, and create data-frame" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The taxi trip and fare files were joined based on the instructions provided in: \n", + "\"https://azure.microsoft.com/en-us/documentation/articles/machine-learning-data-science-process-hive-walkthrough/\"" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "## READ IN DATA FRAME FROM CSV\n", + "taxi_train_df = spark.read.parquet(trainfilename)\n", + "\n", + "## CREATE A CLEANED DATA-FRAME BY DROPPING SOME UN-NECESSARY COLUMNS & FILTERING FOR UNDESIRED VALUES OR OUTLIERS\n", + "taxi_df_train_cleaned = taxi_train_df.drop('medallion').drop('hack_license').drop('total_amount').drop('tolls_amount')\\\n", + " .filter(\"passenger_count > 0 and passenger_count < 8 AND tip_amount >= 0 AND tip_amount < 15 AND \\\n", + " fare_amount >= 1 AND fare_amount < 150 AND trip_distance > 0 AND trip_distance < 100 AND \\\n", + " trip_time_in_secs > 30 AND trip_time_in_secs < 7200\" )\n", + "\n", + "## PERSIST AND MATERIALIZE DF IN MEMORY \n", + "taxi_df_train_cleaned.persist()\n", + "taxi_df_train_cleaned.count()\n", + "\n", + "## REGISTER DATA-FRAME AS A TEMP-TABLE IN SQL-CONTEXT\n", + "taxi_df_train_cleaned.createOrReplaceTempView(\"taxi_train\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- pickup_hour: integer (nullable = true)\n", + " |-- vendor_id: string (nullable = true)\n", + " |-- fare_amount: double (nullable = true)\n", + " |-- surcharge: double (nullable = true)\n", + " |-- tip_amount: double (nullable = true)\n", + " |-- payment_type: string (nullable = true)\n", + " |-- rate_code: integer (nullable = true)\n", + " |-- passenger_count: integer (nullable = true)\n", + " |-- trip_distance: double (nullable = true)\n", + " |-- trip_time_in_secs: integer (nullable = true)" + ] + } + ], + "source": [ + "taxi_df_train_cleaned.printSchema()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Data exploration & visualization: Plotting of target variables and features" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### First, summarize data using SQL, this outputs a Spark data frame. If the data-set is too large, it can be sampled" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%sql -q -o sqlResultsPD\n", + "SELECT fare_amount, passenger_count, tip_amount FROM taxi_train WHERE passenger_count > 0 AND passenger_count < 7 AND fare_amount > 0 AND fare_amount < 100 AND tip_amount > 0 AND tip_amount < 15" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Plot histogram of tip amount, relationship between tip amount vs. other features" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nu+++u9fbfeihh7j//vupr69nzJgxPPPMM1x22WXstddePPbYY2y88cad2p900klsueWWnHPOOdx/\n//386Ec/YsSIEdx7771svfXWnHfeedx6661cdNFFfOhDH2Ly5Mlrlj3ooIP44x//yLHHHsvOO+/M\nvHnzOOWUU3j++efXCiW90fF7OfTQQ6mtreX888+nsbGRH//4x4waNYrzzjsPgGuvvZYvfelL7Lrr\nrkydOhWA97///X3e3kBxGDEzs5Kw4447MnHiRK677joOPvjgXh3m2XjjjbnrrrsYMmQIAGPHjuXU\nU0/llltu4cADD+zVdg888EAOOeSQTtMOOuggPvKRj3DDDTdwxBFHdJq35ZZb8tvf/haA448/nief\nfJILL7yQE044gdmzZwPw5S9/mXHjxnHVVVetCSM33XQTd999N9/5znf45je/CcAJJ5zAoYceysyZ\nM5k2bRrbbLNNr2rOV1dXxw9/+MM1z1taWrjyyivXhJHDDz+c4447jtraWg4//PCCtjGQfJjGzMzW\nW1OnTl0TRCD5cB8yZAi33nprr9cxdOjQNf9etWoVr776KrW1tYwYMYLGxsZObSWtdXhj1113Beg0\nvaqqikmTJrFo0aI102677TY22GADTjrppE7Lf/3rX6e9vZ3bbrut1zXn13Tcccd1mrb77rvzyiuv\n8K9//augdQ42hxEzM1svSWL8+M5jVDbZZBO23HJLnnnmmV6v58033+TMM89k7NixDB06lJqaGkaO\nHElrayutra1rtc/vsRk+fDgA73vf+9aavmzZsjXPn332Wbbaais22WSTTu067r6cP0alL/Jr2myz\nzQA6bb+U+TCNmZlVtGnTpnHNNdfQ0NDARz7yEYYPH44kvvjFL9Le3r5W+9yemHVN7+7soJ50Nz6m\nq1rWVVMh28+Cw4iZmZWMvlzYLCJ48skn2XPPPddMW7FiBS+88AIHHHBAr9dzww03cNRRR3HBBRes\nmfbWW2/1+aycddl666256667WLFiRafekaampjXz4Z1ejfzt96fnBPr2ux1sPkxjZmYlo+NDurdB\n4Ic//CGrVq1a8/yyyy5j9erV7L///r3e5pAhQ9bqdZg1axarV6/u9Tp6Y//992fVqlVrBrl2mDFj\nBlVVVey3334AbLrpptTU1HDPPfd0anfppZf2K1BssskmRQ9YxeKeETOzMrJk0VPr9fbr6uqICP7n\n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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\nYkHHrtqsOwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "## %%local creates a pandas data-frame on the head node memory, from spark data-frame, \n", + "## which can then be used for plotting. Here, sampling data is a good idea, depending on the memory of the head node\n", + "\n", + "# TIP BY PAYMENT TYPE AND PASSENGER COUNT\n", + "ax1 = sqlResultsPD[['tip_amount']].plot(kind='hist', bins=25, facecolor='lightblue')\n", + "ax1.set_title('Tip amount distribution')\n", + "ax1.set_xlabel('Tip Amount ($)'); ax1.set_ylabel('Counts');\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()\n", + "\n", + "# TIP BY PASSENGER COUNT\n", + "ax2 = sqlResultsPD.boxplot(column=['tip_amount'], by=['passenger_count'])\n", + "ax2.set_title('Tip amount by Passenger count')\n", + "ax2.set_xlabel('Passenger count'); ax2.set_ylabel('Tip Amount ($)');\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()\n", + "\n", + "# TIP AMOUNT BY FARE AMOUNT, POINTS ARE SCALED BY PASSENGER COUNT\n", + "ax = sqlResultsPD.plot(kind='scatter', x= 'fare_amount', y = 'tip_amount', c='blue', alpha = 0.10, s=2.5*(sqlResultsPD.passenger_count))\n", + "ax.set_title('Tip amount by Fare amount')\n", + "ax.set_xlabel('Fare Amount ($)'); ax.set_ylabel('Tip Amount ($)');\n", + "plt.axis([-2, 80, -2, 20])\n", + "plt.figure(figsize=(4,4)); plt.suptitle(''); plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## Feature engineering, transformation and data preparation for modeling" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Create a new feature by binning hours into traffic time buckets using Spark SQL" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "### CREATE FOUR BUCKETS FOR TRAFFIC TIMES\n", + "sqlStatement = \"\"\"SELECT payment_type, pickup_hour, fare_amount, tip_amount, \n", + " vendor_id, rate_code, passenger_count, trip_distance, trip_time_in_secs, \n", + " CASE\n", + " WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN 'Night'\n", + " WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN 'AMRush' \n", + " WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN 'Afternoon'\n", + " WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN 'PMRush'\n", + " END as TrafficTimeBins,\n", + " CASE\n", + " WHEN (tip_amount > 0) THEN 1 \n", + " WHEN (tip_amount <= 0) THEN 0 \n", + " END as tipped\n", + " FROM taxi_train\"\"\"\n", + "\n", + "taxi_df_train_with_newFeatures = spark.sql(sqlStatement)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Indexing of categorical features\n", + "Here we only transform some variables to show examples, which are character strings. Other variables, such as week-day, which are represented by numerical valies, can also be indexed as categorical variables.\n", + "\n", + "For indexing, we used stringIndexer function from MLlib." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from pyspark.ml import Pipeline\n", + "from pyspark.ml.feature import OneHotEncoder, StringIndexer, VectorIndexer\n", + "\n", + "# DEFINE THE TRANSFORMATIONS THAT NEEDS TO BE APPLIED TO SOME OF THE FEATURES\n", + "sI1 = StringIndexer(inputCol=\"vendor_id\", outputCol=\"vendorIndex\");\n", + "sI2 = StringIndexer(inputCol=\"rate_code\", outputCol=\"rateIndex\");\n", + "sI3 = StringIndexer(inputCol=\"payment_type\", outputCol=\"paymentIndex\");\n", + "sI4 = StringIndexer(inputCol=\"TrafficTimeBins\", outputCol=\"TrafficTimeBinsIndex\");\n", + "\n", + "# APPLY TRANSFORMATIONS\n", + "encodedFinal = Pipeline(stages=[sI1, sI2, sI3, sI4]).fit(taxi_df_train_with_newFeatures).transform(taxi_df_train_with_newFeatures);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Split data into train/test. Training fraction will be used to create model, and testing fraction will be used to evaluate model." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "343766" + ] + } + ], + "source": [ + "trainingFraction = 0.75; testingFraction = (1-trainingFraction);\n", + "seed = 1234;\n", + "\n", + "# SPLIT SAMPLED DATA-FRAME INTO TRAIN/TEST, WITH A RANDOM COLUMN ADDED FOR DOING CV (SHOWN LATER)\n", + "trainData, testData = encodedFinal.randomSplit([trainingFraction, testingFraction], seed=seed);\n", + "\n", + "# CACHE DATA FRAMES IN MEMORY\n", + "trainData.persist(); trainData.count()\n", + "testData.persist(); testData.count()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train a regression model: Predict the amount of tip paid for taxi trips" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Train Elastic Net regression model, and evaluate performance on test data" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RMSE = 1.2417618432862056\n", + "R-sqr = 0.38028265612436984" + ] + } + ], + "source": [ + "from pyspark.ml.feature import RFormula\n", + "from pyspark.ml.regression import LinearRegression\n", + "from pyspark.mllib.evaluation import RegressionMetrics\n", + "\n", + "## DEFINE REGRESSION FURMULA\n", + "regFormula = RFormula(formula=\"tip_amount ~ paymentIndex + vendorIndex + rateIndex + TrafficTimeBinsIndex + pickup_hour + passenger_count + trip_time_in_secs + trip_distance + fare_amount\")\n", + "\n", + "## DEFINE INDEXER FOR CATEGORIAL VARIABLES\n", + "featureIndexer = VectorIndexer(inputCol=\"features\", outputCol=\"indexedFeatures\", maxCategories=32)\n", + "\n", + "## DEFINE ELASTIC NET REGRESSOR\n", + "eNet = LinearRegression(featuresCol=\"indexedFeatures\", maxIter=25, regParam=0.01, elasticNetParam=0.5)\n", + "\n", + "## Fit model, with formula and other transformations\n", + "model = Pipeline(stages=[regFormula, featureIndexer, eNet]).fit(trainData)\n", + "\n", + "## PREDICT ON TEST DATA AND EVALUATE\n", + "predictions = model.transform(testData)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "testMetrics = RegressionMetrics(predictionAndLabels)\n", + "print(\"RMSE = %s\" % testMetrics.rootMeanSquaredError)\n", + "print(\"R-sqr = %s\" % testMetrics.r2)\n", + "\n", + "## PLOC ACTUALS VS. PREDICTIONS\n", + "predictions.select(\"label\",\"prediction\").createOrReplaceTempView(\"tmp_results\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Train Gradient Boosting Tree regression model, and evaluate performance on test data" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RMSE = 0.8990189304085447\n", + "R-sqr = 0.7508947409706976" + ] + } + ], + "source": [ + "from pyspark.ml.regression import GBTRegressor\n", + "\n", + "## DEFINE REGRESSION FURMULA\n", + "regFormula = RFormula(formula=\"tip_amount ~ paymentIndex + vendorIndex + rateIndex + TrafficTimeBinsIndex + pickup_hour + passenger_count + trip_time_in_secs + trip_distance + fare_amount\")\n", + "\n", + "## DEFINE INDEXER FOR CATEGORIAL VARIABLES\n", + "featureIndexer = VectorIndexer(inputCol=\"features\", outputCol=\"indexedFeatures\", maxCategories=32)\n", + "\n", + "## DEFINE GRADIENT BOOSTING TREE REGRESSOR\n", + "gBT = GBTRegressor(featuresCol=\"indexedFeatures\", maxIter=10)\n", + "\n", + "## Fit model, with formula and other transformations\n", + "model = Pipeline(stages=[regFormula, featureIndexer, gBT]).fit(trainData)\n", + "\n", + "## PREDICT ON TEST DATA AND EVALUATE\n", + "predictions = model.transform(testData)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "testMetrics = RegressionMetrics(predictionAndLabels)\n", + "print(\"RMSE = %s\" % testMetrics.rootMeanSquaredError)\n", + "print(\"R-sqr = %s\" % testMetrics.r2)\n", + "\n", + "## PLOC ACTUALS VS. PREDICTIONS\n", + "predictions.select(\"label\",\"prediction\").createOrReplaceTempView(\"tmp_results\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Train a random forest regression model using the Pipeline function, save, and evaluate on test data set" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RMSE = 0.9735073076409382\n", + "R-sqr = 0.5898914711478505" + ] + } + ], + "source": [ + "from pyspark.ml.feature import RFormula\n", + "from sklearn.metrics import roc_curve,auc\n", + "from pyspark.ml.regression import RandomForestRegressor\n", + "from pyspark.mllib.evaluation import RegressionMetrics\n", + "\n", + "## DEFINE REGRESSION FURMULA\n", + "regFormula = RFormula(formula=\"tip_amount ~ paymentIndex + vendorIndex + rateIndex + TrafficTimeBinsIndex + pickup_hour + passenger_count + trip_time_in_secs + trip_distance + fare_amount\")\n", + "\n", + "## DEFINE INDEXER FOR CATEGORIAL VARIABLES\n", + "featureIndexer = VectorIndexer(inputCol=\"features\", outputCol=\"indexedFeatures\", maxCategories=32)\n", + "\n", + "## DEFINE RANDOM FOREST ESTIMATOR\n", + "randForest = RandomForestRegressor(featuresCol = 'indexedFeatures', labelCol = 'label', numTrees=20, \n", + " featureSubsetStrategy=\"auto\",impurity='variance', maxDepth=6, maxBins=100)\n", + "\n", + "## Fit model, with formula and other transformations\n", + "model = Pipeline(stages=[regFormula, featureIndexer, randForest]).fit(trainData)\n", + "\n", + "## SAVE MODEL\n", + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"RandomForestRegressionModel_\" + datestamp;\n", + "randForestDirfilename = modelDir + fileName;\n", + "model.save(randForestDirfilename)\n", + "\n", + "## PREDICT ON TEST DATA AND EVALUATE\n", + "predictions = model.transform(testData)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "testMetrics = RegressionMetrics(predictionAndLabels)\n", + "print(\"RMSE = %s\" % testMetrics.rootMeanSquaredError)\n", + "print(\"R-sqr = %s\" % testMetrics.r2)\n", + "\n", + "## PLOC ACTUALS VS. PREDICTIONS\n", + "predictions.select(\"label\",\"prediction\").createOrReplaceTempView(\"tmp_results\");" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%%sql -q -o predictionsPD\n", + "SELECT * from tmp_results" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%local\n", + "import numpy as np\n", + "\n", + "ax = predictionsPD.plot(kind='scatter', figsize = (5,5), x='label', y='prediction', color='blue', alpha = 0.25, label='Actual vs. predicted');\n", + "fit = np.polyfit(predictionsPD['label'], predictionsPD['prediction'], deg=1)\n", + "ax.set_title('Actual vs. Predicted Tip Amounts ($)')\n", + "ax.set_xlabel(\"Actual\"); ax.set_ylabel(\"Predicted\");\n", + "ax.plot(predictionsPD['label'], fit[0] * predictionsPD['label'] + fit[1], color='magenta')\n", + "plt.axis([-1, 15, -1, 15])\n", + "plt.show(ax)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hyper-parameter tuning: Train a random forest model using cross-validation" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "R-squared on test data = 0.794074" + ] + } + ], + "source": [ + "from pyspark.ml.tuning import CrossValidator, ParamGridBuilder\n", + "from pyspark.ml.evaluation import RegressionEvaluator\n", + "\n", + "## DEFINE RANDOM FOREST MODELS\n", + "randForest = RandomForestRegressor(featuresCol = 'indexedFeatures', labelCol = 'label', \n", + " featureSubsetStrategy=\"auto\",impurity='variance', maxBins=100)\n", + "\n", + "## DEFINE MODELING PIPELINE, INCLUDING FORMULA, FEATURE TRANSFORMATIONS, AND ESTIMATOR\n", + "pipeline = Pipeline(stages=[regFormula, featureIndexer, randForest])\n", + "\n", + "## DEFINE PARAMETER GRID FOR RANDOM FOREST\n", + "paramGrid = ParamGridBuilder() \\\n", + " .addGrid(randForest.numTrees, [10, 25, 50]) \\\n", + " .addGrid(randForest.maxDepth, [3, 5, 7]) \\\n", + " .build()\n", + "\n", + "## DEFINE CROSS VALIDATION\n", + "crossval = CrossValidator(estimator=pipeline,\n", + " estimatorParamMaps=paramGrid,\n", + " evaluator=RegressionEvaluator(metricName=\"rmse\"),\n", + " numFolds=3)\n", + "\n", + "## TRAIN MODEL USING CV\n", + "cvModel = crossval.fit(trainData)\n", + "\n", + "## PREDICT AND EVALUATE TEST DATA SET\n", + "predictions = cvModel.transform(testData)\n", + "evaluator = RegressionEvaluator(labelCol=\"label\", predictionCol=\"prediction\", metricName=\"r2\")\n", + "r2 = evaluator.evaluate(predictions)\n", + "print(\"R-squared on test data = %g\" % r2)\n", + "\n", + "## SAVE THE BEST MODEL\n", + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"CV_RandomForestRegressionModel_\" + datestamp;\n", + "CVDirfilename = modelDir + fileName;\n", + "cvModel.bestModel.save(CVDirfilename);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load a saved pipeline model and evaluate it on test data set" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RMSE = 0.9735073076409382\n", + "R-sqr = 0.5898914711478505" + ] + } + ], + "source": [ + "from pyspark.ml import PipelineModel\n", + "\n", + "savedModel = PipelineModel.load(randForestDirfilename)\n", + "\n", + "predictions = savedModel.transform(testData)\n", + "predictionAndLabels = predictions.select(\"label\",\"prediction\").rdd\n", + "testMetrics = RegressionMetrics(predictionAndLabels)\n", + "print(\"RMSE = %s\" % testMetrics.rootMeanSquaredError)\n", + "print(\"R-sqr = %s\" % testMetrics.r2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load and transform an independent validation data-set, and evaluate the saved pipeline model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Note that this validation data, by design, has a different format than the original trainig data. By grangling and transformations, we make the data format the same as the training data for the purpose of scoring." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- medallion: string (nullable = true)\n", + " |-- hack_license: string (nullable = true)\n", + " |-- vendor_id: string (nullable = true)\n", + " |-- rate_code: integer (nullable = true)\n", + " |-- store_and_fwd_flag: string (nullable = true)\n", + " |-- pickup_datetime: timestamp (nullable = true)\n", + " |-- dropoff_datetime: timestamp (nullable = true)\n", + " |-- pickup_hour: integer (nullable = true)\n", + " |-- pickup_week: integer (nullable = true)\n", + " |-- weekday: integer (nullable = true)\n", + " |-- passenger_count: integer (nullable = true)\n", + " |-- trip_time_in_secs: double (nullable = true)\n", + " |-- trip_distance: double (nullable = true)\n", + " |-- pickup_longitude: double (nullable = true)\n", + " |-- pickup_latitude: double (nullable = true)\n", + " |-- dropoff_longitude: double (nullable = true)\n", + " |-- dropoff_latitude: double (nullable = true)\n", + " |-- direct_distance: string (nullable = true)\n", + " |-- payment_type: string (nullable = true)\n", + " |-- fare_amount: double (nullable = true)\n", + " |-- surcharge: double (nullable = true)\n", + " |-- mta_tax: double (nullable = true)\n", + " |-- tip_amount: double (nullable = true)\n", + " |-- tolls_amount: double (nullable = true)\n", + " |-- total_amount: double (nullable = true)\n", + " |-- tipped: integer (nullable = true)\n", + " |-- tip_class: integer (nullable = true)" + ] + } + ], + "source": [ + "## READ IN DATA FRAME FROM CSV\n", + "taxi_valid_df = spark.read.csv(path=taxi_valid_file_loc, header=True, inferSchema=True)\n", + "taxi_valid_df.printSchema()" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "## READ IN DATA FRAME FROM CSV\n", + "taxi_valid_df = spark.read.csv(path=taxi_valid_file_loc, header=True, inferSchema=True)\n", + "\n", + "## CREATE A CLEANED DATA-FRAME BY DROPPING SOME UN-NECESSARY COLUMNS & FILTERING FOR UNDESIRED VALUES OR OUTLIERS\n", + "taxi_df_valid_cleaned = taxi_valid_df.drop('medallion').drop('hack_license').drop('store_and_fwd_flag').drop('pickup_datetime')\\\n", + " .drop('dropoff_datetime').drop('pickup_longitude').drop('pickup_latitude').drop('dropoff_latitude')\\\n", + " .drop('dropoff_longitude').drop('tip_class').drop('total_amount').drop('tolls_amount').drop('mta_tax')\\\n", + " .drop('direct_distance').drop('surcharge')\\\n", + " .filter(\"passenger_count > 0 and passenger_count < 8 AND payment_type in ('CSH', 'CRD') \\\n", + " AND tip_amount >= 0 AND tip_amount < 30 AND fare_amount >= 1 AND fare_amount < 150 AND trip_distance > 0 \\\n", + " AND trip_distance < 100 AND trip_time_in_secs > 30 AND trip_time_in_secs < 7200\" )\n", + "\n", + "## REGISTER DATA-FRAME AS A TEMP-TABLE IN SQL-CONTEXT\n", + "taxi_df_valid_cleaned.createOrReplaceTempView(\"taxi_valid\")\n", + "\n", + "### CREATE FOUR BUCKETS FOR TRAFFIC TIMES\n", + "sqlStatement = \"\"\" SELECT *, CASE\n", + " WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN \"Night\" \n", + " WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN \"AMRush\" \n", + " WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN \"Afternoon\"\n", + " WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN \"PMRush\"\n", + " END as TrafficTimeBins\n", + " FROM taxi_valid\n", + "\"\"\"\n", + "taxi_df_valid_with_newFeatures = spark.sql(sqlStatement)\n", + "\n", + "## APPLY THE SAME TRANSFORATION ON THIS DATA AS ORIGINAL TRAINING DATA\n", + "encodedFinalValid = Pipeline(stages=[sI1, sI2, sI3, sI4]).fit(taxi_df_train_with_newFeatures).transform(taxi_df_valid_with_newFeatures)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "R-squared on validation data = 0.785713" + ] + } + ], + "source": [ + "## LOAD SAVED MODEL, SCORE VALIDATION DATA, AND EVALUATE\n", + "savedModel = PipelineModel.load(CVDirfilename)\n", + "predictions = savedModel.transform(encodedFinalValid)\n", + "r2 = evaluator.evaluate(predictions)\n", + "print(\"R-squared on validation data = %g\" % r2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Save predictions to a file in HDFS" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "datestamp = datetime.datetime.now().strftime('%m-%d-%Y-%s');\n", + "fileName = \"Predictions_CV_\" + datestamp;\n", + "predictionfile = dataDir + fileName;\n", + "predictions.select(\"label\",\"prediction\").write.mode(\"overwrite\").csv(predictionfile)" + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "celltoolbar": "Raw Cell Format", + "kernelspec": { + "display_name": "PySpark3", + "language": "", + "name": "pyspark3kernel" + }, + "language_info": { + "codemirror_mode": { + "name": "python", + "version": 3 + }, + "mimetype": "text/x-python", + "name": "pyspark3", + "pygments_lexer": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/Misc/StrataSanJose2017/Code/MRS/1-Clean-Join-Subset.r b/Misc/StrataSanJose2017/Code/MRS/1-Clean-Join-Subset.r new file mode 100644 index 00000000..00be1771 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/MRS/1-Clean-Join-Subset.r @@ -0,0 +1,228 @@ +setwd("/home/remoteuser/Code/MRS") +source("SetComputeContext.r") + +.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) + +library(SparkR) + +sparkEnvir <- list(spark.executor.instances = '10', + spark.yarn.executor.memoryOverhead = '8000') + +sc <- sparkR.init( + sparkEnvir = sparkEnvir, + sparkPackages = "com.databricks:spark-csv_2.10:1.3.0" +) + +sqlContext <- sparkRSQL.init(sc) + +airPath <- file.path(fullDataDir, "AirlineSubsetCsv") +weatherPath <- file.path(fullDataDir, "WeatherSubsetCsv") + +# create a SparkR DataFrame for the airline data + +airDF <- read.df(sqlContext, airPath, source = "com.databricks.spark.csv", + header = "true", inferSchema = "true") + +# Create a SparkR DataFrame for the weather data + +weatherDF <- read.df(sqlContext, weatherPath, source = "com.databricks.spark.csv", + header = "true", inferSchema = "true") + +################################################ +# Data Cleaning and Transformation +################################################ + +airDF <- SparkR::rename(airDF, + ArrDel15 = airDF$ARR_DEL15, + Year = airDF$YEAR, + Month = airDF$MONTH, + DayofMonth = airDF$DAY_OF_MONTH, + DayOfWeek = airDF$DAY_OF_WEEK, + Carrier = airDF$UNIQUE_CARRIER, + OriginAirportID = airDF$ORIGIN_AIRPORT_ID, + DestAirportID = airDF$DEST_AIRPORT_ID, + CRSDepTime = airDF$CRS_DEP_TIME, + CRSArrTime = airDF$CRS_ARR_TIME +) + +# Select desired columns from the flight data. +varsToKeep <- c("ArrDel15", "Year", "Month", "DayofMonth", "DayOfWeek", "Carrier", "OriginAirportID", "DestAirportID", "CRSDepTime", "CRSArrTime") +airDF <- select(airDF, varsToKeep) + +# Round down scheduled departure time to full hour. +airDF$CRSDepTime <- floor(airDF$CRSDepTime / 100) + +# Average weather readings by hour +weatherDF <- agg(groupBy(weatherDF, "AdjustedYear", "AdjustedMonth", "AdjustedDay", "AdjustedHour", "AirportID"), Visibility="avg", + DryBulbCelsius="avg", DewPointCelsius="avg", RelativeHumidity="avg", WindSpeed="avg", Altimeter="avg" +) + +weatherDF <- SparkR::rename(weatherDF, + Visibility = weatherDF$'avg(Visibility)', + DryBulbCelsius = weatherDF$'avg(DryBulbCelsius)', + DewPointCelsius = weatherDF$'avg(DewPointCelsius)', + RelativeHumidity = weatherDF$'avg(RelativeHumidity)', + WindSpeed = weatherDF$'avg(WindSpeed)', + Altimeter = weatherDF$'avg(Altimeter)' +) + +####################################################### +# Join airline data with weather at Origin Airport +####################################################### + +joinedDF <- SparkR::join( + airDF, + weatherDF, + airDF$OriginAirportID == weatherDF$AirportID & + airDF$Year == weatherDF$AdjustedYear & + airDF$Month == weatherDF$AdjustedMonth & + airDF$DayofMonth == weatherDF$AdjustedDay & + airDF$CRSDepTime == weatherDF$AdjustedHour, + joinType = "left_outer" +) + +# Remove redundant columns +vars <- names(joinedDF) +varsToDrop <- c('AdjustedYear', 'AdjustedMonth', 'AdjustedDay', 'AdjustedHour', 'AirportID') +varsToKeep <- vars[!(vars %in% varsToDrop)] +joinedDF1 <- select(joinedDF, varsToKeep) + +joinedDF2 <- SparkR::rename(joinedDF1, + VisibilityOrigin = joinedDF1$Visibility, + DryBulbCelsiusOrigin = joinedDF1$DryBulbCelsius, + DewPointCelsiusOrigin = joinedDF1$DewPointCelsius, + RelativeHumidityOrigin = joinedDF1$RelativeHumidity, + WindSpeedOrigin = joinedDF1$WindSpeed, + AltimeterOrigin = joinedDF1$Altimeter +) + +####################################################### +# Join airline data with weather at Destination Airport +####################################################### + +joinedDF3 <- SparkR::join( + joinedDF2, + weatherDF, + airDF$DestAirportID == weatherDF$AirportID & + airDF$Year == weatherDF$AdjustedYear & + airDF$Month == weatherDF$AdjustedMonth & + airDF$DayofMonth == weatherDF$AdjustedDay & + airDF$CRSDepTime == weatherDF$AdjustedHour, + joinType = "left_outer" +) + +# Remove redundant columns +vars <- names(joinedDF3) +varsToDrop <- c('AdjustedYear', 'AdjustedMonth', 'AdjustedDay', 'AdjustedHour', 'AirportID') +varsToKeep <- vars[!(vars %in% varsToDrop)] +joinedDF4 <- select(joinedDF3, varsToKeep) + +joinedDF5 <- SparkR::rename(joinedDF4, + VisibilityDest = joinedDF4$Visibility, + DryBulbCelsiusDest = joinedDF4$DryBulbCelsius, + DewPointCelsiusDest = joinedDF4$DewPointCelsius, + RelativeHumidityDest = joinedDF4$RelativeHumidity, + WindSpeedDest = joinedDF4$WindSpeed, + AltimeterDest = joinedDF4$Altimeter +) + + +################################################ +# Output to CSV +################################################ + +# Increase numCSVs when working with larger data +# on a larger cluster +numCSVs <- 2 # write.df below will produce this many CSV files +joinedDF5 <- repartition(joinedDF5, numCSVs) + +# write result to directory of CSVs +write.df(joinedDF5, file.path(fullDataDir, "joined5CsvSubset"), "com.databricks.spark.csv", "overwrite", header = "true") + +# We can shut down the SparkR Spark context now +sparkR.stop() + +# remove non-data files +if (is(rxOptions()$fileSystem, "RxHdfsFileSystem")) +{ + rxHadoopRemove(file.path(fullDataDir, "joined5CsvSubset/_SUCCESS")) +} else +{ + file.remove(Sys.glob(file.path(dataDir, "joined5CsvSubset/.*.crc"))) + file.remove(Sys.glob(file.path(dataDir, "joined5CsvSubset/_SUCCESS"))) +} + +################################################ +# Import to compressed, binary XDF format +################################################ + +colInfo <- list( + ArrDel15 = list(type="numeric"), + Year = list(type="factor"), + Month = list(type="factor"), + DayofMonth = list(type="factor"), + DayOfWeek = list(type="factor"), + Carrier = list(type="factor"), + OriginAirportID = list(type="factor"), + DestAirportID = list(type="factor"), + RelativeHumidityOrigin = list(type="numeric"), + AltimeterOrigin = list(type="numeric"), + DryBulbCelsiusOrigin = list(type="numeric"), + WindSpeedOrigin = list(type="numeric"), + VisibilityOrigin = list(type="numeric"), + DewPointCelsiusOrigin = list(type="numeric"), + RelativeHumidityDest = list(type="numeric"), + AltimeterDest = list(type="numeric"), + DryBulbCelsiusDest = list(type="numeric"), + WindSpeedDest = list(type="numeric"), + VisibilityDest = list(type="numeric"), + DewPointCelsiusDest = list(type="numeric") +) + +joinedDF5Txt <- RxTextData(file.path(dataDir, "joined5CsvSubset"), + colInfo = colInfo) + +finalData <- RxXdfData(file.path(dataDir, "joined5XDFSubset")) + +# For local compute context, skip the following line +startRxSpark() + +rxImport(inData = joinedDF5Txt, finalData, overwrite = TRUE) + +rxGetInfo(finalData, getVarInfo = T) +# File name: /user/RevoShare/remoteuser/Data/joined5XDFSubset +# Number of composite data files: 2 +# Number of observations: 1900875 +# Number of variables: 22 +# Number of blocks: 4 +# Compression type: zlib +# Variable information: +# Var 1: ArrDel15, Type: numeric, Low/High: (0.0000, 1.0000) +# Var 2: Year +# 2 factor levels: 2011 2012 +# Var 3: Month +# 2 factor levels: 2 1 +# Var 4: DayofMonth +# 31 factor levels: 1 10 18 2 8 ... 12 4 7 29 5 +# Var 5: DayOfWeek +# 7 factor levels: 2 4 3 7 6 5 1 +# Var 6: Carrier +# 17 factor levels: EV FL MQ OO WN ... CO F9 B6 VX HA +# Var 7: OriginAirportID +# 295 factor levels: 10397 11697 11298 14869 11292 ... 10165 13139 11699 14955 10728 +# Var 8: DestAirportID +# 295 factor levels: 10135 10136 10140 10146 10157 ... 13139 11699 10728 14955 10577 +# Var 9: CRSDepTime, Type: integer, Low/High: (0, 23) +# Var 10: CRSArrTime, Type: integer, Low/High: (1, 2400) +# Var 11: RelativeHumidityOrigin, Type: numeric, Low/High: (2.0000, 100.0000) +# Var 12: AltimeterOrigin, Type: numeric, Low/High: (28.3000, 31.1600) +# Var 13: DryBulbCelsiusOrigin, Type: numeric, Low/High: (-46.1000, 32.2000) +# Var 14: WindSpeedOrigin, Type: numeric, Low/High: (0.0000, 48.0000) +# Var 15: VisibilityOrigin, Type: numeric, Low/High: (0.0000, 80.0000) +# Var 16: DewPointCelsiusOrigin, Type: numeric, Low/High: (-41.7000, 23.9000) +# Var 17: RelativeHumidityDest, Type: numeric, Low/High: (2.0000, 100.0000) +# Var 18: AltimeterDest, Type: numeric, Low/High: (28.2500, 31.1600) +# Var 19: DryBulbCelsiusDest, Type: numeric, Low/High: (-46.1000, 31.7000) +# Var 20: WindSpeedDest, Type: numeric, Low/High: (0.0000, 47.6250) +# Var 21: VisibilityDest, Type: numeric, Low/High: (0.0000, 80.0000) +# Var 22: DewPointCelsiusDest, Type: numeric, Low/High: (-43.0000, 24.2000) diff --git a/Misc/StrataSanJose2017/Code/MRS/2-Train-Test-Subset.r b/Misc/StrataSanJose2017/Code/MRS/2-Train-Test-Subset.r new file mode 100644 index 00000000..c862ea87 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/MRS/2-Train-Test-Subset.r @@ -0,0 +1,67 @@ +setwd("/home/remoteuser/Code/MRS") +source("SetComputeContext.r") + +# For local compute context, skip the following line +startRxSpark() + +finalData <- RxXdfData(file.path(dataDir, "joined5XDFSubset")) + +################################################ +# Split out Training and Test Datasets +################################################ + +# split out the training data + +trainDS <- RxXdfData( file.path(dataDir, "finalDataTrainSubset" )) + +rxDataStep( inData = finalData, outFile = trainDS, + rowSelection = ( Year != 2012 ), overwrite = T ) + +# split out the testing data + +testDS <- RxXdfData( file.path(dataDir, "finalDataTestSubset" )) + +rxDataStep( inData = finalData, outFile = testDS, + rowSelection = ( Year == 2012 ), overwrite = T ) + + +################################################ +# Train and Test a Logistic Regression model +################################################ + +formula <- as.formula(ArrDel15 ~ Month + DayofMonth + DayOfWeek + Carrier + OriginAirportID + + DestAirportID + CRSDepTime + CRSArrTime + RelativeHumidityOrigin + + AltimeterOrigin + DryBulbCelsiusOrigin + WindSpeedOrigin + + VisibilityOrigin + DewPointCelsiusOrigin + RelativeHumidityDest + + AltimeterDest + DryBulbCelsiusDest + WindSpeedDest + VisibilityDest + + DewPointCelsiusDest +) + +# Use the scalable rxLogit() function + +logitModel <- rxLogit(formula, data = trainDS) + +options(max.print = 10000) +base::summary(logitModel) + +# Predict over test data (Logistic Regression). + +logitPredict <- RxXdfData(file.path(dataDir, "logitPredictSubset")) + +# Use the scalable rxPredict() function + +rxPredict(logitModel, data = testDS, outData = logitPredict, + extraVarsToWrite = c("ArrDel15"), + type = 'response', overwrite = TRUE) + +# Calculate ROC and Area Under the Curve (AUC). + +logitRoc <- rxRoc("ArrDel15", "ArrDel15_Pred", logitPredict) +logitAuc <- rxAuc(logitRoc) + +plot(logitRoc) + +save(logitModel, file = "logitModelSubset.RData") + +# For local compute context, skip the following line +rxSparkDisconnect(rxGetComputeContext()) diff --git a/Misc/StrataSanJose2017/Code/MRS/3-Deploy-Score-mrsdeploy.r b/Misc/StrataSanJose2017/Code/MRS/3-Deploy-Score-mrsdeploy.r new file mode 100644 index 00000000..2693a777 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/MRS/3-Deploy-Score-mrsdeploy.r @@ -0,0 +1,83 @@ +# Use R Server Operationalization to deploy the logistic regression model as a scalable web service. + +# To enable Microsoft R Server Operationalization on an HDInsight cluster, +# follow these instructions: +# https://docs.microsoft.com/en-us/azure/hdinsight/hdinsight-hadoop-r-server-get-started#using-microsoft-r-server-operationalization + +setwd("/home/remoteuser/Code/MRS") +source("SetComputeContext.r") + +rxSetComputeContext("local") + +# Load our logistic regression model + +load("logitModelSubset.RData") # loads logitModel + +# Reference the test data to be scored +testDS <- RxXdfData( file.path(dataDir, "finalDataTestSubset") ) + +# Read the first 6 rows and remove the ArrDel15 column +dataToBeScored <- base::subset(head(testDS), select = -ArrDel15) + +# Record the levels of the factor variables +colInfo <- rxCreateColInfo(dataToBeScored) + +modelInfo <- list(predictiveModel = logitModel, colInfo = colInfo) + +# Define a scoring function to be published as a web service + +scoringFn <- function(newdata){ + library(RevoScaleR) + data <- rxImport(newdata, colInfo = modelInfo$colInfo) + rxPredict(modelInfo$predictiveModel, data) +} + +###################################################### +# Authenticate with the Operationalization service +###################################################### +# load mrsdeploy package + +library(mrsdeploy) + +myUsername <- "admin" +myPassword <- "INSERT PASSWORD HERE" + +remoteLogin( + "http://127.0.0.1:12800", + username = myUsername, + password = myPassword, + session = FALSE +) + +################################################ +# Deploy the scoring function as a web service +################################################ + +# specify the version +version <- "v1.0.0" + +# publish the scoring function web service +api_frame <- publishService( + name = "Delay_Prediction_Service", # name must not contain spaces + code = scoringFn, + model = modelInfo, + inputs = list(newdata = "data.frame"), + outputs = list(answer = "data.frame"), + v = version +) + +# N.B. To update an existing web service, either +# 1) use the updateService function, or +# 2) change the version number + +################################################ +# Score new data via the web service +################################################ + +endpoint <- getService("Delay_Prediction_Service", version) + +response <- endpoint$scoringFn(dataToBeScored) + +scores <- response$output("answer") + +head(scores) diff --git a/Misc/StrataSanJose2017/Code/MRS/Readme.md b/Misc/StrataSanJose2017/Code/MRS/Readme.md new file mode 100644 index 00000000..74b103b0 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/MRS/Readme.md @@ -0,0 +1 @@ +This folder contains Microsoft R Server (MRS) codes for hands-on exercises and samples discussed during tutorial. diff --git a/Misc/StrataSanJose2017/Code/MRS/SetComputeContext.r b/Misc/StrataSanJose2017/Code/MRS/SetComputeContext.r new file mode 100644 index 00000000..95bee369 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/MRS/SetComputeContext.r @@ -0,0 +1,97 @@ +if(file.exists("/dsvm")) +{ + # Get existing PATH, to merge with new + the.path <- Sys.getenv("PATH") + the.path <- strsplit(the.path, ':') + new.path <- c("/anaconda/envs/py35/bin", + "/dsvm/tools/cntk/cntk/bin", + "/usr/local/mpi/bin", + "/dsvm/tools/spark/current/bin", + "/anaconda/envs/py35/bin", + "/dsvm/tools/cntk/cntk/bin", + "/usr/local/mpi/bin", + "/dsvm/tools/spark/current/bin", + "/usr/local/bin", + "/usr/bin", + "/usr/local/sbin", + "/usr/sbin", + "/opt/hadoop/current/sbin", + "/opt/hadoop/current/bin", + "/home/remoteuser/.local/bin", + "/home/remoteuser/bin", + "/opt/hadoop/current/sbin", + "/opt/hadoop/current/bin") + new.path <- paste(union(the.path[[1]], new.path), collapse=':') + # Set environment variables for the Data Science VM + Sys.setenv(SPARK_HOME = "/dsvm/tools/spark/current", + YARN_CONF_DIR = "/opt/hadoop/current/etc/hadoop", + JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64", + PATH = new.path + ) +} + +useHDFS <- TRUE + +if(useHDFS) { + + ################################################ + # Use Hadoop-compatible Distributed File System + # N.B. Can be used with local or RxSpark compute contexts + ################################################ + + rxOptions(fileSystem = RxHdfsFileSystem()) + + dataDir <- "/user/RevoShare/remoteuser/Data" + + ################################################ + + if(rxOptions()$hdfsHost == "default") { + fullDataDir <- dataDir + } else { + fullDataDir <- paste0(rxOptions()$hdfsHost, dataDir) + } +} else { + + ################################################ + # Use Native, Local File System + # N.B. Can only be used with local compute context + ################################################ + + rxOptions(fileSystem = RxNativeFileSystem()) + + dataDir <- file.path(getwd(), "delayDataLarge") + fullDataDir <- paste0("file://", dataDir) + + ################################################ +} + +################################################ +# Distributed computing using Spark +################################################ + +startRxSpark <- function() { + if (useHDFS) { + # When running on an HDInsight cluster, + # specifying numExecutors, executorCores, + # and executorMem is optional + rxSparkConnect(reset = T, + consoleOutput = TRUE, + numExecutors = 1, + executorCores = 2, + executorMem = "1g" + ) + } else { + cat("Using local compute context to process local data.\n") + } +} + +rxRoc <- function(...){ + previousContext <- rxSetComputeContext(RxLocalSeq()) + + # rxRoc requires local compute context + roc <- RevoScaleR::rxRoc(...) + + rxSetComputeContext(previousContext) + + return(roc) +} diff --git a/Misc/StrataSanJose2017/Code/MRS/learning_curves/learning_curve_lib.R b/Misc/StrataSanJose2017/Code/MRS/learning_curves/learning_curve_lib.R new file mode 100644 index 00000000..76a7a4d8 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/MRS/learning_curves/learning_curve_lib.R @@ -0,0 +1,224 @@ +# Use random number seed to select the rows to be used for training or testing. +# Collect error stats for training set from the model when possible. + +# execObjects = c("data_table", "SALT") +run_training_fraction <- function(model_class, training_fraction, + with_formula, test_set_kfold_id, KFOLDS=3, ...){ + learner <- get(model_class) + + NUM_BUCKETS <- 1000 # for approximate AUC + + row_tagger <- function(data_list, start_row, num_rows, + chunk_num, prob, kfolds, kfold_id, salt){ + rowNums <- seq(from=start_row, length.out=num_rows) + set.seed(chunk_num + salt) + kfold <- sample(1:kfolds, size=num_rows, replace=TRUE) + in_test_set <- kfold == kfold_id + num_training_candidates <- sum(!in_test_set) + keepers <- sample(rowNums[!in_test_set], prob * num_training_candidates) + data_list$in_training_set <- rowNums %in% keepers + data_list$in_test_set <- in_test_set + data_list + } + + row_selection_transform <- function(data_list){ + row_tagger(data_list, .rxStartRow, .rxNumRows, .rxChunkNum, + prob, kfolds, kfold_id, salt) + } + + # Calculate RMSE (root mean squared error) for predictions made with a given model on a dataset. + # Only rows in the test set are counted. + RMSE_transform <- function(data_list){ + if (.rxChunkNum == 1){ + .rxSet("SSE", 0) + .rxSet("rowCount", 0) + } + SSE <- .rxGet("SSE") + rowCount <- .rxGet("rowCount") + + data_list <- row_tagger(data_list, .rxStartRow, .rxNumRows, .rxChunkNum, + prob, kfolds, kfold_id, salt) + + # rxPredict returns a dataframe if you give it one. # data_list$in_test_set + if (class(model)[1] == "SDCAR"){ + test_chunk <- as.data.frame(data_list)[data_list[[SET_SELECTOR]],] + outcome_var <- model$params$formulaVars[1] + residual <- rxPredict(model, test_chunk)[[1]] - test_chunk[[outcome_var]] + } else { + residual <- rxPredict(model, as.data.frame(data_list)[data_list[[SET_SELECTOR]],], + computeResiduals=TRUE, residVarNames="residual")$residual + } + + SSE <- SSE + sum(residual^2, na.rm=TRUE) + rowCount <- rowCount + sum(!is.na(residual)) + .rxSet("SSE", SSE) + .rxSet("rowCount", rowCount) + return(data_list) + } + + AUC_transform <- function(data_list){ + # NUM_BUCKETS <- 100; + if (.rxChunkNum == 1){ + # assume the first chunk gives a reasonably representative sample of score distribution + # chunk1_scores <- rxPredict(model, as.data.frame(data_list))[[1]] + # quantile_breaks <- unique(quantile(chunk1_scores, probs=0:NUM_BUCKETS/NUM_BUCKETS))) + # scores must be in range of probabilities (between 0 and 1) + .rxSet("BREAKS", (0:NUM_BUCKETS)/NUM_BUCKETS) # + .rxSet("TP", numeric(NUM_BUCKETS)) + .rxSet("FP", numeric(NUM_BUCKETS)) + } + TPR <- .rxGet("TP") + FPR <- .rxGet("FP") + BREAKS <- .rxGet("BREAKS") + + data_list <- row_tagger(data_list, .rxStartRow, .rxNumRows, .rxChunkNum, + prob, kfolds, kfold_id, salt) + + data_set <- as.data.frame(data_list)[data_list[[SET_SELECTOR]],] + labels <- data_set[[model$param$formulaVars$original$depVars]] + scores <- rxPredict(model, data_set)[[1]] # rxPredict returns a dataframe if you give it one. + bucket <- cut(scores, breaks=BREAKS, include.lowest=TRUE) + + # data.frame(labels, scores, bucket) + TP <- rev(as.vector(xtabs(labels ~ bucket))) # positive cases in each bucket, top scores first + N <- rev(as.vector(xtabs( ~ bucket))) # total cases in each bucket + FP <- N - TP + + .rxSet("TP", TP) + .rxSet("FP", FP) + return(data_list) + } + + simple_auc <- function(TPR, FPR){ + dFPR <- c(0, diff(FPR)) + sum(TPR * dFPR) - sum(diff(TPR) * diff(FPR))/2 + } + + calculate_RMSE <- function(with_model, xdfdata, set_selector){ + xformObjs <- rxDataStep(inData=xdfdata, + transformFunc=RMSE_transform, + transformVars=c(rxGetVarNames(xdfdata) ), + transformObjects=list(SSE=0, rowCount=0, SET_SELECTOR=set_selector, + model=with_model, row_tagger=row_tagger, + prob=training_fraction, kfolds=KFOLDS, + kfold_id=test_set_kfold_id, + salt=SALT), + returnTransformObjects=TRUE) + with(xformObjs, sqrt(SSE/rowCount)) + } + + calculate_AUC <- function(with_model, xdfdata, set_selector){ + # NUM_BUCKETS <- 100; kfolds=3 + xformObjs <- rxDataStep(inData=xdfdata, + transformFunc=AUC_transform, + transformVars=c( rxGetVarNames(xdfdata) ), + transformObjects=list(TP=numeric(NUM_BUCKETS), FP=numeric(NUM_BUCKETS), + SET_SELECTOR=set_selector, + model=with_model, row_tagger=row_tagger, + prob=training_fraction, kfolds=KFOLDS, + kfold_id=test_set_kfold_id, + salt=SALT), + returnTransformObjects=TRUE) + with(xformObjs, { + TPR <- cumsum(TP)/sum(TP) + FPR <- cumsum(FP)/sum(FP) + simple_auc(TPR, FPR) + }) + } + + get_training_error <- function(fit) { + switch( class(fit)[[1]], + rxLinMod = with(summary(fit)[[1]], sqrt(residual.squares/nValidObs)), + rxBTrees =, + rxDForest = if(!is.null(fit$type) && "anova" == fit$type){ + calculate_RMSE(fit, data_table, "in_training_set") + } else { + calculate_AUC(fit, data_table, "in_training_set") + }, + rxDTree = if ("anova" == fit$method){ + calculate_RMSE(fit, data_table, "in_training_set") + } else { # "class" + calculate_AUC(fit, data_table, "in_training_set") + }, + rxLogit = calculate_AUC(fit, data_table, "in_training_set"), + SDCA = calculate_AUC(fit, data_table, "in_training_set"), + #rxFastLinear, class = SDCA (BinaryClassifierTrainer) + SDCAR = calculate_RMSE(fit, data_table, "in_training_set") + # rxFastLinear, class = SDCAR (RegressorTrainer) + ) + } + + get_test_error <- function(fit) { + switch( class(fit)[[1]], + rxLinMod = calculate_RMSE(fit, data_table, "in_test_set"), + rxBTrees =, + rxDForest = if(!is.null(fit$type) && "anova" == fit$type){ + calculate_RMSE(fit, data_table, "in_test_set") + } else { # fit$type == "class" + calculate_AUC(fit, data_table, "in_test_set") + }, + rxDTree = if ("anova" == fit$method){ + calculate_RMSE(fit, data_table, "in_test_set") + } else { # "class" + calculate_AUC(fit, data_table, "in_test_set") + }, + rxLogit = calculate_AUC(fit, data_table, "in_test_set"), + SDCA = calculate_AUC(fit, data_table, "in_test_set"), + #rxFastLinear, class = SDCA (BinaryClassifierTrainer) + SDCAR = calculate_RMSE(fit, data_table, "in_test_set") + # rxFastLinear, class = SDCAR (RegressorTrainer) + ) + } + + get_tss <- function(fit){ + switch( class(fit)[[1]], + rxLinMod = , + rxLogit = fit$nValidObs, + rxDTree = fit$valid.obs, + rxBTrees =, + rxDForest =, + SDCA =, + SDCAR = training_fraction * (1 - 1/KFOLDS) * rxGetInfo(data_table)$numRows + ) + } + + train_time <- system.time( + fit <- learner(as.formula(with_formula), data_table, + rowSelection=(in_training_set == TRUE), + transformFunc=row_selection_transform, + transformObjects=list(row_tagger=row_tagger, prob=training_fraction, + kfold_id=test_set_kfold_id, kfolds=KFOLDS, + salt=SALT), + ...) + )[['elapsed']] + + e1_time <- system.time( + training_error <- get_training_error(fit) + )[['elapsed']] + + e2_time <- system.time( + test_error <- get_test_error(fit) + )[['elapsed']] + + data.frame(tss=get_tss(fit), model_class=model_class, training=training_error, test=test_error, + train_time=train_time, train_error_time=e1_time, test_error_time=e2_time, + formula=with_formula, kfold=test_set_kfold_id, ...) + +} + + +create_formula <- function(outcome, varnames, interaction_pow=1){ + vars <- paste(setdiff(varnames, outcome), collapse=" + ") + if (interaction_pow > 1) vars <- sprintf("(%s)^%d", vars, interaction_pow) + sprintf("%s ~ %s", outcome, vars) +} + +#' get_training_fractions +#' Create a vector of fractions of available training data to be used at the evaluation +#' points of a learning curve. +#' @param min_tss; target minimum training set size. +#' @param max_tss: approximate maximum training set size. This is used to calculate the +#' fraction used for the smallest point. +#' @param num_tss: number of training set sizes. +get_training_set_fractions <- function(min_tss, max_tss, num_tss) + exp(seq(log(min_tss/max_tss), log(1), length=num_tss)) diff --git a/Misc/StrataSanJose2017/Code/MRS/logitModelSubset.RData b/Misc/StrataSanJose2017/Code/MRS/logitModelSubset.RData new file mode 100644 index 00000000..fb1e3f04 Binary files /dev/null and b/Misc/StrataSanJose2017/Code/MRS/logitModelSubset.RData differ diff --git a/Misc/StrataSanJose2017/Code/Readme.md b/Misc/StrataSanJose2017/Code/Readme.md new file mode 100644 index 00000000..dd658e46 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/Readme.md @@ -0,0 +1 @@ +This folder contains MRS, SparkR and sparklyr codes for hands-on exercises and samples discussed during tutorial. diff --git a/Misc/StrataSanJose2017/Code/SparkR/Operationalization_RemoteAccessAPI_forDSVM.R b/Misc/StrataSanJose2017/Code/SparkR/Operationalization_RemoteAccessAPI_forDSVM.R new file mode 100644 index 00000000..3c2d004e --- /dev/null +++ b/Misc/StrataSanJose2017/Code/SparkR/Operationalization_RemoteAccessAPI_forDSVM.R @@ -0,0 +1,29 @@ +###################################################################### +## Remote Login using mrsdeploy +###################################################################### +library(mrsdeploy) +remoteLogin( + "http://localhost:12800", + username = "admin", + password = "INSERT PASSWORD HERE", + session = FALSE +) +listServices() + + +###################################################################### +## Call API +###################################################################### +version <- "v0.0.1" +api_1 <- getService("scoring_input_files", version) + +modelfile <- "/user/RevoShare/remoteuser/Models/SparkGlmModel" +input <- "/user/RevoShare/remoteuser/Data/NYCjoinedParquetSubset" +output <- "/user/RevoShare/rserve2/Predictions/SparkRGLMPred" + +result_1 <- api_1$web_scoring( + modelfile = modelfile, + input = input, + output = output +) + diff --git a/Misc/StrataSanJose2017/Code/SparkR/Operationalization_RemoteAccessAPI_forHDICluster.R b/Misc/StrataSanJose2017/Code/SparkR/Operationalization_RemoteAccessAPI_forHDICluster.R new file mode 100644 index 00000000..2cdeaebd --- /dev/null +++ b/Misc/StrataSanJose2017/Code/SparkR/Operationalization_RemoteAccessAPI_forHDICluster.R @@ -0,0 +1,29 @@ +############################################################### +## Remote Login +############################################################### +remoteLogin( + "http://localhost:12800", + username = "admin", + password = "", + session = FALSE +) +listServices() + +############################################################### +## Call API +############################################################### +version <- "v0.0.1" +api_1 <- getService("scoring_input_files", version) + +modelfile <- "/HdiSamples/HdiSamples/NYCTaxi/SparkGlmModel" +input <- "/HdiSamples/HdiSamples/NYCTaxi/NYCjoinedParquetSubsetSampled" +output <- "/HdiSamples/HdiSamples/NYCTaxi/SparkRGLMPredictionsClient" + +result_1 <- api_1$web_scoring( + modelfile = modelfile, + input = input, + output = output +) + + +result_1$success diff --git a/Misc/StrataSanJose2017/Code/SparkR/Readme.md b/Misc/StrataSanJose2017/Code/SparkR/Readme.md new file mode 100644 index 00000000..804931e5 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/SparkR/Readme.md @@ -0,0 +1 @@ +Contains code for SparkR for data ingestion, Spark SQL, featurization, and data export. diff --git a/Misc/StrataSanJose2017/Code/SparkR/SparkR_GLM_Operationalization_forDSVM.R b/Misc/StrataSanJose2017/Code/SparkR/SparkR_GLM_Operationalization_forDSVM.R new file mode 100644 index 00000000..7de7ad95 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/SparkR/SparkR_GLM_Operationalization_forDSVM.R @@ -0,0 +1,173 @@ +########################################### +# LOAD LIBRARIES FROM SPECIFIED PATH +# CREATE SPARK CONTEXT +########################################### +Sys.setenv(YARN_CONF_DIR="/opt/hadoop/current/etc/hadoop", + HADOOP_HOME="/opt/hadoop/current", + JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64", + SPARK_HOME = "/dsvm/tools/spark/current", + PATH=paste0(Sys.getenv("PATH"),":/opt/hadoop/current/bin:/dsvm/tools/spark/current/bin") ) + +list.files(file.path(Sys.getenv("SPARK_HOME"), "R", "lib")) +.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) +library(SparkR) + +sc <- sparkR.session( + sparkPackages = "com.databricks:spark-csv_2.10:1.3.0" +) +SparkR::setLogLevel("OFF") + +################################################################ +## LOGIN TO OPERATIONALIZATION SERVICE ON VM AND LIST ANY EXISTING WEB SERVICES +################################################################ +library(mrsdeploy) +remoteLogin( + "http://localhost:12800", + username = "admin", + password = "INSERT PASSWORD HERE", + session = FALSE +) +listServices() + +########################################### +## DEFINE A FUNCTION FOR WEB-SERVICE SCORING +########################################### +web_scoring <- function(modelfile, input, output) { + + Sys.setenv(YARN_CONF_DIR="/opt/hadoop/current/etc/hadoop", + HADOOP_HOME="/opt/hadoop/current", + JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64", + SPARK_HOME = "/dsvm/tools/spark/current", + PATH=paste0(Sys.getenv("PATH"),":/opt/hadoop/current/bin:/dsvm/tools/spark/current/bin")) + + list.files(file.path(Sys.getenv("SPARK_HOME"), "R", "lib")) + .libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) + library(SparkR) + + sc <- sparkR.session( + sparkPackages = "com.databricks:spark-csv_2.10:1.3.0" + ) + SparkR::setLogLevel("OFF") + + ## Read a df + df <- read.df(input, source = "parquet", header = "true", inferSchema = "true", na.strings = "NA") + ## Load a model + model <- read.ml(modelfile) + + ## Predict and select relevant columns + predictions <- SparkR::predict(model, newData = df) + predfilt <- SparkR::select(predictions, c("label","prediction")) + + write.df(predfilt, path = output, source = "com.databricks.spark.csv", mode = "overwrite") + + sampledpredfilt <- head(SparkR::sample(predfilt, FALSE, 0.01, 1234), 10) + + # Return sampled 10 rows of the prediction data-frame + return(sampledpredfilt) + + sparkR.stop() +} + +############################################################### +## TEST THE WEB-SCORING FUNCTION +############################################################### +modelfile <- "/user/RevoShare/remoteuser/Models/SparkGlmModel" +input <- "/user/RevoShare/remoteuser/Data/NYCjoinedParquetSubset" +output <- "/user/RevoShare/rserve2/Predictions/SparkRGLMPredTest" +web_scoring (modelfile, input, output) + + +################################################################ +## CREATE A WEB SERVICE WTIH VERSION NUMBER +################################################################ +version <- "v0.0.1" +api_string <- publishService( + "scoring_input_files", + code = web_scoring, + inputs = list(modelfile = "character", + input = "character", + output = "character"), + v = version +) +listServices() +#deleteService("scoring_input_files", version); + +################################################################ +## CALL WEB SERVICE +################################################################ +version <- "v0.0.1" +api_1 <- getService("scoring_input_files", version) + +modelfile <- "/user/RevoShare/remoteuser/Models/SparkGlmModel" +input <- "/user/RevoShare/remoteuser/Data/NYCjoinedParquetSubset" +output <- "/user/RevoShare/rserve2/Predictions/SparkRGLMPred" + +result_1 <- api_1$web_scoring( + modelfile = modelfile, + input = input, + output = output +) + +## Check scored files +system("hadoop fs -ls /user/RevoShare/rserve2/Predictions/SparkRGLMPred") + +## Load predictions and inspect +schema <- structType(structField("label", "double"), + structField("predicted","double")); +scoredDF <- read.df(output, source = "com.databricks.spark.csv", header = "false", schema=schema) +head(scoredDF) + +########################################### +## STOP SPARK CONTEXT +########################################### +sparkR.stop() + + +################################################################ +################################################################ +################################################################ +################################################################ + + +################################################################ +## TRAIN A MODEL IF NECESSARY +################################################################ + +########################################### +## SPECIFY BASE HDFS DIRECTORY +########################################### +fullDataDir <- "/user/RevoShare/remoteuser/Data/NYCjoinedParquetSubset" + +########################################### +## READ IN FILE +########################################### +df <- read.df(fullDataDir, source = "parquet", header = "true", + inferSchema = "true", na.strings = "NA") +cache(df) +printSchema(df) + +########################################### +## CREATE GLM MODEL +########################################### +model <- SparkR::glm(tip_amount ~ payment_type + pickup_hour + fare_amount + passenger_count + + trip_distance + trip_time_in_secs + TrafficTimeBins, + data = df, family = "gaussian", epsilon = 1e-05, maxit = 10) + +########################################### +## PREDICT ON A DATAFRAME +########################################### +predictions <- SparkR::predict(model, newData = df) +predfilt <- SparkR::select(predictions, c("label","prediction")) + +########################################### +## SAVE MODEL +########################################### +modelPath <- "/user/RevoShare/remoteuser/Models/SparkGlmModel"; +#write.ml(model, modelPath) + +########################################### +## STOP SPARK CONTEXT +########################################### +sparkR.stop() + + diff --git a/Misc/StrataSanJose2017/Code/SparkR/SparkR_GLM_Operationalization_forHDICluster.R b/Misc/StrataSanJose2017/Code/SparkR/SparkR_GLM_Operationalization_forHDICluster.R new file mode 100644 index 00000000..21409197 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/SparkR/SparkR_GLM_Operationalization_forHDICluster.R @@ -0,0 +1,156 @@ +########################################## +# LOAD LIBRARIES FROM SPECIFIED PATH +########################################### +Sys.setenv(SPARK_HOME = "/usr/hdp/current/spark2-client") +list.files(file.path(Sys.getenv("SPARK_HOME"), "R", "lib")) +.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) + +library(SparkR) + +########################################### +# CREATE SPARK CONTEXT +########################################### +sc <- sparkR.session( + sparkPackages = "com.databricks:spark-csv_2.10:1.3.0" +) +SparkR::setLogLevel("OFF") + +########################################### +## DEFINE A FUNCTION FOR WEB-SERVICE SCORING +########################################### +web_scoring <- function(modelfile, input, output) { + + Sys.setenv(SPARK_HOME = "/usr/hdp/current/spark2-client") + list.files(file.path(Sys.getenv("SPARK_HOME"), "R", "lib")) + .libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) + library(SparkR) + sc <- sparkR.session( + sparkPackages = "com.databricks:spark-csv_2.10:1.3.0" + ) + SparkR::setLogLevel("OFF") + + ## Read a df + df <- read.df(input, source = "parquet", header = "true", inferSchema = "true", na.strings = "NA") + ## Load a model + model <- SparkR::read.ml(modelfile) + + ## Predict and select relevant columns + predictions <- SparkR::predict(model, newData = df) + predfilt <- SparkR::select(predictions, c("label","prediction")) + + write.df(predfilt, path = output, source = "com.databricks.spark.csv", mode = "overwrite") + + sampledpredfilt <- head(SparkR::sample(predfilt, FALSE, 0.01, 1234), 10) + + sparkR.stop() + # Return sampled 10 rows of the prediction data-frame + return(sampledpredfilt) + +} + +############################################################### +## Define file paths and test on HDI server +############################################################### +modelfile <- "/HdiSamples/HdiSamples/NYCTaxi/SparkGlmModel" +input <- "/HdiSamples/HdiSamples/NYCTaxi/NYCjoinedParquetSubsetSampled" +output <- "/HdiSamples/HdiSamples/NYCTaxi/SparkRGLMPredictions" +web_scoring (modelfile, input, output) + +################################################################ +## LOGIN TO SERVER AND LIST ANY EXISTING WEB SERVICES +################################################################ +library(mrsdeploy) +#ssh -L localhost:12800:localhost:12800 remoteuser@DebrajSpark2-ed-ssh.azurehdinsight.net +remoteLogin( + "http://127.0.0.1:12800", + username = "admin", + password = "", + session = FALSE +) +listServices() + +################################################################ +## CREATE A WEB SERVICE WTIH VERSION NUMBER +################################################################ + +version <- "v0.0.1" +#deleteService("scoring_input_files2", version); +api_string <- publishService( + "scoring_input_files2", + code = web_scoring, + inputs = list(modelfile = "character", + input = "character", + output = "character"), + v = version +) +listServices() + +################################################################ +## CALL WEB SERVICE +################################################################ +version <- "v0.0.1" +api_1 <- getService("scoring_input_files2", version) + +modelfile <- "/HdiSamples/HdiSamples/NYCTaxi/SparkGlmModel" +input <- "/HdiSamples/HdiSamples/NYCTaxi/NYCjoinedParquetSubsetSampled" +output <- "/HdiSamples/HdiSamples/NYCTaxi/SparkRGLMPredictions" + +result_1 <- api_1$web_scoring( + modelfile = modelfile, + input = input, + output = output +) + +result_1$success + +system("hadoop fs -ls /HdiSamples/HdiSamples/NYCTaxi/SparkRGLMPredictions") + +################################################################ +## END +################################################################ + +sparkR.stop() +#deleteService("scoring_input_files", version) + + + +################################################################# +################################################################# +## ADDITIONAL CODE FOR MODEL CREATION AND SAVING +################################################################# +################################################################# + + +########################################### +## READ IN FILE, SAMPLE AND PARTITION +########################################### +fullDataDir <- "/HdiSamples/HdiSamples/NYCTaxi/NYCjoinedParquetSubset" +df <- read.df(fullDataDir, source = "parquet", header = "true", inferSchema = "true", na.strings = "NA") +dfSampled <- SparkR::sample(df, withReplacement = FALSE, fraction = 0.01, seed = 123) +SparkR::write.parquet(dfSampled, "/HdiSamples/HdiSamples/NYCTaxi/NYCjoinedParquetSubsetSampled") +cache(dfSampled); count(dfSampled) +printSchema(dfSampled) + +########################################### +## CREATE GLM MODEL +########################################### +sampledDataDir <- "/HdiSamples/HdiSamples/NYCTaxi/NYCjoinedParquetSubsetSampled" +df <- read.df(sampledDataDir, source = "parquet", header = "true", inferSchema = "true", na.strings = "NA") +model <- SparkR::spark.glm(tip_amount ~ payment_type + pickup_hour + + fare_amount + passenger_count + trip_distance + + trip_time_in_secs + TrafficTimeBins, + data = df, family = "gaussian", + tol = 1e-05, maxIter = 10) + +########################################### +## SAVE MODEL +########################################### +modelPath = "/HdiSamples/HdiSamples/NYCTaxi/SparkRGLMforOper" +write.ml(model, modelPath) + +########################################### +## PREDICT ON A DATAFRAME +########################################### +#predictions <- SparkR::predict(model, newData = dfSampled) +#predfilt <- SparkR::select(predictions, c("label","prediction")) +#df_local <- SparkR::collect(predfilt) diff --git a/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forDSVM.Rmd b/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forDSVM.Rmd new file mode 100644 index 00000000..8378230d --- /dev/null +++ b/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forDSVM.Rmd @@ -0,0 +1,408 @@ +--- +title: "Using SparkR with 2013 NYCTaxi Data: Data wrangling, manipulations, modeling, and evaluation" +date: "`r format(Sys.time(), '%B %d, %Y')`" +author: "Algorithms and Data Science & R Server Teams, Microsoft Data Group" +output: + html_document: + fig_caption: yes + fig_height: 4 + fig_width: 4 + highlight: haddock + keep_md: yes + number_sections: yes + theme: journal + toc: yes + toc_float: yes +runtime: knit +--- + +
+#Introduction +This Markdown document shows the use of SparkR for data wrangling, manipulation, and creating machine learning models. The data used for this exercise is the public NYC Taxi Trip and Fare data-set (2013, December, ~4 Gb, ~13 million rows) available from: http://www.andresmh.com/nyctaxitrips. Data for this exercise can be downloaded from the public blob (see below). The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here. + +We use Spark SQL for many of the data wrangling tasks. For plotting and visualization, small amounts of data from Spark dataframes are transformed to the local data frames. +
+
+ +#Creating spark connection, loading packages +```{r Load Packages, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# LOAD LIBRARIES FROM SPECIFIED PATH +########################################### +Sys.setenv(YARN_CONF_DIR="/opt/hadoop/current/etc/hadoop", + HADOOP_HOME="/opt/hadoop/current", + JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64", + SPARK_HOME = "/dsvm/tools/spark/current", + PATH=paste0(Sys.getenv("PATH"),":/opt/hadoop/current/bin:/dsvm/tools/spark/current/bin")) +### *** NOTE: Spark paths are different in DSVM and HDI cluster *** ### + +.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) +library(SparkR) +library(rmarkdown) +library(knitr) +library(gridExtra) +library(ggplot2) + +########################################### +# CREATE SPARK CONTEXT +########################################### +sc <- sparkR.session( + sparkPackages = "com.databricks:spark-csv_2.10:1.3.0" +) +SparkR::setLogLevel("OFF") +### *** NOTE: spark.master can be set to use "yarn" on HDInsight clusters *** ### + +########################################### +## SPECIFY BASE HDFS DIRECTORY +########################################### +fullDataDir <- "/user/RevoShare/remoteuser/Data" +system("hadoop fs -ls /user/RevoShare/remoteuser/Data") +### *** NOTE: HDFS file paths are different in DSVM and HDInsight cluster *** ### + +``` +
+
+ + +#Reading files from HDFS (csv or parquet format) +Data for this exercise can be downloaded from the public blob locations below: +
+1. Trip (Csv): http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/KDD2016/trip_data_12.csv +
+2. Fare (Csv): http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/KDD2016/trip_fare_12.csv +
+The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here. The csv files can be read into Spark context and saved in parquet format. Once saved in parquet format, data can be read in much more quickly than csv files. + +##Read in data from csv files +```{r Read in files, message=FALSE, warning=FALSE, echo=TRUE} +starttime <- Sys.time(); +########################################### +# LIST FILES FROM HDFS +########################################### +system("hadoop fs -ls /user/RevoShare/remoteuser/Data") + +########################################### +# TRIP FILE (parquet format, CSV only shown for reference) +########################################### +#tripPathCSV <- file.path(fullDataDir, "trip_data_12.csv") +#tripDF <- read.df(tripPathCSV, source = "com.databricks.spark.csv", header = "true", inferSchema = "true") +#SparkR::cache(tripDF); SparkR::count(tripDF) +#write.df(tripDF, file.path(fullDataDir, "TripData2013DecParquet"), "parquet", "overwrite") +tripPath <- file.path(fullDataDir, "TripData2013DecParquet") +tripDF <- read.df (tripPath, source = "parquet") +printSchema(tripDF) +SparkR::cache(tripDF); SparkR::count(tripDF) +head(tripDF, 3) + +########################################### +# FARE FILE (parquet format) +########################################### +farePath <- file.path(fullDataDir, "FareData2013DecParquet") +fareDF <- read.df (farePath, source = "parquet") +printSchema(fareDF) +SparkR::cache(fareDF); SparkR::count(fareDF) +head(fareDF, 3) + +endtime <- Sys.time(); +print (endtime - starttime); +``` + +#Data wrangling & cleanup using SQL +SparkR is an R package that provides a light-weight frontend to use Apache Spark from R. In Spark 2.0, SparkR provides a distributed data frame implementation that supports operations like selection, filtering, aggregation etc. (similar to R data frames, dplyr) but on large datasets. SparkR also provides support for distributed machine learning using MLlib. + +You can register dataframes as tables in SQLContext and join using multiple columns. The following SQL also filters the data for some outliers. +```{r Register tables, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# 1. REGISTER TABLES AND JOIN ON MULTIPLE COLUMNS, FILTER DATA +# 2. REGISTER JIONED TABLE +########################################### +starttime <- Sys.time(); + +createOrReplaceTempView(tripDF, "trip") +createOrReplaceTempView(fareDF, "fare") + +trip_fareDF <- SparkR::sql("SELECT + f.pickup_datetime, hour(f.pickup_datetime) as pickup_hour, + t.dropoff_datetime, hour(t.dropoff_datetime) as dropoff_hour, + f.vendor_id, f.fare_amount, f.surcharge, f.tolls_amount, + f.tip_amount, f.payment_type, t.rate_code, + t.passenger_count, t.trip_distance, t.trip_time_in_secs, + t.pickup_longitude, t.pickup_latitude, t.dropoff_longitude, + t.dropoff_latitude + FROM trip t, fare f + WHERE t.medallion = f.medallion AND t.hack_license = f.hack_license + AND t.pickup_datetime = f.pickup_datetime + AND t.passenger_count > 0 and t.passenger_count < 8 + AND f.tip_amount >= 0 AND f.tip_amount <= 15 + AND f.fare_amount >= 1 AND f.fare_amount <= 150 + AND f.tip_amount < f.fare_amount AND t.trip_distance > 0 + AND t.trip_distance <= 40 AND t.trip_distance >= 1 + AND t.trip_time_in_secs >= 30 AND t.trip_time_in_secs <= 7200 + AND t.rate_code <= 5 AND f.payment_type in ('CSH','CRD')") +createOrReplaceTempView(trip_fareDF, "trip_fare") + +########################################### +# Cache joined DF in memory +########################################### +SparkR::cache(trip_fareDF); SparkR::count(trip_fareDF); + +########################################### +# SHOW REGISTERED TABLES +########################################### +head(SparkR::sql("show tables")) + +endtime <- Sys.time(); +print (endtime - starttime); +``` + + +#Feature engineering using SQL +You can create new features using sQL statements. For example, you can use case statements to generate categorical features from coneunuous (numerical) ones. +```{r Feature engineering, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# CREATE FEATURES IN SQL USING CASE STATEMENTS +########################################### +starttime <- Sys.time(); + +trip_fare_feat <- SparkR::sql("SELECT + payment_type, pickup_hour, fare_amount, tip_amount, + passenger_count, trip_distance, trip_time_in_secs, + CASE + WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN 'Night' + WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN 'AMRush' + WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN 'Afternoon' + WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN 'PMRush' + END as TrafficTimeBins, + CASE + WHEN (tip_amount > 0) THEN 1 + WHEN (tip_amount <= 0) THEN 0 + END as tipped + FROM trip_fare") + +SparkR::cache(trip_fare_feat); SparkR::count(trip_fare_feat); +createOrReplaceTempView(trip_fare_feat, "trip_fare_feat") +head(trip_fare_feat, 3) + +endtime <- Sys.time(); +print (endtime - starttime); +``` +
+ +#Data visualization +##Plots to inspect data and relationships between variables +For visualization, a small portion data will have to be sampled and brought into local memory as a data.frame object. R's plotting functions (e.g. from those in ggplot package) can then be applied to the data.frame for visualization. +```{r Exploration and visualization, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=4} +########################################### +# SAMPLE SMALL PORTION OF DATA FOR VISUALIZATION +########################################### +starttime <- Sys.time(); + +trip_fare_featSampled <- SparkR::sample(trip_fare_feat, withReplacement=FALSE, + fraction=0.0001, seed=123) + +########################################### +# CONVERT SPARK DF TO LOCAL DATA.FRAME IN MEMORY OF R-SERVER EDGE NODE +########################################### +trip_fare_featSampledDF <- as.data.frame(trip_fare_featSampled); + +########################################### +# Generate HISTOGRAM OF TIP AMOUNT +########################################### +hist <- ggplot(trip_fare_featSampledDF, aes(x=tip_amount)) + + geom_histogram(binwidth = 0.5, aes(fill = ..count..)) + + scale_fill_gradient("Count", low = "green", high = "red") + + labs(title="Histogram for Tip Amount"); + +########################################### +# Generate Scatter Plot OF TRIP DISTANCE vs. TIP AMOUNT +########################################### +scatter <- ggplot(trip_fare_featSampledDF, aes(tip_amount, trip_distance)) + + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + + labs(title="Tip amount vs. trip distance"); + +########################################### +# Plot Histogram and Scatter Plot OF TIP AMOUNT Side by Side +########################################### +grid.arrange(hist, scatter, ncol=2) + +endtime <- Sys.time(); +print (endtime - starttime); +``` + +##Advanced summarization using SQL and plotting: +We explore trips in NYC during rush and non-rush hours. This section shows more examples of SQL and advanced plotting and visualization, using ggmap. We plot the number of trips by day of the month, as well as number of trips on the NY City map during rush and non-rush hours. +```{r Advanced SQL and Visualization 1, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=4} +########################################### +# GROUP TRIPS BY YEAR, MONTH, DAY, PAYMENT TYPE +########################################### +starttime <- Sys.time(); + +trip_stats_by_day <- SparkR::sql("select + year(pickup_datetime) as year, month(pickup_datetime) as month, + day(pickup_datetime) as day, payment_type, count(1) as trips + from fare + where payment_type in ('CSH','CRD') + group by year(pickup_datetime), month(pickup_datetime), + day(pickup_datetime), payment_type") +tsbd <- as.data.frame(trip_stats_by_day) + +########################################### +# PLOT NUMBER OF TRIPS BY DAY IN DEC 2013 +########################################### +ggplot(data=tsbd, aes(day, trips)) + + geom_point(aes(color=payment_type)) + + geom_smooth(aes(color=payment_type)) + +endtime <- Sys.time(); +print (endtime - starttime); +``` + +#Modeling with SparkR +##Down-sample data for modeling +If a data-set is large, it may need to be down-sampled for modeling in reasonable amount of time. Here we used the sample function from SparkR to down-sample the joined trip-fare data. We then save the data in HDFS for use as input into the sparklyr modeling functions. +```{r Downsample data for training, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# SAMPLE DATA FOR MODELING +########################################### +starttime <- Sys.time(); + +trip_fare_featSampled <- SparkR::sample(trip_fare_feat, withReplacement=FALSE, + fraction=0.1, seed=123) +SparkR::cache(trip_fare_featSampled); SparkR::count(trip_fare_featSampled); +createOrReplaceTempView(trip_fare_featSampled, "trip_fare_featSampled") + +endtime <- Sys.time(); +print (endtime - starttime); +``` + + +##Partition data into train/test +```{r Partition data, message=FALSE, warning=FALSE, echo=TRUE, fig.width=4, fig.height=4} +########################################### +# PARTITION DATA INTO TRAIN-TEST USIN SQL +########################################### +dfrand <- SparkR::sql("SELECT *, RAND() as randnum from trip_fare_featSampled" ); +trainDF <- SparkR::filter(dfrand, dfrand$randnum <= 0.7) +testDF <- SparkR::filter(dfrand, dfrand$randnum > 0.7) +head(trainDF, 3) + +endtime <- Sys.time(); +print (endtime - starttime); +``` + +##Create a SparkR::glm model +```{r Create model, message=FALSE, warning=FALSE, echo=TRUE, fig.width=4, fig.height=4} +########################################### +## CREATE GLM MODEL +########################################### +starttime <- Sys.time(); + +model <- SparkR::spark.glm(tip_amount ~ payment_type + pickup_hour + + fare_amount + passenger_count + trip_distance + + trip_time_in_secs + TrafficTimeBins, + data = trainDF, tol = 1e-05, maxIter = 10) +print (summary(model)); + +endtime <- Sys.time(); +print (endtime - starttime); +``` + +##Evaluate on a test set +```{r Evaluate on test set, message=FALSE, warning=FALSE, echo=TRUE, fig.width=4, fig.height=4} +########################################### +## PREDICT ON TEST SET, AND EVALUATE ACCURACY +########################################### +starttime <- Sys.time(); + +predictions <- SparkR::predict(model, newData = testDF) +predictions_sampled <- SparkR::sample(predictions, withReplacement=FALSE, + fraction=0.1, seed=123) +predfilt <- SparkR::select(predictions_sampled, c("label","prediction")) +predfilt_local <- SparkR::collect(predfilt) +## We sample prediction DF, since the collect operation creates a regular DF in memory, and takes a long time + +# Evaluate and plot predictions (R-sqr) +Rsqr = cor(predfilt_local$label, predfilt_local$prediction)^2; Rsqr; + +# Sample predictions for plotting +predictedSampled <- predfilt_local[base::sample(1:nrow(predfilt_local), 1000),] + +# Plot predicted vs. actual values +lm_model <- lm(prediction ~ label, data = predictedSampled) +ggplot(predictedSampled, aes(label, prediction)) + + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + + geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], + intercept = summary(lm_model)$coefficients[1,1]), + color = "red") + +endtime <- Sys.time(); +print (endtime - starttime); +``` + + +#Save data & model +##Save model for deployment +Partition data into train/test, and train a glm model and evaluate it. +```{r Persist model, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +## SAVE MODEL [REMOVE FILE IF IT EXISTS] +########################################### +if (length(system("hadoop fs -ls /user/RevoShare/remoteuser/Models/SparkGlmModel", intern=TRUE))>=1) { + system("hadoop fs -rm -r /user/RevoShare/remoteuser/Models/SparkGlmModel") +} +modelPath = file.path("/user/RevoShare/remoteuser/Models", "SparkGlmModel"); +write.ml(model, modelPath) + +``` + +##Save joined and sampled data +Repartition the data in specific number of chunks and save +```{r Downsample and save data, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# REPARTITION DATA FOR SAVING +########################################### +starttime <- Sys.time(); + +trip_fare_featRepartitioned <- repartition(trip_fare_feat, 10) # write.df below will produce this many files + +########################################### +# SAVE DATAFRANE AS PARQUET file +########################################### +write.df(df=trip_fare_featRepartitioned, + path=file.path(fullDataDir, "NYCjoinedParquetSubset"), + source="parquet", mode="overwrite") + +endtime <- Sys.time(); +print (endtime - starttime); +``` + +#Uncache data and exit spark +```{r UNcache and exit , message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# UNPERSIST CACHED DATA FRAME +########################################### +SparkR::unpersist(tripDF) +SparkR::unpersist(fareDF) +SparkR::unpersist(trip_fareDF) +SparkR::unpersist(trip_fare_feat) +SparkR::unpersist(trip_fare_featSampled) + +########################################### +# LIST FILES FROM HDFS +########################################### +system("hadoop fs -ls /user/RevoShare/remoteuser/Data/NYCjoinedParquetSubset") +system("hadoop fs -ls /user/RevoShare/remoteuser/Models") + +########################################### +# STOP SPARKR CONTEXT +########################################### +sparkR.stop() +``` + +
+
+
+
+ +#Summary +The examples shown here can be adopted to fit other data exploration and modeling scenarios having different data-types or prediction tasks (e.g. classification). diff --git a/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forDSVM.html b/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forDSVM.html new file mode 100644 index 00000000..cbaa42d2 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forDSVM.html @@ -0,0 +1,772 @@ + + + + + + + + + + + + + + + +Using SparkR with 2013 NYCTaxi Data: Data wrangling, manipulations, modeling, and evaluation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + +
+
+

1 Introduction

+

This Markdown document shows the use of SparkR for data wrangling, manipulation, and creating machine learning models. The data used for this exercise is the public NYC Taxi Trip and Fare data-set (2013, December, ~4 Gb, ~13 million rows) available from: http://www.andresmh.com/nyctaxitrips. Data for this exercise can be downloaded from the public blob (see below). The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here.

+We use Spark SQL for many of the data wrangling tasks. For plotting and visualization, small amounts of data from Spark dataframes are transformed to the local data frames. +
+


+
+
+

2 Creating spark connection, loading packages

+
###########################################
+# LOAD LIBRARIES FROM SPECIFIED PATH
+###########################################
+Sys.setenv(YARN_CONF_DIR="/opt/hadoop/current/etc/hadoop", 
+           HADOOP_HOME="/opt/hadoop/current", 
+           JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64",
+           SPARK_HOME = "/dsvm/tools/spark/current",
+           PATH=paste0(Sys.getenv("PATH"),":/opt/hadoop/current/bin:/dsvm/tools/spark/current/bin"))
+### *** NOTE: Spark paths are different in DSVM and HDI cluster *** ###
+
+.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths()))
+library(SparkR)
+library(rmarkdown)
+library(knitr)
+library(gridExtra)
+library(ggplot2)
+
+###########################################
+# CREATE SPARK CONTEXT
+###########################################
+sc <- sparkR.session(
+  sparkPackages = "com.databricks:spark-csv_2.10:1.3.0"
+)
+
## Launching java with spark-submit command /dsvm/tools/spark/current/bin/spark-submit  --packages com.databricks:spark-csv_2.10:1.3.0 sparkr-shell /tmp/Rtmp6Osd0y/backend_port6b965773f8b7
+
SparkR::setLogLevel("OFF")
+
## NULL
+
### *** NOTE: spark.master can be set to use "yarn" on HDInsight clusters *** ###
+
+###########################################
+## SPECIFY BASE HDFS DIRECTORY
+###########################################
+fullDataDir <- "/user/RevoShare/remoteuser/Data"
+system("hadoop fs -ls /user/RevoShare/remoteuser/Data")
+### *** NOTE: HDFS file paths are different in DSVM and HDInsight cluster *** ###
+
+
+
+
+

3 Reading files from HDFS (csv or parquet format)

+

Data for this exercise can be downloaded from the public blob locations below:
1. Trip (Csv): http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/KDD2016/trip_data_12.csv
2. Fare (Csv): http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/KDD2016/trip_fare_12.csv
The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here. The csv files can be read into Spark context and saved in parquet format. Once saved in parquet format, data can be read in much more quickly than csv files.

+
+

3.1 Read in data from csv files

+
starttime <- Sys.time();
+###########################################
+# LIST FILES FROM HDFS
+###########################################
+system("hadoop fs -ls /user/RevoShare/remoteuser/Data")
+
+###########################################
+# TRIP FILE (parquet format, CSV only shown for reference)
+###########################################
+#tripPathCSV <- file.path(fullDataDir, "trip_data_12.csv")
+#tripDF <- read.df(tripPathCSV, source = "com.databricks.spark.csv", header = "true", inferSchema = "true")
+#SparkR::cache(tripDF); SparkR::count(tripDF)
+#write.df(tripDF, file.path(fullDataDir, "TripData2013DecParquet"), "parquet", "overwrite")
+tripPath <- file.path(fullDataDir, "TripData2013DecParquet")
+tripDF <- read.df (tripPath, source = "parquet")
+printSchema(tripDF)
+
## root
+##  |-- medallion: string (nullable = true)
+##  |-- hack_license: string (nullable = true)
+##  |-- vendor_id: string (nullable = true)
+##  |-- rate_code: integer (nullable = true)
+##  |-- store_and_fwd_flag: string (nullable = true)
+##  |-- pickup_datetime: timestamp (nullable = true)
+##  |-- dropoff_datetime: timestamp (nullable = true)
+##  |-- passenger_count: integer (nullable = true)
+##  |-- trip_time_in_secs: integer (nullable = true)
+##  |-- trip_distance: double (nullable = true)
+##  |-- pickup_longitude: double (nullable = true)
+##  |-- pickup_latitude: double (nullable = true)
+##  |-- dropoff_longitude: double (nullable = true)
+##  |-- dropoff_latitude: double (nullable = true)
+
SparkR::cache(tripDF); SparkR::count(tripDF)
+
## SparkDataFrame[medallion:string, hack_license:string, vendor_id:string, rate_code:int, store_and_fwd_flag:string, pickup_datetime:timestamp, dropoff_datetime:timestamp, passenger_count:int, trip_time_in_secs:int, trip_distance:double, pickup_longitude:double, pickup_latitude:double, dropoff_longitude:double, dropoff_latitude:double]
+
## [1] 13971118
+
head(tripDF, 3)
+
##                          medallion                     hack_license
+## 1 274157A7E6A1820B13BB747B086462AD AB02E290BE9B06110B56E58EC34AEBEB
+## 2 08257250116FC8B672F7B148D8D023B9 F98F7866BAEE6E0E2F1ADA23AA1578FE
+## 3 135A8EDC6EBD6F4397B7A6D281C75AB0 2A560563544C372864592E0DA3FB34AE
+##   vendor_id rate_code store_and_fwd_flag     pickup_datetime
+## 1       VTS         1                 NA 2013-12-01 13:02:00
+## 2       VTS         1                 NA 2013-12-01 13:20:00
+## 3       VTS         1                 NA 2013-12-01 13:23:00
+##      dropoff_datetime passenger_count trip_time_in_secs trip_distance
+## 1 2013-12-01 13:20:00               1              1080          3.77
+## 2 2013-12-01 13:29:00               1               540          1.79
+## 3 2013-12-01 13:31:00               5               480          0.75
+##   pickup_longitude pickup_latitude dropoff_longitude dropoff_latitude
+## 1        -74.00285        40.76052         -74.01007         40.71142
+## 2        -74.00649        40.73297         -74.01484         40.71127
+## 3        -73.96761        40.76290         -73.97512         40.76524
+
###########################################
+# FARE FILE (parquet format)
+###########################################
+farePath <- file.path(fullDataDir, "FareData2013DecParquet")
+fareDF <- read.df (farePath, source = "parquet")
+printSchema(fareDF)
+
## root
+##  |-- medallion: string (nullable = true)
+##  |-- hack_license: string (nullable = true)
+##  |-- vendor_id: string (nullable = true)
+##  |-- pickup_datetime: timestamp (nullable = true)
+##  |-- payment_type: string (nullable = true)
+##  |-- fare_amount: double (nullable = true)
+##  |-- surcharge: double (nullable = true)
+##  |-- mta_tax: double (nullable = true)
+##  |-- tip_amount: double (nullable = true)
+##  |-- tolls_amount: double (nullable = true)
+##  |-- total_amount: double (nullable = true)
+
SparkR::cache(fareDF); SparkR::count(fareDF)
+
## SparkDataFrame[medallion:string, hack_license:string, vendor_id:string, pickup_datetime:timestamp, payment_type:string, fare_amount:double, surcharge:double, mta_tax:double, tip_amount:double, tolls_amount:double, total_amount:double]
+
## [1] 13971118
+
head(fareDF, 3)
+
##                          medallion                     hack_license
+## 1 343EE0E83B908DC51AA01245FDC32974 29BABE0FFB16752B5A1DC5E4AD91EA4A
+## 2 1803A06155B880D1027E09C12042C949 5D03700E691CEC54E6EA1E13E846DBAA
+## 3 E79F0C20DD4108BCD139A88F90220099 412977BC1257772EE2A0FE5F52628BEB
+##   vendor_id     pickup_datetime payment_type fare_amount surcharge mta_tax
+## 1       CMT 2013-12-17 22:00:32          CSH         6.0       0.5     0.5
+## 2       CMT 2013-12-17 22:07:01          CSH         7.0       0.5     0.5
+## 3       CMT 2013-12-17 22:08:20          CSH         4.5       0.5     0.5
+##   tip_amount tolls_amount total_amount
+## 1          0            0          7.0
+## 2          0            0          8.0
+## 3          0            0          5.5
+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 44.65277 secs
+
+
+
+

4 Data wrangling & cleanup using SQL

+

SparkR is an R package that provides a light-weight frontend to use Apache Spark from R. In Spark 2.0, SparkR provides a distributed data frame implementation that supports operations like selection, filtering, aggregation etc. (similar to R data frames, dplyr) but on large datasets. SparkR also provides support for distributed machine learning using MLlib.

+

You can register dataframes as tables in SQLContext and join using multiple columns. The following SQL also filters the data for some outliers.

+
###########################################
+# 1. REGISTER TABLES AND JOIN ON MULTIPLE COLUMNS, FILTER DATA
+# 2. REGISTER JIONED TABLE
+###########################################
+starttime <- Sys.time();
+
+createOrReplaceTempView(tripDF, "trip")
+createOrReplaceTempView(fareDF, "fare")
+
+trip_fareDF <-  SparkR::sql("SELECT 
+  f.pickup_datetime, hour(f.pickup_datetime) as pickup_hour, 
+  t.dropoff_datetime, hour(t.dropoff_datetime) as dropoff_hour,
+  f.vendor_id, f.fare_amount, f.surcharge, f.tolls_amount, 
+  f.tip_amount, f.payment_type, t.rate_code, 
+  t.passenger_count, t.trip_distance, t.trip_time_in_secs, 
+  t.pickup_longitude, t.pickup_latitude, t.dropoff_longitude, 
+  t.dropoff_latitude
+  FROM trip t, fare f  
+  WHERE t.medallion = f.medallion AND t.hack_license = f.hack_license 
+  AND t.pickup_datetime = f.pickup_datetime 
+  AND t.passenger_count > 0 and t.passenger_count < 8 
+  AND f.tip_amount >= 0 AND f.tip_amount <= 15 
+  AND f.fare_amount >= 1 AND f.fare_amount <= 150 
+  AND f.tip_amount < f.fare_amount AND t.trip_distance > 0 
+  AND t.trip_distance <= 40 AND t.trip_distance >= 1
+  AND t.trip_time_in_secs >= 30 AND t.trip_time_in_secs <= 7200 
+  AND t.rate_code <= 5 AND f.payment_type in ('CSH','CRD')")
+createOrReplaceTempView(trip_fareDF, "trip_fare")
+
+###########################################
+# Cache joined DF in memory
+###########################################
+SparkR::cache(trip_fareDF); SparkR::count(trip_fareDF);
+
## SparkDataFrame[pickup_datetime:timestamp, pickup_hour:int, dropoff_datetime:timestamp, dropoff_hour:int, vendor_id:string, fare_amount:double, surcharge:double, tolls_amount:double, tip_amount:double, payment_type:string, rate_code:int, passenger_count:int, trip_distance:double, trip_time_in_secs:int, pickup_longitude:double, pickup_latitude:double, dropoff_longitude:double, dropoff_latitude:double]
+
## [1] 10710669
+
###########################################
+# SHOW REGISTERED TABLES
+###########################################
+head(SparkR::sql("show tables"))
+
##   tableName isTemporary
+## 1      fare        TRUE
+## 2      trip        TRUE
+## 3 trip_fare        TRUE
+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 1.090564 mins
+
+
+

5 Feature engineering using SQL

+

You can create new features using sQL statements. For example, you can use case statements to generate categorical features from coneunuous (numerical) ones.

+
###########################################
+# CREATE FEATURES IN SQL USING CASE STATEMENTS
+###########################################
+starttime <- Sys.time();
+
+trip_fare_feat <- SparkR::sql("SELECT 
+    payment_type, pickup_hour, fare_amount, tip_amount, 
+    passenger_count, trip_distance, trip_time_in_secs, 
+  CASE
+    WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN 'Night'
+    WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN 'AMRush' 
+    WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN 'Afternoon'
+    WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN 'PMRush'
+    END as TrafficTimeBins,
+  CASE
+    WHEN (tip_amount > 0) THEN 1 
+    WHEN (tip_amount <= 0) THEN 0 
+    END as tipped
+  FROM trip_fare")
+
+SparkR::cache(trip_fare_feat); SparkR::count(trip_fare_feat);
+
## SparkDataFrame[payment_type:string, pickup_hour:int, fare_amount:double, tip_amount:double, passenger_count:int, trip_distance:double, trip_time_in_secs:int, TrafficTimeBins:string, tipped:int]
+
## [1] 10710669
+
createOrReplaceTempView(trip_fare_feat, "trip_fare_feat")
+head(trip_fare_feat, 3)
+
##   payment_type pickup_hour fare_amount tip_amount passenger_count
+## 1          CRD          15         8.0        1.7               1
+## 2          CRD           1        12.0        2.6               2
+## 3          CRD          23        11.5        1.0               2
+##   trip_distance trip_time_in_secs TrafficTimeBins tipped
+## 1           1.6               532       Afternoon      1
+## 2           3.1               653           Night      1
+## 3           2.6               812           Night      1
+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 6.499619 secs
+
+
+
+

6 Data visualization

+
+

6.1 Plots to inspect data and relationships between variables

+

For visualization, a small portion data will have to be sampled and brought into local memory as a data.frame object. R’s plotting functions (e.g. from those in ggplot package) can then be applied to the data.frame for visualization.

+
###########################################
+# SAMPLE SMALL PORTION OF DATA FOR VISUALIZATION
+###########################################
+starttime <- Sys.time();
+
+trip_fare_featSampled <- SparkR::sample(trip_fare_feat, withReplacement=FALSE, 
+                                fraction=0.0001, seed=123)
+
+###########################################
+# CONVERT SPARK DF TO LOCAL DATA.FRAME IN MEMORY OF R-SERVER EDGE NODE
+###########################################
+trip_fare_featSampledDF <- as.data.frame(trip_fare_featSampled);
+
+###########################################
+# Generate HISTOGRAM OF TIP AMOUNT
+###########################################
+hist <- ggplot(trip_fare_featSampledDF, aes(x=tip_amount)) + 
+  geom_histogram(binwidth = 0.5, aes(fill = ..count..)) + 
+  scale_fill_gradient("Count", low = "green", high = "red") + 
+  labs(title="Histogram for Tip Amount");
+
+###########################################
+# Generate Scatter Plot OF TRIP DISTANCE vs. TIP AMOUNT
+###########################################
+scatter <- ggplot(trip_fare_featSampledDF, aes(tip_amount, trip_distance)) + 
+  geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + 
+  labs(title="Tip amount vs. trip distance");
+
+###########################################
+# Plot Histogram and Scatter Plot OF TIP AMOUNT Side by Side
+###########################################
+grid.arrange(hist, scatter, ncol=2)
+

+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 1.678182 secs
+
+
+

6.2 Advanced summarization using SQL and plotting:

+

We explore trips in NYC during rush and non-rush hours. This section shows more examples of SQL and advanced plotting and visualization, using ggmap. We plot the number of trips by day of the month, as well as number of trips on the NY City map during rush and non-rush hours.

+
###########################################
+# GROUP TRIPS BY YEAR, MONTH, DAY, PAYMENT TYPE
+###########################################
+starttime <- Sys.time();
+
+trip_stats_by_day <- SparkR::sql("select 
+      year(pickup_datetime) as year, month(pickup_datetime) as month, 
+      day(pickup_datetime) as day, payment_type, count(1) as trips 
+      from fare 
+      where payment_type in ('CSH','CRD')
+      group by year(pickup_datetime), month(pickup_datetime), 
+      day(pickup_datetime), payment_type")
+tsbd <- as.data.frame(trip_stats_by_day)
+
+###########################################
+# PLOT NUMBER OF TRIPS BY DAY IN DEC 2013
+###########################################
+ggplot(data=tsbd, aes(day, trips)) + 
+  geom_point(aes(color=payment_type)) + 
+  geom_smooth(aes(color=payment_type))
+

+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 5.599727 secs
+
+
+
+

7 Modeling with SparkR

+
+

7.1 Down-sample data for modeling

+

If a data-set is large, it may need to be down-sampled for modeling in reasonable amount of time. Here we used the sample function from SparkR to down-sample the joined trip-fare data. We then save the data in HDFS for use as input into the sparklyr modeling functions.

+
###########################################
+# SAMPLE DATA FOR MODELING
+###########################################
+starttime <- Sys.time();
+
+trip_fare_featSampled <- SparkR::sample(trip_fare_feat, withReplacement=FALSE, 
+                                fraction=0.1, seed=123)
+SparkR::cache(trip_fare_featSampled); SparkR::count(trip_fare_featSampled);
+
## SparkDataFrame[payment_type:string, pickup_hour:int, fare_amount:double, tip_amount:double, passenger_count:int, trip_distance:double, trip_time_in_secs:int, TrafficTimeBins:string, tipped:int]
+
## [1] 1071935
+
createOrReplaceTempView(trip_fare_featSampled, "trip_fare_featSampled")
+
+endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 1.761098 secs
+
+
+

7.2 Partition data into train/test

+
###########################################
+# PARTITION DATA INTO TRAIN-TEST USIN SQL
+###########################################
+dfrand <- SparkR::sql("SELECT *, RAND() as randnum from trip_fare_featSampled" );
+trainDF <- SparkR::filter(dfrand, dfrand$randnum <= 0.7)
+testDF <- SparkR::filter(dfrand, dfrand$randnum > 0.7)
+head(trainDF, 3)
+
##   payment_type pickup_hour fare_amount tip_amount passenger_count
+## 1          CRD           5        10.5        1.0               1
+## 2          CSH          13         7.5        0.0               1
+## 3          CRD          18         7.5        1.7               1
+##   trip_distance trip_time_in_secs TrafficTimeBins tipped   randnum
+## 1          2.80               451           Night      1 0.2592047
+## 2          1.00               571       Afternoon      0 0.2328304
+## 3          1.36               540          PMRush      1 0.2528229
+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 1.958512 secs
+
+
+

7.3 Create a SparkR::glm model

+
########################################### 
+## CREATE GLM MODEL
+###########################################
+starttime <- Sys.time();
+
+model <- SparkR::spark.glm(tip_amount ~ payment_type + pickup_hour + 
+                    fare_amount + passenger_count + trip_distance + 
+                    trip_time_in_secs + TrafficTimeBins, 
+                    data = trainDF, tol = 1e-05, maxIter = 10)
+print (summary(model));
+
## 
+## Deviance Residuals: 
+## (Note: These are approximate quantiles with relative error <= 0.01)
+##      Min        1Q    Median        3Q       Max  
+## -14.6279   -0.5462    0.0496    0.5797   12.0386  
+## 
+## Coefficients:
+##                            Estimate    Std. Error  t value  Pr(>|t|)  
+## (Intercept)                -1.36       0.0059253   -229.52  0         
+## payment_type_CRD           2.7302      0.0031755   859.77   0         
+## pickup_hour                0.0042837   0.0002578   16.616   0         
+## fare_amount                0.066823    0.00069331  96.383   0         
+## passenger_count            -0.0072438  0.001141    -6.3486  2.1736e-10
+## trip_distance              0.096706    0.0015684   61.659   0         
+## trip_time_in_secs          7.3482e-05  5.3107e-06  13.837   0         
+## TrafficTimeBins_Night      -0.031249   0.0048684   -6.4187  1.3748e-10
+## TrafficTimeBins_Afternoon  0.022284    0.0051946   4.2898   1.7888e-05
+## TrafficTimeBins_PMRush     0.034443    0.0057435   5.9968   2.0127e-09
+## 
+## (Dispersion parameter for gaussian family taken to be 1.847631)
+## 
+##     Null deviance: 3693142  on 750532  degrees of freedom
+## Residual deviance: 1386689  on 750523  degrees of freedom
+## AIC: 2590688
+## 
+## Number of Fisher Scoring iterations: 1
+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 18.08822 secs
+
+
+

7.4 Evaluate on a test set

+
########################################### 
+## PREDICT ON TEST SET, AND EVALUATE ACCURACY
+###########################################
+starttime <- Sys.time();
+
+predictions <- SparkR::predict(model, newData = testDF)
+predictions_sampled <- SparkR::sample(predictions, withReplacement=FALSE, 
+                                fraction=0.1, seed=123)
+predfilt <- SparkR::select(predictions_sampled, c("label","prediction"))
+predfilt_local <- SparkR::collect(predfilt)
+## We sample prediction DF, since the collect operation creates a regular DF in memory, and takes a long time
+
+# Evaluate and plot predictions (R-sqr)
+Rsqr = cor(predfilt_local$label, predfilt_local$prediction)^2; Rsqr;
+
## [1] 0.6187278
+
# Sample predictions for plotting
+predictedSampled <- predfilt_local[base::sample(1:nrow(predfilt_local), 1000),]
+
+# Plot predicted vs. actual values
+lm_model <- lm(prediction ~ label, data = predictedSampled)
+ggplot(predictedSampled, aes(label, prediction)) + 
+  geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2)   + 
+  geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], 
+                  intercept = summary(lm_model)$coefficients[1,1]), 
+              color = "red")
+

+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 1.864072 secs
+
+
+
+

8 Save data & model

+
+

8.1 Save model for deployment

+

Partition data into train/test, and train a glm model and evaluate it.

+
###########################################
+## SAVE MODEL [REMOVE FILE IF IT EXISTS]
+###########################################
+if (length(system("hadoop fs -ls /user/RevoShare/remoteuser/Models/SparkGlmModel", intern=TRUE))>=1) {
+  system("hadoop fs -rm -r /user/RevoShare/remoteuser/Models/SparkGlmModel")
+}
+modelPath =  file.path("/user/RevoShare/remoteuser/Models", "SparkGlmModel");
+write.ml(model, modelPath) 
+
+
+

8.2 Save joined and sampled data

+

Repartition the data in specific number of chunks and save

+
###########################################
+# REPARTITION DATA FOR SAVING
+###########################################
+starttime <- Sys.time();
+
+trip_fare_featRepartitioned <- repartition(trip_fare_feat, 10) # write.df below will produce this many files
+
+###########################################
+# SAVE DATAFRANE AS PARQUET file
+###########################################
+write.df(df=trip_fare_featRepartitioned, 
+         path=file.path(fullDataDir, "NYCjoinedParquetSubset"), 
+         source="parquet", mode="overwrite")
+
+endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 8.022501 secs
+
+
+
+

9 Uncache data and exit spark

+
###########################################
+# UNPERSIST CACHED DATA FRAME
+###########################################
+SparkR::unpersist(tripDF)
+
## SparkDataFrame[medallion:string, hack_license:string, vendor_id:string, rate_code:int, store_and_fwd_flag:string, pickup_datetime:timestamp, dropoff_datetime:timestamp, passenger_count:int, trip_time_in_secs:int, trip_distance:double, pickup_longitude:double, pickup_latitude:double, dropoff_longitude:double, dropoff_latitude:double]
+
SparkR::unpersist(fareDF)
+
## SparkDataFrame[medallion:string, hack_license:string, vendor_id:string, pickup_datetime:timestamp, payment_type:string, fare_amount:double, surcharge:double, mta_tax:double, tip_amount:double, tolls_amount:double, total_amount:double]
+
SparkR::unpersist(trip_fareDF)
+
## SparkDataFrame[pickup_datetime:timestamp, pickup_hour:int, dropoff_datetime:timestamp, dropoff_hour:int, vendor_id:string, fare_amount:double, surcharge:double, tolls_amount:double, tip_amount:double, payment_type:string, rate_code:int, passenger_count:int, trip_distance:double, trip_time_in_secs:int, pickup_longitude:double, pickup_latitude:double, dropoff_longitude:double, dropoff_latitude:double]
+
SparkR::unpersist(trip_fare_feat)
+
## SparkDataFrame[payment_type:string, pickup_hour:int, fare_amount:double, tip_amount:double, passenger_count:int, trip_distance:double, trip_time_in_secs:int, TrafficTimeBins:string, tipped:int]
+
SparkR::unpersist(trip_fare_featSampled)
+
## SparkDataFrame[payment_type:string, pickup_hour:int, fare_amount:double, tip_amount:double, passenger_count:int, trip_distance:double, trip_time_in_secs:int, TrafficTimeBins:string, tipped:int]
+
###########################################
+# LIST FILES FROM HDFS
+###########################################
+system("hadoop fs -ls /user/RevoShare/remoteuser/Data/NYCjoinedParquetSubset")
+system("hadoop fs -ls /user/RevoShare/remoteuser/Models")
+
+###########################################
+# STOP SPARKR CONTEXT
+###########################################
+sparkR.stop()
+
+
+
+


+
+
+

10 Summary

+

The examples shown here can be adopted to fit other data exploration and modeling scenarios having different data-types or prediction tasks (e.g. classification).

+
+ + + +
+
+ +
+ + + + + + + + diff --git a/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forHDICLuster.Rmd b/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forHDICLuster.Rmd new file mode 100644 index 00000000..c935abe6 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forHDICLuster.Rmd @@ -0,0 +1,510 @@ +--- +title: "Using SparkR with 2013 NYCTaxi Data: Data wrangling, manipulations, modeling, and evaluation" +date: "`r format(Sys.time(), '%B %d, %Y')`" +author: "Algorithms and Data Science & R Server Teams, Microsoft Data Group" +output: + html_document: + fig_caption: yes + fig_height: 4 + fig_width: 4 + highlight: haddock + keep_md: yes + number_sections: yes + theme: journal + toc: yes + toc_float: yes +runtime: knit +--- + +
+#Introduction +This Markdown document shows the use of SparkR for data wrangling, manipulation, and creating machine learning models. The data used for this exercise is the public NYC Taxi Trip and Fare data-set (2013, ~45 Gb, ~140 million rows) available from: http://www.andresmh.com/nyctaxitrips. Data for this exercise can be downloaded from the public blob (see below). The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here. + +We use Spark SQL for many of the data wrangling tasks. For plotting and visualization, small amounts of data from Spark dataframes are transformed to the local data frames. +
+
+ +#Creating spark context and loading packages +```{r Load Packages, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# LOAD LIBRARIES FROM SPECIFIED PATH +########################################### +.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) +library(SparkR) +library(rmarkdown) +library(knitr) +library(gridExtra) +library(ggplot2) +library(ggmap) + +########################################### +# CREATE SPARK CONTEXT +########################################### +sc <- sparkR.session( + sparkPackages = "com.databricks:spark-csv_2.10:1.3.0" +) +SparkR::setLogLevel("OFF") + +########################################### +## SPECIFY BASE HDFS DIRECTORY +########################################### +fullDataDir <- "/HdiSamples/HdiSamples/NYCTaxi" +``` +
+ + +#Reading in files from HDFS (csv or parquet) +Data for this exercise can be downloaded from the public blob locations below: +
+1. Trip (Csv): http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/combined_taxi_trip_w_header.csv (~19 Gb) +
+2. Fare (Csv): http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/combined_taxi_fare_w_header.csv (~28 Gb) +
+The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here. The csv files can be read into Spark context and saved in parquet format. Once saved in parquet format, data can be read in much more quickly than csv files. +```{r Read in files, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# TRIP FILE (CSV format, or PARQUET format) +########################################### +starttime <- Sys.time(); + +#tripPath <- file.path(fullDataDir, "combined_taxi_trip_w_header.csv") +#tripDF <- read.df(tripPath, source = "com.databricks.spark.csv", +# header = "true", inferSchema = "true") +#tripPathParquet <- file.path(fullDataDir, "CombinedTaxiTripParquet2013") +#tripDFRepartitioned <- repartition(tripDF, 20) # write.df below will produce this many files +#write.parquet(tripDFRepartitioned, tripPathParquet) + +tripPathParquet <- file.path(fullDataDir, "CombinedTaxiTripParquet2013") +tripDF <- read.parquet(tripPathParquet) +head(tripDF, 3) +printSchema(tripDF) + +########################################### +# FARE FILE (CSV or PARQUET format) +########################################### +#farePath <- file.path(fullDataDir, "combined_taxi_fare_w_header.csv") +#fareDF <- read.df(farePath, source = "com.databricks.spark.csv", +# header = "true", inferSchema = "true") +#farePathParquet <- file.path(fullDataDir, "CombinedTaxiFareParquet2013") +#fareDFRepartitioned <- repartition(fareDF, 20) # write.df below will produce this many files +#write.parquet(fareDFRepartitioned, farePathParquet) + +farePathParquet <- file.path(fullDataDir, "CombinedTaxiFareParquet2013") +fareDF <- read.parquet(farePathParquet) +SparkR::cache(fareDF); SparkR::count(fareDF); +head(fareDF, 3) +printSchema(fareDF) + +endtime <- Sys.time(); +print (endtime-starttime); +``` + + +#Using SparkR for data wrangling & manipulation +SparkR is an R package that provides a light-weight frontend to use Apache Spark from R. In Spark 2.0, SparkR provides a distributed data frame implementation that supports operations like selection, filtering, aggregation etc. (similar to R data frames, dplyr) but on large datasets. SparkR also provides support for distributed machine learning using MLlib. +
+ +##Join datasets using SQL +You can register dataframes as tables in SQLContext and join using multiple columns. The following SQL also filters the data for some outliers. +```{r Register tables, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# 1. REGISTER TABLES AND JOIN ON MULTIPLE COLUMNS, FILTER DATA +# 2. REGISTER JIONED TABLE +########################################### +starttime <- Sys.time(); + +createOrReplaceTempView(tripDF, "trip") +createOrReplaceTempView(fareDF, "fare") + +trip_fareDF <- SparkR::sql("SELECT + f.pickup_datetime, hour(f.pickup_datetime) as pickup_hour, + t.dropoff_datetime, hour(t.dropoff_datetime) as dropoff_hour, + f.vendor_id, f.fare_amount, f.surcharge, f.tolls_amount, + f.tip_amount, f.payment_type, t.rate_code, + t.passenger_count, t.trip_distance, t.trip_time_in_secs, + t.pickup_longitude, t.pickup_latitude, t.dropoff_longitude, + t.dropoff_latitude + FROM trip t, fare f + WHERE t.medallion = f.medallion AND t.hack_license = f.hack_license + AND t.pickup_datetime = f.pickup_datetime + AND t.passenger_count > 0 and t.passenger_count < 8 + AND f.tip_amount >= 0 AND f.tip_amount <= 15 + AND f.fare_amount >= 1 AND f.fare_amount <= 150 + AND f.tip_amount < f.fare_amount AND t.trip_distance > 0 + AND t.trip_distance <= 40 AND t.trip_distance >= 1 + AND t.trip_time_in_secs >= 30 AND t.trip_time_in_secs <= 7200 + AND t.rate_code <= 5 AND f.payment_type in ('CSH','CRD')") +createOrReplaceTempView(trip_fareDF, "trip_fare") +SparkR::cache(trip_fareDF); SparkR::count(trip_fareDF); + +########################################### +# WRITE JOINED FILE IN PARQUET FORMAT IN HDFS +########################################### +#tripfarePathParquet <- file.path(fullDataDir, "CombinedTaxi_Trip_and_Fare_Parquet2013") +#tripfareDFRepartitioned <- repartition(trip_fareDF, 20) # write.df below will produce this many files +#write.parquet(tripfareDFRepartitioned, tripfarePathParquet) + +########################################### +# SHOW REGISTERED TABLES +########################################### +head(SparkR::sql("show tables")) + +########################################### +# IF THE JOINED FILE IS SAVED, YOU CAN DIRECTLY READ IT IN +########################################### +#tripfarePathParquet <- file.path(fullDataDir, "CombinedTaxi_Trip_and_Fare_Parquet2013") +#trip_fareDF <- read.parquet(tripfarePathParquet) +#head(trip_fareDF, 3) +#printSchema(trip_fareDF) +#createOrReplaceTempView(trip_fareDF, "trip_fare") + +endtime <- Sys.time(); +print (endtime-starttime); +``` + + +#Feature engineering using SQL +You can create new features using sQL statements. For example, you can use case statements to generate categorical features from coneunuous (numerical) ones. +```{r Feature engineering, message=FALSE, warning=FALSE, echo=TRUE} +starttime <- Sys.time(); + +########################################### +# CREATE FEATURES IN SQL USING CASE STATEMENTS +########################################### +trip_fare_feat <- SparkR::sql("SELECT + payment_type, pickup_hour, fare_amount, tip_amount, + passenger_count, trip_distance, trip_time_in_secs, + CASE + WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN 'Night' + WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN 'AMRush' + WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN 'Afternoon' + WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN 'PMRush' + END as TrafficTimeBins, + CASE + WHEN (tip_amount > 0) THEN 1 + WHEN (tip_amount <= 0) THEN 0 + END as tipped + FROM trip_fare") + +SparkR::cache(trip_fare_feat); SparkR::count(trip_fare_feat); +createOrReplaceTempView(trip_fare_feat, "trip_fare_feat") +head(trip_fare_feat, 3) + +endtime <- Sys.time(); +print (endtime-starttime); +``` +
+ +#Data visualization +##Data exploration and plotting +For visualization, a small portion data will have to be sampled and brought into local memory as a data.frame object. R's plotting functions (e.g. from those in ggplot package) can then be applied to the data.frame for visualization. +```{r Exploration and visualization, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=4} +starttime <- Sys.time(); + +########################################### +# SAMPLE SMALL PORTION OF DATA +########################################### +trip_fare_featSampled <- SparkR::sample(trip_fare_feat, withReplacement=FALSE, + fraction=0.00001, seed=123) + +########################################### +# CONVERT SPARK DF TO LOCAL DATA.FRAME IN MEMORY OF R-SERVER EDGE NODE +########################################### +trip_fare_featSampledDF <- as.data.frame(trip_fare_featSampled); + +########################################### +# Generate HISTOGRAM OF TIP AMOUNT +########################################### +hist <- ggplot(trip_fare_featSampledDF, aes(x=tip_amount)) + + geom_histogram(binwidth = 0.5, aes(fill = ..count..)) + + scale_fill_gradient("Count", low = "green", high = "red") + + labs(title="Histogram for Tip Amount"); + +########################################### +# Generate Scatter Plot OF TRIP DISTANCE vs. TIP AMOUNT +########################################### +scatter <- ggplot(trip_fare_featSampledDF, aes(tip_amount, trip_distance)) + + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + + labs(title="Tip amount vs. trip distance"); + +########################################### +# Plot Histogram and Scatter Plot OF TIP AMOUNT Side by Side +########################################### +grid.arrange(hist, scatter, ncol=2) + +endtime <- Sys.time(); +print (endtime-starttime); +``` + +##Advanced SQL summarization and plotting: +###Trips in NYC area during rush and non-rush hours +This section shows more examples of SQL and advanced plotting and visualization, using ggmap. We plot the number of trips by day of the month, as well as number of trips on the NY City map during rush and non-rush hours. +```{r Advanced SQL and Visualization 1, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=4} +starttime <- Sys.time(); + +########################################### +# GROUP TRIPS BY YEAR, MONTH, DAY, PAYMENT TYPE +########################################### +trip_stats_by_day <- SparkR::sql("select + year(pickup_datetime) as year, month(pickup_datetime) as month, + day(pickup_datetime) as day, payment_type, count(1) as trips + from fare + where payment_type in ('CSH','CRD') + group by year(pickup_datetime), month(pickup_datetime), + day(pickup_datetime), payment_type") +tsbd <- as.data.frame(trip_stats_by_day) + +########################################### +# PLOT NUMBER OF TRIPS BY DAY IN DEC 2013 +########################################### +ggplot(data=tsbd, aes(day, trips)) + geom_point(aes(color=payment_type)) + geom_smooth(aes(color=payment_type)) + +########################################### +# PLOT TAXI PICKUPS BY HOUR TRAFFIC AND NON-TRAFFIC +########################################### +library(ggmap); +# Now, for the mapping of the rides; first we get the map +nyc_geocode <- geocode("New York City") +nyc_map13 <- get_map(location=c(nyc_geocode$lon, nyc_geocode$lat), zoom=13) + +trips_2014_June_18_5am <- SparkR::sql("select * from trip_fare where hour(pickup_datetime) = 5 and day(pickup_datetime) = 11 and month(pickup_datetime) = 12 and year(pickup_datetime) = 2013 and trip_distance > 2") +trips_2014_June_18_5amDF <- as.data.frame(trips_2014_June_18_5am) +map1 <- ggmap(nyc_map13) + geom_point(data=trips_2014_June_18_5amDF, aes(pickup_longitude, pickup_latitude), color="darkred", alpha=0.04) + + +trips_2014_June_18_5pm <- SparkR::sql("select * from trip_fare where hour(pickup_datetime) = 17 and day(pickup_datetime) = 11 and month(pickup_datetime) = 12 and year(pickup_datetime) = 2013 and trip_distance > 2") +trips_2014_June_18_5pmDF <- as.data.frame(trips_2014_June_18_5pm) +map2 <- ggmap(nyc_map13) + geom_point(data=trips_2014_June_18_5pmDF, aes(pickup_longitude, pickup_latitude), color="darkred", alpha=0.04) + +grid.arrange(map1, map2, ncol=2) + +endtime <- Sys.time(); +print (endtime-starttime); +``` + +###Net efflux from NY City during AM and PM rush hours +This section examples of advanced SQL, as well as plotting. Nex efflux is shown on NY City maps during morning and evening rush hours. +```{r Advanced SQL and Visualization 2, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=4} +starttime <- Sys.time(); + +########################################### +# PLOT MORNING EFFLUX AT 8 AM +########################################### +morning_efflux <- SparkR::sql("select lon, lat, + sum(flux) as net_flux from + (select round(pickup_longitude, 3) as lon, + round(pickup_latitude, 3) as lat, count(1) flux + from trip_fare + where hour(pickup_datetime) = 8 and + day(pickup_datetime) = 11 and + month(pickup_datetime) = 12 and + year(pickup_datetime) = 2013 + and trip_distance > 2 + group by round(pickup_longitude, 3), round(pickup_latitude, 3) + union all + select round(pickup_longitude, 3) as lon, + round(pickup_latitude, 3) as lat, count(1) * -1 flux + from trip_fare + where hour(dropoff_datetime) = 8 and + day(dropoff_datetime) = 11 and + month(dropoff_datetime) = 12 and year(dropoff_datetime) = 2013 + and trip_distance > 2 + group by round(pickup_longitude, 3), round(pickup_latitude, 3) + ) piecewise_flux + group by lon, lat") +morning_effluxDF <- as.data.frame(morning_efflux) +map1 <- ggmap(nyc_map13) + geom_point(data=morning_effluxDF, aes(lon, lat, color=net_flux), alpha = 0.4, size=0.5) + scale_color_continuous(low="blue", high="red") + + +########################################### +# PLOT EVENIG EFFLUX AT 6 PM +########################################### +evening_efflux <- SparkR::sql("select lon, lat, sum(flux) as net_flux from + (select round(pickup_longitude, 3) as lon, + round(pickup_latitude, 3) as lat, count(1) flux + from trip_fare + where hour(pickup_datetime) = 18 and + day(pickup_datetime) = 11 and + month(pickup_datetime) = 12 and + year(pickup_datetime) = 2013 + and trip_distance > 2 + group by round(pickup_longitude, 3), round(pickup_latitude, 3) + union all + select round(pickup_longitude, 3) as lon, + round(pickup_latitude, 3) as lat, count(1) * -1 flux + from trip_fare + where hour(dropoff_datetime) = 18 and + day(dropoff_datetime) = 11 and + month(dropoff_datetime) = 12 and year(dropoff_datetime) = 2013 + and trip_distance > 2 + group by round(pickup_longitude, 3), round(pickup_latitude, 3) + ) piecewise_flux + group by lon, lat") +evening_efflux <- as.data.frame(evening_efflux) +map2 <- ggmap(nyc_map13) + geom_point(data=evening_efflux, aes(lon, lat, color=net_flux), alpha = 0.4, size=0.5) + scale_color_continuous(low="red", high="darkgreen") + +grid.arrange(map1, map2, ncol=2) + +endtime <- Sys.time(); +print (endtime-starttime); +``` + + +#Modeling with SparkR +##Down-sampling data for modeling +If a data-set is large, it may need to be down-sampled for modeling in reasonable amount of time. Here we used the sample function from SparkR to down-sample the joined trip-fare data. We then save the data in HDFS for use as input into the sparklyr modeling functions. +```{r Downsample data for training, message=FALSE, warning=FALSE, echo=TRUE} +starttime <- Sys.time(); + +########################################### +# SAMPLE DATA FOR MODELING +########################################### +trip_fare_featSampled <- sample(trip_fare_feat, withReplacement=FALSE, + fraction=1, seed=123) +SparkR::cache(trip_fare_featSampled); SparkR::count(trip_fare_featSampled); +createOrReplaceTempView(trip_fare_featSampled, "trip_fare_featSampled") + +endtime <- Sys.time(); +print (endtime-starttime); +``` + +##Partition data into train/test +```{r Partition data, message=FALSE, warning=FALSE, echo=TRUE, fig.width=5, fig.height=4} +starttime <- Sys.time(); + +########################################### +# PARTITION DATA INTO TRAIN-TEST USIN SQL +########################################### +dfrand <- SparkR::sql("SELECT *, RAND() as randnum from trip_fare_featSampled" ); +trainDF <- SparkR::filter(dfrand, dfrand$randnum <= 0.7) +testDF <- SparkR::filter(dfrand, dfrand$randnum > 0.7) +``` + +##Create a SparkR::glm model +Train a glm model. +```{r Glm train, message=FALSE, warning=FALSE, echo=TRUE, fig.width=5, fig.height=4} +starttime <- Sys.time(); + +########################################### +## CREATE GLM MODEL +########################################### +model <- SparkR::spark.glm(tip_amount ~ payment_type + pickup_hour + + fare_amount + passenger_count + trip_distance + + trip_time_in_secs + TrafficTimeBins, + data = trainDF, family = "gaussian", + tol = 1e-05, maxIter = 10) +print (summary(model)); + +endtime <- Sys.time(); +print (endtime-starttime); +``` + +##Evaluate predictions on test set +```{r Evaluate glm model, message=FALSE, warning=FALSE, echo=TRUE, fig.width=5, fig.height=4} +starttime <- Sys.time(); + +########################################### +## PREDICT ON TEST SET, AND EVALUATE ACCURACY +########################################### +predictions <- SparkR::predict(model, newData = testDF) +predfilt <- SparkR::select(predictions, c("label","prediction")) +SparkR::cache(predfilt); SparkR::count(predfilt); + +## SAMPLE PREDICTIONS FOR EVALUATION USINGS R DATAFRAME +predfiltSampled <- sample(predfilt, withReplacement=FALSE, + fraction=0.0001, seed=123) +predfilt_local <- as.data.frame(predfiltSampled) + +# EVALUATE AND PLOT PREDICTIONS (R-sqr) +Rsqr = cor(predfilt_local$label, predfilt_local$prediction)^2; Rsqr; + +# Sample predictions for plotting +predictedSampled <- predfilt_local[base::sample(1:nrow(predfilt_local), 1000),] + +# Plot predicted vs. actual values +lm_model <- lm(prediction ~ label, data = predictedSampled) +ggplot(predictedSampled, aes(label, prediction)) + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=1.5) + geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], intercept = summary(lm_model)$coefficients[1,1]), color = "red") + +endtime <- Sys.time(); +print (endtime-starttime); +``` + + +#Save data & model +##Save model for deployment +Partition data into train/test, and train a glm model and evaluate it. +```{r Persist model, message=FALSE, warning=FALSE, echo=TRUE} +starttime <- Sys.time(); + +########################################### +## SAVE MODEL [REMOVE FILE IF IT EXISTS] +########################################### +if (length(system("hadoop fs -ls /HdiSamples/HdiSamples/NYCTaxi/SparkGlmModel", intern=TRUE))>=1) { + system("hadoop fs -rm -r /HdiSamples/HdiSamples/NYCTaxi/SparkGlmModel") +} +modelPath = file.path(fullDataDir, "SparkGlmModel"); +write.ml(model, modelPath) +``` + +##Save predictions to CSV +```{r Save predictions to csv, message=FALSE, warning=FALSE, echo=TRUE} +starttime <- Sys.time(); + +########################################### +## REPARTITION DATA FOR SAVING, AND WRITE TO CSV +########################################### +predictFile <- file.path(fullDataDir, "SparkRGLMPredictions") +predfiltRepartition <- repartition(predfilt, 10) + +write.df(df=predfiltRepartition, path=predictFile, + source = "com.databricks.spark.csv", + mode = "overwrite") + +endtime <- Sys.time(); +print (endtime-starttime); +``` + +##Save joined and sampled data +Repartition the data in specific number of chunks and save +```{r Save data, message=FALSE, warning=FALSE, echo=TRUE} +starttime <- Sys.time(); + +########################################### +# REPARTITION DATA FOR SAVING, AND WRITE TO PARQUET FILE +########################################### +joinedFilePath <- file.path(fullDataDir, "NYCjoinedParquetSubset") + +trip_fare_featSampledRepartitioned <- repartition(trip_fare_feat, 10) # write.df below will produce this many files +write.df(df=trip_fare_featSampledRepartitioned, + path=joinedFilePath, + source="parquet", mode="overwrite") + + +endtime <- Sys.time(); +print (endtime-starttime); +``` + +#Clear cache and exit +```{r Clear chace and exit Spark, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# UNPERSIST CACHED DATA FRAMES +########################################### +SparkR::unpersist(fareDF) +SparkR::unpersist(trip_fareDF) +SparkR::unpersist(trip_fare_feat) +SparkR::unpersist(trip_fare_featSampled) +SparkR::unpersist(predfilt) + +########################################### +# STOP SPARKR CONTEXT +########################################### +sparkR.stop() +``` + +
+
+
+
+ +#Summary +The examples shown here can be adopted to fit other data exploration and modeling scenarios having different data-types or prediction tasks (e.g. classification). \ No newline at end of file diff --git a/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forHDICLuster.html b/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forHDICLuster.html new file mode 100644 index 00000000..9191b57c --- /dev/null +++ b/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forHDICLuster.html @@ -0,0 +1,868 @@ + + + + + + + + + + + + + + + +Using SparkR with 2013 NYCTaxi Data: Data wrangling, manipulations, modeling, and evaluation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + +
+
+

1 Introduction

+

This Markdown document shows the use of SparkR for data wrangling, manipulation, and creating machine learning models. The data used for this exercise is the public NYC Taxi Trip and Fare data-set (2013, ~45 Gb, ~140 million rows) available from: http://www.andresmh.com/nyctaxitrips. Data for this exercise can be downloaded from the public blob (see below). The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here.

+We use Spark SQL for many of the data wrangling tasks. For plotting and visualization, small amounts of data from Spark dataframes are transformed to the local data frames. +
+


+
+
+

2 Creating spark context and loading packages

+
###########################################
+# LOAD LIBRARIES FROM SPECIFIED PATH
+###########################################
+.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths()))
+library(SparkR)
+library(rmarkdown)
+library(knitr)
+library(gridExtra)
+library(ggplot2)
+library(ggmap)
+
+###########################################
+# CREATE SPARK CONTEXT
+###########################################
+sc <- sparkR.session(
+  sparkPackages = "com.databricks:spark-csv_2.10:1.3.0"
+)
+
## Launching java with spark-submit command /usr/hdp/current/spark2-client/bin/spark-submit  --packages com.databricks:spark-csv_2.10:1.3.0 sparkr-shell /tmp/RtmpO70qVB/backend_portbcbf434f8de0
+
SparkR::setLogLevel("OFF")
+
## NULL
+
###########################################
+## SPECIFY BASE HDFS DIRECTORY
+###########################################
+fullDataDir <- "/HdiSamples/HdiSamples/NYCTaxi"
+
+
+
+

3 Reading in files from HDFS (csv or parquet)

+

Data for this exercise can be downloaded from the public blob locations below:
1. Trip (Csv): http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/combined_taxi_trip_w_header.csv (~19 Gb)
2. Fare (Csv): http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/combined_taxi_fare_w_header.csv (~28 Gb)
The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here. The csv files can be read into Spark context and saved in parquet format. Once saved in parquet format, data can be read in much more quickly than csv files.

+
###########################################
+# TRIP FILE (CSV format, or PARQUET format)
+###########################################
+starttime <- Sys.time();
+
+#tripPath <- file.path(fullDataDir, "combined_taxi_trip_w_header.csv")
+#tripDF <- read.df(tripPath, source = "com.databricks.spark.csv", 
+#                  header = "true", inferSchema = "true")
+#tripPathParquet <- file.path(fullDataDir, "CombinedTaxiTripParquet2013")
+#tripDFRepartitioned <- repartition(tripDF, 20) # write.df below will produce this many files
+#write.parquet(tripDFRepartitioned, tripPathParquet)
+
+tripPathParquet <- file.path(fullDataDir, "CombinedTaxiTripParquet2013")
+tripDF <- read.parquet(tripPathParquet)
+head(tripDF, 3)
+
##                          medallion                     hack_license
+## 1 BF9FD47CB82BFBAF286ED29B6323D2DF D97E9DF6E43B5E8739F737B8A25E44EC
+## 2 9AEA423D30A07A1D376FB8E78C88A5E2 80E4604CCC69FC46772A38098750EC52
+## 3 B7B64C41FDB012BAD7DC47343B57B887 16FCE7EEFC8B15D091CD47CF05087014
+##   vendor_id rate_code store_and_fwd_flag     pickup_datetime
+## 1       CMT         1                  N 2013-01-20 22:15:25
+## 2       CMT         1                  N 2013-01-01 01:19:28
+## 3       CMT         1                  N 2013-01-20 22:54:28
+##      dropoff_datetime passenger_count trip_time_in_secs trip_distance
+## 1 2013-01-20 22:26:56               1               691           2.0
+## 2 2013-01-01 01:44:17               1              1489           5.2
+## 3 2013-01-20 23:03:29               1               541           3.6
+##   pickup_longitude pickup_latitude dropoff_longitude dropoff_latitude
+## 1        -74.00253        40.73426         -73.97876         40.73656
+## 2        -73.92196        40.86759         -73.96371         40.80549
+## 3        -74.01291        40.71695         -73.98824         40.75993
+
printSchema(tripDF)
+
## root
+##  |-- medallion: string (nullable = true)
+##  |-- hack_license: string (nullable = true)
+##  |-- vendor_id: string (nullable = true)
+##  |-- rate_code: integer (nullable = true)
+##  |-- store_and_fwd_flag: string (nullable = true)
+##  |-- pickup_datetime: timestamp (nullable = true)
+##  |-- dropoff_datetime: timestamp (nullable = true)
+##  |-- passenger_count: integer (nullable = true)
+##  |-- trip_time_in_secs: integer (nullable = true)
+##  |-- trip_distance: double (nullable = true)
+##  |-- pickup_longitude: double (nullable = true)
+##  |-- pickup_latitude: double (nullable = true)
+##  |-- dropoff_longitude: double (nullable = true)
+##  |-- dropoff_latitude: double (nullable = true)
+
###########################################
+# FARE FILE (CSV or PARQUET format)
+###########################################
+#farePath <- file.path(fullDataDir, "combined_taxi_fare_w_header.csv")
+#fareDF <- read.df(farePath, source = "com.databricks.spark.csv", 
+#                  header = "true", inferSchema = "true")
+#farePathParquet <- file.path(fullDataDir, "CombinedTaxiFareParquet2013")
+#fareDFRepartitioned <- repartition(fareDF, 20) # write.df below will produce this many files
+#write.parquet(fareDFRepartitioned, farePathParquet)
+
+farePathParquet <- file.path(fullDataDir, "CombinedTaxiFareParquet2013")
+fareDF <- read.parquet(farePathParquet)
+SparkR::cache(fareDF); SparkR::count(fareDF);
+
## SparkDataFrame[medallion:string, hack_license:string, vendor_id:string, pickup_datetime:timestamp, payment_type:string, fare_amount:double, surcharge:double, mta_tax:double, tip_amount:double, tolls_amount:double, total_amount:double]
+
## [1] 173179759
+
head(fareDF, 3)
+
##                          medallion                     hack_license
+## 1 F003B055B21DB1BF03FF509D9843B3B0 E96ACE764A594BEF583DD0D47CB7533D
+## 2 EC31C9229EB30C45371D5D2B834B5FE2 0B494DD625E182E7334E2C8BE0254EE9
+## 3 4E03D87466970A4AE440B414AEFDBC5A 376334E11FA1DCD8859A1B8BC18917FD
+##   vendor_id     pickup_datetime payment_type fare_amount surcharge mta_tax
+## 1       CMT 2013-01-03 06:36:54          CSH         6.5       0.0     0.5
+## 2       CMT 2013-01-02 16:57:51          CSH         5.0       1.0     0.5
+## 3       CMT 2013-01-03 22:34:52          CSH        20.0       0.5     0.5
+##   tip_amount tolls_amount total_amount
+## 1          0            0          7.0
+## 2          0            0          6.5
+## 3          0            0         21.0
+
printSchema(fareDF)
+
## root
+##  |-- medallion: string (nullable = true)
+##  |-- hack_license: string (nullable = true)
+##  |-- vendor_id: string (nullable = true)
+##  |-- pickup_datetime: timestamp (nullable = true)
+##  |-- payment_type: string (nullable = true)
+##  |-- fare_amount: double (nullable = true)
+##  |-- surcharge: double (nullable = true)
+##  |-- mta_tax: double (nullable = true)
+##  |-- tip_amount: double (nullable = true)
+##  |-- tolls_amount: double (nullable = true)
+##  |-- total_amount: double (nullable = true)
+
endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 54.81886 secs
+
+
+

4 Using SparkR for data wrangling & manipulation

+

SparkR is an R package that provides a light-weight frontend to use Apache Spark from R. In Spark 2.0, SparkR provides a distributed data frame implementation that supports operations like selection, filtering, aggregation etc. (similar to R data frames, dplyr) but on large datasets. SparkR also provides support for distributed machine learning using MLlib.

+
+

4.1 Join datasets using SQL

+

You can register dataframes as tables in SQLContext and join using multiple columns. The following SQL also filters the data for some outliers.

+
###########################################
+# 1. REGISTER TABLES AND JOIN ON MULTIPLE COLUMNS, FILTER DATA
+# 2. REGISTER JIONED TABLE
+###########################################
+starttime <- Sys.time();
+
+createOrReplaceTempView(tripDF, "trip")
+createOrReplaceTempView(fareDF, "fare")
+
+trip_fareDF <-  SparkR::sql("SELECT 
+  f.pickup_datetime, hour(f.pickup_datetime) as pickup_hour, 
+  t.dropoff_datetime, hour(t.dropoff_datetime) as dropoff_hour,
+  f.vendor_id, f.fare_amount, f.surcharge, f.tolls_amount, 
+  f.tip_amount, f.payment_type, t.rate_code, 
+  t.passenger_count, t.trip_distance, t.trip_time_in_secs, 
+  t.pickup_longitude, t.pickup_latitude, t.dropoff_longitude, 
+  t.dropoff_latitude
+  FROM trip t, fare f  
+  WHERE t.medallion = f.medallion AND t.hack_license = f.hack_license 
+  AND t.pickup_datetime = f.pickup_datetime 
+  AND t.passenger_count > 0 and t.passenger_count < 8 
+  AND f.tip_amount >= 0 AND f.tip_amount <= 15 
+  AND f.fare_amount >= 1 AND f.fare_amount <= 150 
+  AND f.tip_amount < f.fare_amount AND t.trip_distance > 0 
+  AND t.trip_distance <= 40 AND t.trip_distance >= 1
+  AND t.trip_time_in_secs >= 30 AND t.trip_time_in_secs <= 7200 
+  AND t.rate_code <= 5 AND f.payment_type in ('CSH','CRD')")
+createOrReplaceTempView(trip_fareDF, "trip_fare")
+SparkR::cache(trip_fareDF); SparkR::count(trip_fareDF);
+
## SparkDataFrame[pickup_datetime:timestamp, pickup_hour:int, dropoff_datetime:timestamp, dropoff_hour:int, vendor_id:string, fare_amount:double, surcharge:double, tolls_amount:double, tip_amount:double, payment_type:string, rate_code:int, passenger_count:int, trip_distance:double, trip_time_in_secs:int, pickup_longitude:double, pickup_latitude:double, dropoff_longitude:double, dropoff_latitude:double]
+
## [1] 134717727
+
###########################################
+# WRITE JOINED FILE IN PARQUET FORMAT IN HDFS
+###########################################
+#tripfarePathParquet <- file.path(fullDataDir, "CombinedTaxi_Trip_and_Fare_Parquet2013")
+#tripfareDFRepartitioned <- repartition(trip_fareDF, 20) # write.df below will produce this many files
+#write.parquet(tripfareDFRepartitioned, tripfarePathParquet)
+
+###########################################
+# SHOW REGISTERED TABLES
+###########################################
+head(SparkR::sql("show tables"))
+
##         tableName isTemporary
+## 1 hivesampletable       FALSE
+## 2            fare        TRUE
+## 3            trip        TRUE
+## 4       trip_fare        TRUE
+
###########################################
+# IF THE JOINED FILE IS SAVED, YOU CAN DIRECTLY READ IT IN
+###########################################
+#tripfarePathParquet <- file.path(fullDataDir, "CombinedTaxi_Trip_and_Fare_Parquet2013")
+#trip_fareDF <- read.parquet(tripfarePathParquet)
+#head(trip_fareDF, 3)
+#printSchema(trip_fareDF)
+#createOrReplaceTempView(trip_fareDF, "trip_fare")
+
+endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 1.662769 mins
+
+
+
+

5 Feature engineering using SQL

+

You can create new features using sQL statements. For example, you can use case statements to generate categorical features from coneunuous (numerical) ones.

+
starttime <- Sys.time();
+
+###########################################
+# CREATE FEATURES IN SQL USING CASE STATEMENTS
+###########################################
+trip_fare_feat <- SparkR::sql("SELECT 
+    payment_type, pickup_hour, fare_amount, tip_amount, 
+    passenger_count, trip_distance, trip_time_in_secs, 
+  CASE
+    WHEN (pickup_hour <= 6 OR pickup_hour >= 20) THEN 'Night'
+    WHEN (pickup_hour >= 7 AND pickup_hour <= 10) THEN 'AMRush' 
+    WHEN (pickup_hour >= 11 AND pickup_hour <= 15) THEN 'Afternoon'
+    WHEN (pickup_hour >= 16 AND pickup_hour <= 19) THEN 'PMRush'
+    END as TrafficTimeBins,
+  CASE
+    WHEN (tip_amount > 0) THEN 1 
+    WHEN (tip_amount <= 0) THEN 0 
+    END as tipped
+  FROM trip_fare")
+
+SparkR::cache(trip_fare_feat); SparkR::count(trip_fare_feat);
+
## SparkDataFrame[payment_type:string, pickup_hour:int, fare_amount:double, tip_amount:double, passenger_count:int, trip_distance:double, trip_time_in_secs:int, TrafficTimeBins:string, tipped:int]
+
## [1] 134717727
+
createOrReplaceTempView(trip_fare_feat, "trip_fare_feat")
+head(trip_fare_feat, 3)
+
##   payment_type pickup_hour fare_amount tip_amount passenger_count
+## 1          CSH           8         7.5          0               1
+## 2          CRD          14        10.5          2               1
+## 3          CSH           9         7.0          0               2
+##   trip_distance trip_time_in_secs TrafficTimeBins tipped
+## 1           1.4               401          AMRush      0
+## 2           1.2               957       Afternoon      1
+## 3           1.1               506          AMRush      0
+
endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 11.792 secs
+
+
+
+

6 Data visualization

+
+

6.1 Data exploration and plotting

+

For visualization, a small portion data will have to be sampled and brought into local memory as a data.frame object. R’s plotting functions (e.g. from those in ggplot package) can then be applied to the data.frame for visualization.

+
starttime <- Sys.time();
+
+###########################################
+# SAMPLE SMALL PORTION OF DATA
+###########################################
+trip_fare_featSampled <- SparkR::sample(trip_fare_feat, withReplacement=FALSE, 
+                                fraction=0.00001, seed=123)
+
+###########################################
+# CONVERT SPARK DF TO LOCAL DATA.FRAME IN MEMORY OF R-SERVER EDGE NODE
+###########################################
+trip_fare_featSampledDF <- as.data.frame(trip_fare_featSampled);
+
+###########################################
+# Generate HISTOGRAM OF TIP AMOUNT
+###########################################
+hist <- ggplot(trip_fare_featSampledDF, aes(x=tip_amount)) + 
+  geom_histogram(binwidth = 0.5, aes(fill = ..count..)) + 
+  scale_fill_gradient("Count", low = "green", high = "red") + 
+  labs(title="Histogram for Tip Amount");
+
+###########################################
+# Generate Scatter Plot OF TRIP DISTANCE vs. TIP AMOUNT
+###########################################
+scatter <- ggplot(trip_fare_featSampledDF, aes(tip_amount, trip_distance)) + 
+  geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + 
+  labs(title="Tip amount vs. trip distance");
+
+###########################################
+# Plot Histogram and Scatter Plot OF TIP AMOUNT Side by Side
+###########################################
+grid.arrange(hist, scatter, ncol=2)
+

+
endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 2.555724 secs
+
+
+

6.2 Advanced SQL summarization and plotting:

+
+

6.2.1 Trips in NYC area during rush and non-rush hours

+

This section shows more examples of SQL and advanced plotting and visualization, using ggmap. We plot the number of trips by day of the month, as well as number of trips on the NY City map during rush and non-rush hours.

+
starttime <- Sys.time();
+
+###########################################
+# GROUP TRIPS BY YEAR, MONTH, DAY, PAYMENT TYPE
+###########################################
+trip_stats_by_day <- SparkR::sql("select 
+      year(pickup_datetime) as year, month(pickup_datetime) as month, 
+      day(pickup_datetime) as day, payment_type, count(1) as trips 
+      from fare 
+      where payment_type in ('CSH','CRD')
+      group by year(pickup_datetime), month(pickup_datetime), 
+      day(pickup_datetime), payment_type")
+tsbd <- as.data.frame(trip_stats_by_day)
+
+###########################################
+# PLOT NUMBER OF TRIPS BY DAY IN DEC 2013
+###########################################
+ggplot(data=tsbd, aes(day, trips)) + geom_point(aes(color=payment_type)) + geom_smooth(aes(color=payment_type))
+

+
###########################################
+# PLOT TAXI PICKUPS BY HOUR TRAFFIC AND NON-TRAFFIC
+###########################################
+library(ggmap);
+# Now, for the mapping of the rides; first we get the map
+nyc_geocode <- geocode("New York City")
+nyc_map13 <- get_map(location=c(nyc_geocode$lon, nyc_geocode$lat), zoom=13)
+
+trips_2014_June_18_5am <- SparkR::sql("select * from trip_fare where hour(pickup_datetime) = 5 and day(pickup_datetime) = 11 and month(pickup_datetime) = 12 and year(pickup_datetime) = 2013 and trip_distance > 2")
+trips_2014_June_18_5amDF <- as.data.frame(trips_2014_June_18_5am)
+map1 <- ggmap(nyc_map13) + geom_point(data=trips_2014_June_18_5amDF, aes(pickup_longitude, pickup_latitude), color="darkred", alpha=0.04)
+
+
+trips_2014_June_18_5pm <- SparkR::sql("select * from trip_fare where hour(pickup_datetime) = 17 and day(pickup_datetime) = 11 and month(pickup_datetime) = 12 and year(pickup_datetime) = 2013 and trip_distance > 2")
+trips_2014_June_18_5pmDF <- as.data.frame(trips_2014_June_18_5pm)
+map2 <- ggmap(nyc_map13) + geom_point(data=trips_2014_June_18_5pmDF, aes(pickup_longitude, pickup_latitude), color="darkred", alpha=0.04)
+
+grid.arrange(map1, map2, ncol=2)
+

+
endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 41.96943 secs
+
+
+

6.2.2 Net efflux from NY City during AM and PM rush hours

+

This section examples of advanced SQL, as well as plotting. Nex efflux is shown on NY City maps during morning and evening rush hours.

+
starttime <- Sys.time();
+
+###########################################
+# PLOT MORNING EFFLUX AT 8 AM
+###########################################
+morning_efflux <- SparkR::sql("select lon, lat, 
+      sum(flux) as net_flux from
+      (select round(pickup_longitude, 3) as lon, 
+      round(pickup_latitude, 3) as lat, count(1) flux
+      from trip_fare
+      where hour(pickup_datetime) = 8 and 
+      day(pickup_datetime) = 11 and 
+      month(pickup_datetime) = 12 and 
+      year(pickup_datetime) = 2013
+      and trip_distance > 2
+      group by round(pickup_longitude, 3), round(pickup_latitude, 3)
+      union all 
+      select round(pickup_longitude, 3) as lon, 
+      round(pickup_latitude, 3) as lat, count(1) * -1 flux
+      from trip_fare
+      where hour(dropoff_datetime) = 8 and 
+      day(dropoff_datetime) = 11 and 
+      month(dropoff_datetime) = 12 and year(dropoff_datetime) = 2013
+      and trip_distance > 2
+      group by round(pickup_longitude, 3), round(pickup_latitude, 3)
+      ) piecewise_flux
+      group by lon, lat")
+morning_effluxDF <- as.data.frame(morning_efflux)
+map1 <- ggmap(nyc_map13) + geom_point(data=morning_effluxDF, aes(lon, lat, color=net_flux), alpha = 0.4, size=0.5) + scale_color_continuous(low="blue", high="red")
+
+
+###########################################
+# PLOT EVENIG EFFLUX AT 6 PM
+###########################################
+evening_efflux <- SparkR::sql("select lon, lat, sum(flux) as net_flux from
+      (select round(pickup_longitude, 3) as lon, 
+      round(pickup_latitude, 3) as lat, count(1) flux
+      from trip_fare
+      where hour(pickup_datetime) = 18 and 
+      day(pickup_datetime) = 11 and 
+      month(pickup_datetime) = 12 and 
+      year(pickup_datetime) = 2013 
+      and trip_distance > 2
+      group by round(pickup_longitude, 3), round(pickup_latitude, 3)
+      union all 
+      select round(pickup_longitude, 3) as lon, 
+      round(pickup_latitude, 3) as lat, count(1) * -1 flux
+      from trip_fare
+      where hour(dropoff_datetime) = 18 and 
+      day(dropoff_datetime) = 11 and 
+      month(dropoff_datetime) = 12 and year(dropoff_datetime) = 2013
+      and trip_distance > 2
+      group by round(pickup_longitude, 3), round(pickup_latitude, 3)
+      ) piecewise_flux
+      group by lon, lat")
+evening_efflux <- as.data.frame(evening_efflux)
+map2 <- ggmap(nyc_map13) + geom_point(data=evening_efflux, aes(lon, lat, color=net_flux), alpha = 0.4, size=0.5) + scale_color_continuous(low="red", high="darkgreen")
+
+grid.arrange(map1, map2, ncol=2)
+

+
endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 8.644903 secs
+
+
+
+
+

7 Modeling with SparkR

+
+

7.1 Down-sampling data for modeling

+

If a data-set is large, it may need to be down-sampled for modeling in reasonable amount of time. Here we used the sample function from SparkR to down-sample the joined trip-fare data. We then save the data in HDFS for use as input into the sparklyr modeling functions.

+
starttime <- Sys.time();
+
+###########################################
+# SAMPLE DATA FOR MODELING
+###########################################
+trip_fare_featSampled <- sample(trip_fare_feat, withReplacement=FALSE, 
+                                fraction=1, seed=123)
+SparkR::cache(trip_fare_featSampled); SparkR::count(trip_fare_featSampled);
+
## SparkDataFrame[payment_type:string, pickup_hour:int, fare_amount:double, tip_amount:double, passenger_count:int, trip_distance:double, trip_time_in_secs:int, TrafficTimeBins:string, tipped:int]
+
## [1] 134717727
+
createOrReplaceTempView(trip_fare_featSampled, "trip_fare_featSampled")
+
+endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 10.95969 secs
+
+
+

7.2 Partition data into train/test

+
starttime <- Sys.time();
+
+###########################################
+# PARTITION DATA INTO TRAIN-TEST USIN SQL
+###########################################
+dfrand <- SparkR::sql("SELECT *, RAND() as randnum from trip_fare_featSampled" );
+trainDF <- SparkR::filter(dfrand, dfrand$randnum <= 0.7)
+testDF <- SparkR::filter(dfrand, dfrand$randnum > 0.7)
+
+
+

7.3 Create a SparkR::glm model

+

Train a glm model.

+
starttime <- Sys.time();
+
+########################################### 
+## CREATE GLM MODEL
+###########################################
+model <- SparkR::spark.glm(tip_amount ~ payment_type + pickup_hour + 
+                    fare_amount + passenger_count + trip_distance + 
+                    trip_time_in_secs + TrafficTimeBins, 
+                    data = trainDF, family = "gaussian", 
+                    tol = 1e-05, maxIter = 10)
+print (summary(model));
+
## 
+## Deviance Residuals: 
+## (Note: These are approximate quantiles with relative error <= 0.01)
+##      Min        1Q    Median        3Q       Max  
+## -15.5785   -0.4975    0.0801    0.5400   12.4203  
+## 
+## Coefficients:
+##                            Estimate   Std. Error  t value  Pr(>|t|)
+## (Intercept)                -1.3068    0.00049975  -2614.9  0       
+## payment_type_CRD           2.6036     0.00026795  9716.6   0       
+## pickup_hour                0.0038033  2.1923e-05  173.48   0       
+## fare_amount                0.074737   5.7425e-05  1301.5   0       
+## passenger_count            -0.010062  9.593e-05   -104.89  0       
+## trip_distance              0.073362   0.00012939  567      0       
+## trip_time_in_secs          2.028e-05  4.8496e-07  41.819   0       
+## TrafficTimeBins_Night      -0.011814  0.00040895  -28.888  0       
+## TrafficTimeBins_Afternoon  0.030689   0.00043639  70.326   0       
+## TrafficTimeBins_PMRush     0.0403     0.00047819  84.275   0       
+## 
+## (Dispersion parameter for gaussian family taken to be 1.649992)
+## 
+##     Null deviance: 415824234  on 94298045  degrees of freedom
+## Residual deviance: 155590991  on 94298036  degrees of freedom
+## AIC: 314827939
+## 
+## Number of Fisher Scoring iterations: 1
+
endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 2.038898 mins
+
+
+

7.4 Evaluate predictions on test set

+
starttime <- Sys.time();
+
+########################################### 
+## PREDICT ON TEST SET, AND EVALUATE ACCURACY
+###########################################
+predictions <- SparkR::predict(model, newData = testDF)
+predfilt <- SparkR::select(predictions, c("label","prediction"))
+SparkR::cache(predfilt); SparkR::count(predfilt);
+
## SparkDataFrame[label:double, prediction:double]
+
## [1] 40419681
+
## SAMPLE PREDICTIONS FOR EVALUATION USINGS R DATAFRAME
+predfiltSampled <- sample(predfilt, withReplacement=FALSE, 
+                                fraction=0.0001, seed=123)
+predfilt_local <- as.data.frame(predfiltSampled)
+
+# EVALUATE AND PLOT PREDICTIONS (R-sqr)
+Rsqr = cor(predfilt_local$label, predfilt_local$prediction)^2; Rsqr;
+
## [1] 0.626165
+
# Sample predictions for plotting
+predictedSampled <- predfilt_local[base::sample(1:nrow(predfilt_local), 1000),]
+
+# Plot predicted vs. actual values
+lm_model <- lm(prediction ~ label, data = predictedSampled)
+ggplot(predictedSampled, aes(label, prediction)) + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=1.5)   + geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], intercept = summary(lm_model)$coefficients[1,1]), color = "red")
+

+
endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 8.006647 secs
+
+
+
+

8 Save data & model

+
+

8.1 Save model for deployment

+

Partition data into train/test, and train a glm model and evaluate it.

+
starttime <- Sys.time();
+
+###########################################
+## SAVE MODEL [REMOVE FILE IF IT EXISTS]
+###########################################
+if (length(system("hadoop fs -ls /HdiSamples/HdiSamples/NYCTaxi/SparkGlmModel", intern=TRUE))>=1) {
+  system("hadoop fs -rm -r /HdiSamples/HdiSamples/NYCTaxi/SparkGlmModel")
+}
+modelPath =  file.path(fullDataDir, "SparkGlmModel");
+write.ml(model, modelPath) 
+
+
+

8.2 Save predictions to CSV

+
starttime <- Sys.time();
+
+###########################################
+## REPARTITION DATA FOR SAVING, AND WRITE TO CSV
+###########################################
+predictFile <- file.path(fullDataDir, "SparkRGLMPredictions")
+predfiltRepartition <- repartition(predfilt, 10)
+
+write.df(df=predfiltRepartition, path=predictFile, 
+        source = "com.databricks.spark.csv", 
+        mode = "overwrite")
+
+endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 9.598252 secs
+
+
+

8.3 Save joined and sampled data

+

Repartition the data in specific number of chunks and save

+
starttime <- Sys.time();
+
+###########################################
+# REPARTITION DATA FOR SAVING, AND WRITE TO PARQUET FILE
+###########################################
+joinedFilePath <- file.path(fullDataDir, "NYCjoinedParquetSubset")
+
+trip_fare_featSampledRepartitioned <- repartition(trip_fare_feat, 10) # write.df below will produce this many files
+write.df(df=trip_fare_featSampledRepartitioned, 
+         path=joinedFilePath, 
+         source="parquet", mode="overwrite")
+
+
+endtime <- Sys.time();
+print (endtime-starttime);
+
## Time difference of 37.85903 secs
+
+
+
+

9 Clear cache and exit

+
###########################################
+# UNPERSIST CACHED DATA FRAMES
+###########################################
+SparkR::unpersist(fareDF)
+
## SparkDataFrame[medallion:string, hack_license:string, vendor_id:string, pickup_datetime:timestamp, payment_type:string, fare_amount:double, surcharge:double, mta_tax:double, tip_amount:double, tolls_amount:double, total_amount:double]
+
SparkR::unpersist(trip_fareDF)
+
## SparkDataFrame[pickup_datetime:timestamp, pickup_hour:int, dropoff_datetime:timestamp, dropoff_hour:int, vendor_id:string, fare_amount:double, surcharge:double, tolls_amount:double, tip_amount:double, payment_type:string, rate_code:int, passenger_count:int, trip_distance:double, trip_time_in_secs:int, pickup_longitude:double, pickup_latitude:double, dropoff_longitude:double, dropoff_latitude:double]
+
SparkR::unpersist(trip_fare_feat)
+
## SparkDataFrame[payment_type:string, pickup_hour:int, fare_amount:double, tip_amount:double, passenger_count:int, trip_distance:double, trip_time_in_secs:int, TrafficTimeBins:string, tipped:int]
+
SparkR::unpersist(trip_fare_featSampled)
+
## SparkDataFrame[payment_type:string, pickup_hour:int, fare_amount:double, tip_amount:double, passenger_count:int, trip_distance:double, trip_time_in_secs:int, TrafficTimeBins:string, tipped:int]
+
SparkR::unpersist(predfilt)
+
## SparkDataFrame[label:double, prediction:double]
+
###########################################
+# STOP SPARKR CONTEXT
+###########################################
+sparkR.stop()
+
+
+
+


+
+
+

10 Summary

+

The examples shown here can be adopted to fit other data exploration and modeling scenarios having different data-types or prediction tasks (e.g. classification).

+
+ + + +
+
+ +
+ + + + + + + + diff --git a/Misc/StrataSanJose2017/Code/UseCaseHTS/Readme.md b/Misc/StrataSanJose2017/Code/UseCaseHTS/Readme.md new file mode 100644 index 00000000..149a1e04 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/UseCaseHTS/Readme.md @@ -0,0 +1 @@ +This folder contains a sample script for the Tutorial Use Case: _Parallel models: training many parallel models for hierarchical time series optimization_ diff --git a/Misc/StrataSanJose2017/Code/UseCaseHTS/aust_hierarchy.png b/Misc/StrataSanJose2017/Code/UseCaseHTS/aust_hierarchy.png new file mode 100644 index 00000000..3b6fb45e Binary files /dev/null and b/Misc/StrataSanJose2017/Code/UseCaseHTS/aust_hierarchy.png differ diff --git a/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.R b/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.R new file mode 100644 index 00000000..2d22b328 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.R @@ -0,0 +1,289 @@ +# ## Hierarchical time series +# +# Time series data can often be disaggregated by attributes of interest to form groups of time series or a hierarchy. For example, one might be interested in forecasting demand of all products in total, by location, by product category, by customer, etc. (see picture below). Forecasting hierarchical time series data is challenging because the generated forecasts need to satisfy the aggregate requirement, that is, lower-level forecasts need to sum up to the higher-level forecasts. There are many approaches that solve this problem, differing in the way they aggregate individual time series forecasts across the groups or the hierarchy: bottom-up, top-down, or middle-out. +# +# Training hierarchical time series forecasting models require searching through a large parameter space. The model's performance greatly varies over the parameters we choose, some of which are: +# +# * univariate time series method for individual series prediction +# * method for reconciling forecasts across hierarchy +# * weights we use to reconcile forecasts across hierarchy +# * etc. +# +# +# In this tutorial, we will use Australian tourism data set from 'fpp' package as a sample data set. +# +# Rob J Hyndman (2013). fpp: Data for "Forecasting: principles and practice". R package version 0.5. +# https://CRAN.R-project.org/package=fpp +# +# This data set contains quarterly visitor nights spent by international tourists to Australia available for years 1999-2010. We will use this historical data to forecast nights spent by tourists in Australia: +# +# * In total +# * By state +# * By city +# +# This data can be represented as a hierarchy, as shown in the following picture: +# +# ![Australia tourism data set](./aust_hierarchy.png) +# +# ## Outline +# In this tutorial we will: +# +# 1. create a hierarchical time series +# 2. split the data into training and testing +# 3. generate a grid of input parameters for forecasting function +# 4. call rxExec() to run forecasting over the parameter space distributed +# accross local cores or cluster nodes +# 5. find optimal parameters based on all runs +# 6. forecast the next two years using the optimal parameters +# 7. try running the above for larger data size +# +# +# ## HTS Forecasting +# +# ### Loading necessary libraries + +# Setting the environment +if(Sys.getenv("SPARK_HOME")==""){ + Sys.setenv(SPARK_HOME="/dsvm/tools/spark/current") +} + +Sys.setenv(YARN_CONF_DIR="/opt/hadoop/current/etc/hadoop", + JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64") +Sys.setenv(PATH="/anaconda/envs/py35/bin:/dsvm/tools/cntk/cntk/bin:/usr/local/mpi/bin:/dsvm/tools/spark/current/bin:/anaconda/envs/py35/bin:/dsvm/tools/cntk/cntk/bin:/usr/local/mpi/bin:/dsvm/tools/spark/current/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/opt/hadoop/current/sbin:/opt/hadoop/current/bin:/home/remoteuser/.local/bin:/home/remoteuser/bin:/opt/hadoop/current/sbin:/opt/hadoop/current/bin") + +# Loading libraries +if(!require("hts")) install.packages('hts') +if(!require("fpp")) install.packages('fpp') + +library(hts) +library(fpp) + +### Forecasting function +# +# We will use the following function for time series forecasting. The function runs hierarchical forecast on training data set +# and returns evaluation metrics on test data set + + +forecast_hts <- function (traindata, testdata, htsmethod, tsmethod, combweights){ + + # Forecasting horizon + horiz = dim(aggts(testdata))[1] + + # Run hierarchical forecast + hts_fcast <- forecast(object = traindata, + h = horiz, + method = htsmethod, + fmethod = tsmethod, + weights = combweights) + + # Return evaluation metrics at the top level + fcast_acc <- accuracy.gts(hts_fcast, test = testdata, levels = 0) + +} + + +### Hierarchical time series data set +# +# Let's process the data + +# Create hierachical time series dataset +htsdata <- hts(vn, nodes=list(4,c(2,2,2,2))) + +# Rename the nodes of the hierarchy +htsdata$labels$`Level 1` <- c("NSW", "VIC", "QLD", "Other") +htsdata$labels$`Level 2` <- c("Sydney", "NSW-Other", "Melbourne", "VIC-Other", "BrisbaneGC", "QLD-Other", "Capitals", "Other") +names(htsdata$labels) <- c("Total", "State", "City") + +# Let's look at the data +htsdata + +# Split data into train and test (leave out years 2010 and 2011 for testing) +train_data <- window(htsdata, end = c(2009, 4)) +test_data <- window(htsdata, start = c(2010, 1)) + + +### Visualizing hierarchy +# +# Let's see what the hierarchical time series data looks like. + +# Plot the hierarchial time series data +plot(htsdata) + + +### Parameter space +# +# Let's generate the parameter space + +# Vary methods for generating base time series forecasts +ts_method <- c("ets", "arima", "rw") + +# Vary methods for reconciling base forecasts to satisfy aggregation requirement +hts_method <- c("bu", "comb", "tdgsa", "tdgsf", "tdfp") + +# Vary forecast weights for the optimal cobination approach +comb_weights <- c("mint", "wls", "ols", "nseries") + +# Generate all possible combinations of the above parameters +param_space <- expand.grid(hts_method, ts_method, comb_weights, stringsAsFactors = FALSE) +colnames(param_space) <- c("hts_method", "ts_method", "comb_weights") + + +# Remove ilegal combinations +# - comb_weights only applies to hts_method == "comb" +rm_inds <- param_space$hts_method != "comb" +param_space$comb_weights[rm_inds] <- "none" +param_space <- param_space[!duplicated.data.frame(param_space),] + + +### Compute contexts +# +# Using Microsoft R Server's _rxSetComputeContext()_ function we can easily switch between different compute platforms, +# and run the same piece of code on those different platforms. _rxExec()_ function then distributes the execution of +# the forecasting function we defined above onto the specified compute context. +# +# * rxSetComputeContext("local") - sets compute context to "local" and causes rxExec() +# to execute runs locally in a serial manner. +# +# * rxSetComputeContext(RxLocalParallel()) - causes rxExec() to run multiple tasks +# in parallel, thereby using the multiple cores on your local machine. +# The downside is that this will use more memory and will slow down your computer +# for other work you may be trying to do at the same time. +# +# * rxSetComputeContext(RxSpark()) - for parallelized distributed execution via Spark +# across the nodes of a cluster + +rxSetComputeContext(RxLocalParallel()) +# rxSetComputeContext(RxSpark(consoleOutput=TRUE, numExecutors = 1, executorCores=2, executorMem="1g")) + +# Measure execution time +et <- system.time( + + # Run many distributed jobs + rxResult <- rxExec(FUN = forecast_hts, + traindata = train_data, + testdata = test_data, + htsmethod = rxElemArg(param_space$hts_method), + tsmethod = rxElemArg(param_space$ts_method), + combweights = rxElemArg(param_space$comb_weights), + consoleOutput = TRUE, + packagesToLoad = c('hts')) +) + +cat(paste("Elapsed time: ", format(et['elapsed'], digits = 4), "seconds. \n")) + + +### Gather the results +# Collect the results of the rxExec(), and find the result with the best evaluation metric (smallest MAPE) + +all_mape <- sapply( rxResult, function(x) x["MAPE",] ) +min_mape_indx <- which.min(all_mape) + +# Optimal parameters +opt_params <- param_space[min_mape_indx,] + +# Forecast the next 8 quarters using optimal parameters +horiz <- 8 +hts_fcast <- forecast(object = htsdata, + h = horiz, + method = opt_params$hts_method, + fmethod = opt_params$ts_method) + + +### Print out the optimal results + +output <- paste("OPTIMAL RESULTS \n\n", + "Minimum obtained MAPE: ", format(min(all_mape), digits = 4), "\n", + "Optimal method for distributing forecasts within the hierarchy: ", opt_params$hts_method, "\n", + "Optimal forecasting method: ", opt_params$ts_method, "\n") +cat(output) + +if(opt_params$hts_method == "comb") cat(paste("Optimal weights used for `comb` method: ", opt_params$comb_weights, "\n")) + +cat("\n Forecast for the next two years at the City level obtained using optimal parameters: \n\n") + +print(aggts(hts_fcast, levels = 2)) + +# Plot the forecasted time series +names(hts_fcast$labels) <- c("Total", "State", "City") +plot(hts_fcast) + +## Hands-on exercise +# +# In this part, we will try to run the above parameter sweep on a larger data set. Forecasting hierarchical time series takes more time for deeper and wider hierarchies, which are very common in real life applications. For example, if we were to forecast company sales by state, city, store, product category, and product, we would end up with hundreds of thousands of time series. Forecasting this time series hierarchy for just one parameter set may take hours. Doing a parameter sweep in such a scenario would be prohibitive. Being able to distribute that computation to a Spark cluster (by switching to _RxSpark()_ compute context) with hundreds of cores reduces that time drastically. +# +# Here, we will generate a larger time series data set. We will do that by replicating the existing data set, and adding a little bit of noise to it. To increase the data set we will multiply the number of time series with a factor _x_. + +### Generating larger data + + +# Increase the number of time series by factor x +# TRY changing this variable +x = 2 + +# Function to add noise to a data set +addNoise <- function(data) { + + data_dim <- dim(data)[1] * dim(data)[2] + noise <- matrix(runif(data_dim, -500, 500), dim(data)[1]) + noisified <- data + noise + return(noisified) + +} + +# Replicate time series x times +larger_data <- coredata(vn)[ , rep(seq(ncol(vn)), x)] +larger_data <- addNoise(larger_data) + +# Create time series object +vnx <- ts(larger_data, frequency = 4, start = c(1998, 1)) + +# Create hierachical time series dataset +htsdata <- hts(vnx, nodes=list(4*x, rep(c(2,2,2,2), x))) + +# Let's see what our data looks like +print(htsdata) + +# Rename the nodes of the hierarchy +htsdata$labels$`Level 1` <- paste0('State_', 1:length(htsdata$labels$`Level 1`)) +htsdata$labels$`Level 2` <- paste0('City_', 1:length(htsdata$labels$`Level 2`)) +names(htsdata$labels) <- c("Total", "State", "City") + +# Split data into train and test (leave out years 2010 and 2011 for testing) +train_data <- window(htsdata, end = c(2009, 4)) +test_data <- window(htsdata, start = c(2010, 1)) + + +# TRY changing the compute context and see how it affects the execution time +rxSetComputeContext(RxLocalParallel()) +# rxSetComputeContext(RxSpark(consoleOutput=TRUE, numExecutors = 1, executorCores=2, executorMem="1g")) + +# Measure execution time +et <- system.time( + + # Run many distributed jobs + rxResult <- rxExec(FUN = forecast_hts, + traindata = train_data, + testdata = test_data, + htsmethod = rxElemArg(param_space$hts_method), + tsmethod = rxElemArg(param_space$ts_method), + combweights = rxElemArg(param_space$comb_weights), + consoleOutput = TRUE, + packagesToLoad = c('hts')) +) + +cat(paste("Elapsed time: ", format(et['elapsed'], digits = 4), "seconds. \n")) + +all_mape <- sapply( rxResult, function(x) x["MAPE",] ) +min_mape_indx <- which.min(all_mape) + +# Optimal parameters +opt_params <- param_space[min_mape_indx,] + +# Forecast the next 8 quarters using optimal parameters +horiz <- 8 +hts_fcast <- forecast(object = htsdata, + h = horiz, + method = opt_params$hts_method, + fmethod = opt_params$ts_method) + +plot(hts_fcast) diff --git a/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.Rmd b/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.Rmd new file mode 100644 index 00000000..18e4e76e --- /dev/null +++ b/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.Rmd @@ -0,0 +1,329 @@ +--- +title: "Parallel models: training many parallel models for hierarchical time series optimization" +author: "Vanja Paunic" +date: "`r format(Sys.time(), '%d %B, %Y')`" +output: + html_document: + fig_caption: yes + fig_height: 4 + fig_width: 5 + highlight: haddock + keep_md: yes + number_sections: yes + theme: journal + toc: yes + toc_float: yes +runtime: knit +--- + +## Hierarchical time series + +Time series data can often be disaggregated by attributes of interest to form groups of time series or a hierarchy. For example, one might be interested in forecasting demand of all products in total, by location, by product category, by customer, etc. (see picture below). Forecasting hierarchical time series data is challenging because the generated forecasts need to satisfy the aggregate requirement, that is, lower-level forecasts need to sum up to the higher-level forecasts. There are many approaches that solve this problem, differing in the way they aggregate individual time series forecasts across the groups or the hierarchy: bottom-up, top-down, or middle-out. + +Training hierarchical time series forecasting models require searching through a large parameter space. The model's performance greatly varies over the parameters we choose, some of which are: + +* univariate time series method for individual series prediction +* method for reconciling forecasts across hierarchy +* weights we use to reconcile forecasts across hierarchy +* etc. + + +In this tutorial, we will use Australian tourism data set from 'fpp' package as a sample data set. + + Rob J Hyndman (2013). fpp: Data for "Forecasting: principles and practice". R package version 0.5. + https://CRAN.R-project.org/package=fpp + +This data set contains quarterly visitor nights spent by international tourists to Australia available for years 1999-2010. We will use this historical data to forecast nights spent by tourists in Australia: + +* In total +* By state +* By city + +This data can be represented as a hierarchy, as shown in the following picture: + +![Australia tourism data set](./aust_hierarchy.png) + +## Outline +In this tutorial we will: + +1. create a hierarchical time series +2. split the data into training and testing +3. generate a grid of input parameters for forecasting function +4. call rxExec() to run forecasting over the parameter space distributed + accross local cores or cluster nodes +5. find optimal parameters based on all runs +6. forecast the next two years using the optimal parameters +7. try running the above for larger data size + + +## HTS Forecasting + +### Loading necessary libraries +```{r} + +# Setting the environment +if(Sys.getenv("SPARK_HOME")==""){ + Sys.setenv(SPARK_HOME="/dsvm/tools/spark/current") +} + +Sys.setenv(YARN_CONF_DIR="/opt/hadoop/current/etc/hadoop", + JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64") +Sys.setenv(PATH="/anaconda/envs/py35/bin:/dsvm/tools/cntk/cntk/bin:/usr/local/mpi/bin:/dsvm/tools/spark/current/bin:/anaconda/envs/py35/bin:/dsvm/tools/cntk/cntk/bin:/usr/local/mpi/bin:/dsvm/tools/spark/current/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/opt/hadoop/current/sbin:/opt/hadoop/current/bin:/home/remoteuser/.local/bin:/home/remoteuser/bin:/opt/hadoop/current/sbin:/opt/hadoop/current/bin") + +``` + + +```{r Installing libraries, message = FALSE} +if(!require("hts")) install.packages('hts') +if(!require("fpp")) install.packages('fpp') + +library(hts) +library(fpp) +``` + +### Forecasting function + +We will use the following function for time series forecasting. The function runs hierarchical forecast on training data set +and returns evaluation metrics on test data set + + +```{r Forecasting function} +forecast_hts <- function (traindata, testdata, htsmethod, tsmethod, combweights){ + + # Forecasting horizon + horiz = dim(aggts(testdata))[1] + + # Run hierarchical forecast + hts_fcast <- forecast(object = traindata, + h = horiz, + method = htsmethod, + fmethod = tsmethod, + weights = combweights) + + # Return evaluation metrics at the top level + fcast_acc <- accuracy.gts(hts_fcast, test = testdata, levels = 0) + +} +``` + +### Hierarchical time series data set + +Let's process the data + +```{r Processing the data} +# Create hierachical time series dataset +htsdata <- hts(vn, nodes=list(4,c(2,2,2,2))) + + +# Rename the nodes of the hierarchy +htsdata$labels$`Level 1` <- c("NSW", "VIC", "QLD", "Other") +htsdata$labels$`Level 2` <- c("Sydney", "NSW-Other", "Melbourne", "VIC-Other", "BrisbaneGC", "QLD-Other", "Capitals", "Other") +names(htsdata$labels) <- c("Total", "State", "City") + +# Let's look at the data +htsdata + +# Split data into train and test (leave out years 2010 and 2011 for testing) +train_data <- window(htsdata, end = c(2009, 4)) +test_data <- window(htsdata, start = c(2010, 1)) + +``` + +### Visualizing hierarchy + +Let's see what the hierarchical time series data looks like. + +```{r Visualizing hts} +# Plot the hierarchial time series data +plot(htsdata) +``` + +### Parameter space + +Let's generate the parameter space + +```{r Generating parameter space} +# Vary methods for generating base time series forecasts +ts_method <- c("ets", "arima", "rw") + +# Vary methods for reconciling base forecasts to satisfy aggregation requirement +hts_method <- c("bu", "comb", "tdgsa", "tdgsf", "tdfp") + +# Vary forecast weights for the optimal cobination approach +comb_weights <- c("mint", "wls", "ols", "nseries") + +# Generate all possible combinations of the above parameters +param_space <- expand.grid(hts_method, ts_method, comb_weights, stringsAsFactors = FALSE) +colnames(param_space) <- c("hts_method", "ts_method", "comb_weights") + + +# Remove ilegal combinations + +# - comb_weights only applies to hts_method == "comb" +rm_inds <- param_space$hts_method != "comb" +param_space$comb_weights[rm_inds] <- "none" +param_space <- param_space[!duplicated.data.frame(param_space),] + +``` + +### Compute contexts + +Using Microsoft R Server's _rxSetComputeContext()_ function we can easily switch between different compute platforms, +and run the same piece of code on those different platforms. _rxExec()_ function then distributes the execution of +the forecasting function we defined above onto the specified compute context. + +* rxSetComputeContext("local") - sets compute context to "local" and causes rxExec() +to execute runs locally in a serial manner. + +* rxSetComputeContext(RxLocalParallel()) - causes rxExec() to run multiple tasks +in parallel, thereby using the multiple cores on your local machine. +The downside is that this will use more memory and will slow down your computer +for other work you may be trying to do at the same time. + +* rxSetComputeContext(RxSpark()) - for parallelized distributed execution via Spark +across the nodes of a cluster + +```{r Running distributed computation} +rxSetComputeContext(RxLocalParallel()) +# rxSetComputeContext(RxSpark(consoleOutput=TRUE, numExecutors = 1, executorCores=2, executorMem="1g")) + +# Measure execution time +et <- system.time( + +# Run many distributed jobs +rxResult <- rxExec(FUN = forecast_hts, + traindata = train_data, + testdata = test_data, + htsmethod = rxElemArg(param_space$hts_method), + tsmethod = rxElemArg(param_space$ts_method), + combweights = rxElemArg(param_space$comb_weights), + consoleOutput = TRUE, + packagesToLoad = c('hts')) +) + +cat(paste("Elapsed time: ", format(et['elapsed'], digits = 4), "seconds. \n")) + +``` + + +### Gather the results +Collect the results of the rxExec(), and find the result with the best evaluation metric (smallest MAPE) + +```{r Getting optimal parameters} +all_mape <- sapply( rxResult, function(x) x["MAPE",] ) +min_mape_indx <- which.min(all_mape) + +# Optimal parameters +opt_params <- param_space[min_mape_indx,] + +# Forecast the next 8 quarters using optimal parameters +horiz <- 8 +hts_fcast <- forecast(object = htsdata, + h = horiz, + method = opt_params$hts_method, + fmethod = opt_params$ts_method) + +``` + + +### Print out the optimal results + +```{r Printing the results, echo=FALSE} +output <- paste("OPTIMAL RESULTS \n\n", + "Minimum obtained MAPE: ", format(min(all_mape), digits = 4), "\n", + "Optimal method for distributing forecasts within the hierarchy: ", opt_params$hts_method, "\n", + "Optimal forecasting method: ", opt_params$ts_method, "\n") +cat(output) + +if(opt_params$hts_method == "comb") cat(paste("Optimal weights used for `comb` method: ", opt_params$comb_weights, "\n")) + +cat("\n Forecast for the next two years at the City level obtained using optimal parameters: \n\n") + +print(aggts(hts_fcast, levels = 2)) + +# Plot the forecasted time series +names(hts_fcast$labels) <- c("Total", "State", "City") +plot(hts_fcast) +``` + +## Hands-on exercise + +In this part, we will try to run the above parameter sweep on a larger data set. Forecasting hierarchical time series takes more time for deeper and wider hierarchies, which are very common in real life applications. For example, if we were to forecast company sales by state, city, store, product category, and product, we would end up with hundreds of thousands of time series. Forecasting this time series hierarchy for just one parameter set may take hours. Doing a parameter sweep in such a scenario would be prohibitive. Being able to distribute that computation to a Spark cluster (by switching to _RxSpark()_ compute context) with hundreds of cores reduces that time drastically. + +Here, we will generate a larger time series data set. We will do that by replicating the existing data set, and adding a little bit of noise to it. To increase the data set we will multiply the number of time series with a factor _x_. + +### Generating larger data + +```{r Generating larger data} +# Increase the number of time series by factor x +# TRY changing this variable +x = 2 + +# Function to add noise to a data set +addNoise <- function(data) { + + data_dim <- dim(data)[1] * dim(data)[2] + noise <- matrix(runif(data_dim, -500, 500), dim(data)[1]) + noisified <- data + noise + return(noisified) + +} + +# Replicate time series x times +larger_data <- coredata(vn)[ , rep(seq(ncol(vn)), x)] +larger_data <- addNoise(larger_data) + +# Create a time series object +vnx <- ts(larger_data, frequency = 4, start = c(1998, 1)) + +# Create hierachical time series dataset +htsdata <- hts(vnx, nodes=list(4*x, rep(c(2,2,2,2), x))) + +# Let's see what our data looks like +print(htsdata) + +# Rename the nodes of the hierarchy +htsdata$labels$`Level 1` <- paste0('State_', 1:length(htsdata$labels$`Level 1`)) +htsdata$labels$`Level 2` <- paste0('City_', 1:length(htsdata$labels$`Level 2`)) +names(htsdata$labels) <- c("Total", "State", "City") + +# Split data into train and test (leave out years 2010 and 2011 for testing) +train_data <- window(htsdata, end = c(2009, 4)) +test_data <- window(htsdata, start = c(2010, 1)) + + +# TRY changing the compute context and see how it affects the execution time +rxSetComputeContext(RxLocalParallel()) +# rxSetComputeContext(RxSpark(consoleOutput=TRUE, numExecutors = 1, executorCores=2, executorMem="1g")) + +# Measure execution time +et <- system.time( + + # Run many distributed jobs + rxResult <- rxExec(FUN = forecast_hts, + traindata = train_data, + testdata = test_data, + htsmethod = rxElemArg(param_space$hts_method), + tsmethod = rxElemArg(param_space$ts_method), + combweights = rxElemArg(param_space$comb_weights), + consoleOutput = TRUE, + packagesToLoad = c('hts')) +) + +cat(paste("Elapsed time: ", format(et['elapsed'], digits = 4), "seconds. \n")) + +all_mape <- sapply( rxResult, function(x) x["MAPE",] ) +min_mape_indx <- which.min(all_mape) + +# Optimal parameters +opt_params <- param_space[min_mape_indx,] + +# Forecast the next 8 quarters using optimal parameters +horiz <- 8 +hts_fcast <- forecast(object = htsdata, + h = horiz, + method = opt_params$hts_method, + fmethod = opt_params$ts_method) + +plot(hts_fcast) +``` diff --git a/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.html b/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.html new file mode 100644 index 00000000..fcc558f7 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.html @@ -0,0 +1,585 @@ + + + + + + + + + + + + + + +Parallel models: training many parallel models for hierarchical time series optimization + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + +
+

0.1 Hierarchical time series

+

Time series data can often be disaggregated by attributes of interest to form groups of time series or a hierarchy. For example, one might be interested in forecasting demand of all products in total, by location, by product category, by customer, etc. (see picture below). Forecasting hierarchical time series data is challenging because the generated forecasts need to satisfy the aggregate requirement, that is, lower-level forecasts need to sum up to the higher-level forecasts. There are many approaches that solve this problem, differing in the way they aggregate individual time series forecasts across the groups or the hierarchy: bottom-up, top-down, or middle-out.

+

Training hierarchical time series forecasting models require searching through a large parameter space. The model’s performance greatly varies over the parameters we choose, some of which are:

+
    +
  • univariate time series method for individual series prediction
  • +
  • method for reconciling forecasts across hierarchy
  • +
  • weights we use to reconcile forecasts across hierarchy
  • +
  • etc.
  • +
+

In this tutorial, we will use Australian tourism data set from â€fpp’ package as a sample data set.

+

Rob J Hyndman (2013). fpp: Data for “Forecasting: principles and practice”. R package version 0.5. https://CRAN.R-project.org/package=fpp

+

This data set contains quarterly visitor nights spent by international tourists to Australia available for years 1999-2010. We will use this historical data to forecast nights spent by tourists in Australia:

+
    +
  • In total
  • +
  • By state
  • +
  • By city
  • +
+

This data can be represented as a hierarchy, as shown in the following picture:

+
+Australia tourism data set +

Australia tourism data set

+
+
+
+

0.2 Outline

+

In this tutorial we will:

+
    +
  1. create a hierarchical time series
  2. +
  3. split the data into training and testing
  4. +
  5. generate a grid of input parameters for forecasting function
  6. +
  7. call rxExec() to run forecasting over the parameter space distributed accross local cores or cluster nodes
  8. +
  9. find optimal parameters based on all runs
  10. +
  11. forecast the next two years using the optimal parameters
  12. +
  13. try running the above for larger data size
  14. +
+
+
+

0.3 HTS Forecasting

+
+

0.3.1 Loading necessary libraries

+
if(!require("hts")) install.packages('hts')
+if(!require("fpp")) install.packages('fpp')
+
+library(hts)
+library(fpp)
+
+
+

0.3.2 Forecasting function

+

We will use the following function for time series forecasting. The function runs hierarchical forecast on training data set and returns evaluation metrics on test data set

+
forecast_hts <- function (traindata, testdata, htsmethod, tsmethod, combweights){
+  
+  # Forecasting horizon
+  horiz =  dim(aggts(testdata))[1]
+  
+  # Run hierarchical forecast
+  hts_fcast <- forecast(object  = traindata, 
+                        h       = horiz,
+                        method  = htsmethod,
+                        fmethod = tsmethod,
+                        weights = combweights)
+  
+  # Return evaluation metrics at the top level
+  fcast_acc <- accuracy.gts(hts_fcast, test = testdata, levels = 0)
+  
+}
+
+
+

0.3.3 Hierarchical time series data set

+

Let’s process the data

+
# Create hierachical time series dataset
+htsdata <- hts(vn, nodes=list(4,c(2,2,2,2)))
+
## Since argument characters are not specified, the default labelling system is used.
+
# Rename the nodes of the hierarchy
+htsdata$labels$`Level 1` <- c("NSW", "VIC", "QLD", "Other")
+htsdata$labels$`Level 2` <- c("Sydney", "NSW-Other", "Melbourne", "VIC-Other", "BrisbaneGC", "QLD-Other", "Capitals", "Other")
+names(htsdata$labels) <- c("Total", "State", "City")
+
+# Let's look at the data
+htsdata
+
## Hierarchical Time Series 
+## 3 Levels 
+## Number of nodes at each level: 1 4 8 
+## Total number of series: 13 
+## Number of observations per series: 56 
+## Top level series: 
+##       Qtr1  Qtr2  Qtr3  Qtr4
+## 1998 84502 65314 72749 70891
+## 1999 86891 66872 72179 68323
+## 2000 85650 64467 70408 72859
+## 2001 80389 67973 69836 71446
+## 2002 83932 63526 75548 75651
+## 2003 83863 67191 71395 71662
+## 2004 85838 66979 73837 70224
+## 2005 85994 59635 66840 63390
+## 2006 82646 67520 65944 69550
+## 2007 86241 67397 69512 69204
+## 2008 87012 59762 66413 64678
+## 2009 74378 57936 65403 64519
+## 2010 74635 59704 67534 63521
+## 2011 72274 62906 70499 64894
+
# Split data into train and test (leave out years 2010 and 2011 for testing)
+train_data <- window(htsdata,  end = c(2009, 4))
+test_data <- window(htsdata, start = c(2010, 1))
+
+
+

0.3.4 Visualizing hierarchy

+

Let’s see what the hierarchical time series data looks like.

+
# Plot the hierarchial time series data
+plot(htsdata)
+

+
+
+

0.3.5 Parameter space

+

Let’s generate the parameter space

+
# Vary methods for generating base time series forecasts
+ts_method <- c("ets", "arima", "rw")
+
+# Vary methods for reconciling base forecasts to satisfy aggregation requirement
+hts_method <- c("bu", "comb", "tdgsa", "tdgsf", "tdfp") 
+
+# Vary forecast weights for the optimal cobination approach 
+comb_weights <- c("mint", "wls", "ols", "nseries")
+
+# Generate all possible combinations of the above parameters
+param_space <- expand.grid(hts_method, ts_method, comb_weights, stringsAsFactors = FALSE)
+colnames(param_space) <- c("hts_method", "ts_method", "comb_weights")
+
+
+# Remove ilegal combinations
+
+#   - comb_weights only applies to hts_method == "comb"
+rm_inds <- param_space$hts_method != "comb"
+param_space$comb_weights[rm_inds] <- "none"
+param_space <- param_space[!duplicated.data.frame(param_space),]
+
+
+

0.3.6 Compute contexts

+

Using Microsoft R Server’s rxSetComputeContext() function we can easily switch between different compute platforms, and run the same piece of code on those different platforms. rxExec() function then distributes the execution of the forecasting function we defined above onto the specified compute context.

+
    +
  • rxSetComputeContext(“local”) - sets compute context to “local” and causes rxExec() to execute runs locally in a serial manner.

  • +
  • rxSetComputeContext(RxLocalParallel()) - causes rxExec() to run multiple tasks in parallel, thereby using the multiple cores on your local machine. The downside is that this will use more memory and will slow down your computer for other work you may be trying to do at the same time.

  • +
  • rxSetComputeContext(RxSpark()) - for parallelized distributed execution via Spark across the nodes of a cluster

  • +
+
rxSetComputeContext(RxSpark())
+# rxSetComputeContext(RxSpark(consoleOutput=TRUE, numExecutors = 1, executorCores=2, executorMem="1g"))
+
+# Measure execution time
+et <- system.time(
+    
+# Run many distributed jobs
+rxResult <- rxExec(FUN            = forecast_hts,  
+                   traindata      = train_data, 
+                   testdata       = test_data, 
+                   htsmethod      = rxElemArg(param_space$hts_method),  
+                   tsmethod       = rxElemArg(param_space$ts_method),
+                   combweights    = rxElemArg(param_space$comb_weights),
+                   consoleOutput  = TRUE,
+                   packagesToLoad = c('hts'))
+)
+
##  Running job: 59212
+
cat(paste("Elapsed time: ", format(et['elapsed'], digits = 4), "seconds. \n"))
+
## Elapsed time:  63.6 seconds.
+
+
+

0.3.7 Gather the results

+

Collect the results of the rxExec(), and find the result with the best evaluation metric (smallest MAPE)

+
all_mape <- sapply( rxResult, function(x) x["MAPE",] )
+min_mape_indx <- which.min(all_mape)
+
+# Optimal parameters
+opt_params <- param_space[min_mape_indx,]
+
+# Forecast the next 8 quarters using optimal parameters
+horiz <- 8
+hts_fcast <- forecast(object  = htsdata, 
+                      h       = horiz,
+                      method  = opt_params$hts_method,
+                      fmethod = opt_params$ts_method)
+
+ +
+
+

0.4 Hands-on exercise

+

In this part, we will try to run the above parameter sweep on a larger data set. Forecasting hierarchical time series takes more time for deeper and wider hierarchies, which are very common in real life applications. For example, if we were to forecast company sales by state, city, store, product category, and product, we would end up with hundreds of thousands of time series. Forecasting this time series hierarchy for just one parameter set may take hours. Doing a parameter sweep in such a scenario would be prohibitive. Being able to distribute that computation to a Spark cluster (by switching to RxSpark() compute context) with hundreds of cores reduces that time drastically.

+

Here, we will generate a larger time series data set. We will do that by replicating the existing data set, and adding a little bit of noise to it. To increase the data set we will multiply the number of time series with a factor x.

+
+

0.4.1 Generating larger data

+
# Increase the number of time series by factor x
+# TRY changing this variable
+x = 5
+
+# Function to add noise to a data set
+addNoise <- function(data) {
+  
+  data_dim <- dim(data)[1] * dim(data)[2]
+  noise <- matrix(runif(data_dim, -500, 500), dim(data)[1])
+  noisified <- data + noise
+  return(noisified)
+  
+}
+
+# Replicate time series x times
+larger_data <- coredata(vn)[ ,  rep(seq(ncol(vn)), x)]
+larger_data <- addNoise(larger_data)
+
+# Create a time series object
+vnx <- ts(larger_data, frequency = 4, start = c(1998, 1))
+
+# Create hierachical time series dataset
+htsdata <- hts(vnx, nodes=list(4*x, rep(c(2,2,2,2), x)))
+
## Since argument characters are not specified, the default labelling system is used.
+
# Let's see what our data looks like
+print(htsdata)
+
## Hierarchical Time Series 
+## 3 Levels 
+## Number of nodes at each level: 1 20 40 
+## Total number of series: 61 
+## Number of observations per series: 56 
+## Top level series: 
+##          Qtr1     Qtr2     Qtr3     Qtr4
+## 1998 422070.3 326537.6 366180.7 355943.7
+## 1999 435820.2 329182.5 361440.4 339734.0
+## 2000 427549.3 322386.6 353522.8 364062.0
+## 2001 401738.8 339711.0 348391.3 356269.4
+## 2002 420672.8 318184.3 378697.8 378502.7
+## 2003 417755.1 333695.0 359748.2 358247.7
+## 2004 428820.7 335062.4 365835.0 352580.6
+## 2005 429109.4 298365.6 333427.7 315407.1
+## 2006 410840.0 338402.2 332062.6 350052.8
+## 2007 429248.6 335755.3 345557.6 348299.8
+## 2008 431761.3 300996.6 328970.6 320320.2
+## 2009 373946.7 294938.4 327345.1 320451.0
+## 2010 374079.5 300747.2 337394.8 315849.5
+## 2011 361359.8 315418.3 353514.5 325755.4
+
# Rename the nodes of the hierarchy
+htsdata$labels$`Level 1` <- paste0('State_', 1:length(htsdata$labels$`Level 1`))
+htsdata$labels$`Level 2` <- paste0('City_', 1:length(htsdata$labels$`Level 2`))
+names(htsdata$labels) <- c("Total", "State", "City")
+
+# Split data into train and test (leave out years 2010 and 2011 for testing)
+train_data <- window(htsdata,  end = c(2009, 4))
+test_data <- window(htsdata, start = c(2010, 1))
+
+
+# TRY changing the compute context and see how it affects the execution time
+# rxSetComputeContext(RxLocalParallel())
+rxSetComputeContext(RxSpark(consoleOutput = TRUE))
+
+# Measure execution time
+et <- system.time(
+  
+  # Run many distributed jobs
+  rxResult <- rxExec(FUN            = forecast_hts,  
+                     traindata      = train_data, 
+                     testdata       = test_data, 
+                     htsmethod      = rxElemArg(param_space$hts_method),  
+                     tsmethod       = rxElemArg(param_space$ts_method),
+                     combweights    = rxElemArg(param_space$comb_weights),
+                     consoleOutput  = TRUE,
+                     packagesToLoad = c('hts'))
+)
+
##  Running job: 61406
+
cat(paste("Elapsed time: ", format(et['elapsed'], digits = 4), "seconds. \n"))
+
## Elapsed time:  88.54 seconds.
+
all_mape <- sapply( rxResult, function(x) x["MAPE",] )
+min_mape_indx <- which.min(all_mape)
+
+# Optimal parameters
+opt_params <- param_space[min_mape_indx,]
+
+# Forecast the next 8 quarters using optimal parameters
+horiz <- 8
+hts_fcast <- forecast(object  = htsdata, 
+                      h       = horiz,
+                      method  = opt_params$hts_method,
+                      fmethod = opt_params$ts_method)
+
+plot(hts_fcast)
+

+
+
+ + + +
+
+ +
+ + + + + + + + diff --git a/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/Readme.md b/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/Readme.md new file mode 100644 index 00000000..86128459 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/Readme.md @@ -0,0 +1 @@ +This folder contains R code for the Learning Curves use case. diff --git a/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/high_cardinality_learning_curves_demo.Rmd b/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/high_cardinality_learning_curves_demo.Rmd new file mode 100644 index 00000000..b7b3d5b1 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/high_cardinality_learning_curves_demo.Rmd @@ -0,0 +1,206 @@ +--- +title: "Learning Curves on High-Cardinality Inputs" +author: "Bob Horton" +date: "March 6, 2017" +output: + html_document: default +--- + +```{r setup, include=FALSE} +t0 <- Sys.time() +knitr::opts_chunk$set(echo=TRUE, cache=TRUE, message=FALSE) +rxOptions(reportProgress=0) +if(file.exists("/dsvm")) +{ + Sys.setenv(SPARK_HOME="/dsvm/tools/spark/current", + YARN_CONF_DIR="/opt/hadoop/current/etc/hadoop", + JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64", + PATH="/anaconda/envs/py35/bin:/dsvm/tools/cntk/cntk/bin:/usr/local/mpi/bin:/dsvm/tools/spark/current/bin:/anaconda/envs/py35/bin:/dsvm/tools/cntk/cntk/bin:/usr/local/mpi/bin:/dsvm/tools/spark/current/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/opt/hadoop/current/sbin:/opt/hadoop/current/bin:/home/remoteuser/.local/bin:/home/remoteuser/bin:/opt/hadoop/current/sbin:/opt/hadoop/current/bin" + ) +} + +# Sys.setenv(PATH="/anaconda/envs/py35/bin:/dsvm/tools/cntk/cntk/bin:/usr/local/mpi/bin:/dsvm/tools/spark/current/bin:/anaconda/envs/py35/bin:/dsvm/tools/cntk/cntk/bin:/usr/local/mpi/bin:/dsvm/tools/spark/current/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/opt/hadoop/current/sbin:/opt/hadoop/current/bin:/home/remoteuser/.local/bin:/home/remoteuser/bin:/opt/hadoop/current/sbin:/opt/hadoop/current/bin") +``` + +## Simulating data + +This data set has a variety of effect sizes associated with variables covering a wide range of cardinalities. + +```{r simdata} +N <- 1e6 # 5e7 +NUM_VARS <- 10 +NOISE <- 20 +SIMULATE_DATA <- TRUE +RUN_LOCAL <- FALSE +HDINSIGHT <- FALSE + +generating_coefficients <- lapply(1:NUM_VARS, function(i){ + cardinality <- 2^(i) + gc <- rnorm(cardinality) + names(gc) <- sprintf("%s%05d", letters[i], 1:cardinality) + gc +}) +names(generating_coefficients) <- LETTERS[1:NUM_VARS] + +simulate_data <- function(N, gencoef, noise=10){ + sd <- data.frame(lapply(gencoef, function(gc){ + v <- base::sample(names(gc), N, replace=TRUE, prob=length(gc):1) + factor(v, levels=names(gc)) + })) + col_weights <- lapply(seq_along(sd), function(i){gencoef[[i]][sd[[i]]]}) + sd$y <- Reduce("+", col_weights) + rnorm(N, sd=noise) + sd +} + +``` + +### Simulate data: + +```{r simulate_data} + +if (HDINSIGHT){ + dataDir <- "/user/RevoShare/sshuser" # HDInsight +} else { + dataDir <- "/user/RevoShare/remoteuser/Data" # single node +} + +data_table <- RxXdfData(file.path(dataDir, "simdata"), fileSystem=RxHdfsFileSystem()) + +if (SIMULATE_DATA){ + write.csv(simulate_data(N, generating_coefficients, noise=NOISE), file="simdata.csv", row.names=FALSE) + simdata_hdfs_path <- file.path(dataDir, "simdata.csv") + if (rxHadoopFileExists(simdata_hdfs_path)) rxHadoopRemove(simdata_hdfs_path) + rxHadoopCopyFromLocal("simdata.csv", simdata_hdfs_path) + cclass <- c(A="factor", B="factor", C="factor", D="factor", + E="factor", F="factor", G="factor", H="factor", + I="factor", J="factor", y="numeric") + inDataCsv <- RxTextData(file.path(dataDir, "simdata.csv"), + colClasses=cclass, fileSystem=RxHdfsFileSystem()) + rxImport(inDataCsv, outFile=data_table, overwrite=TRUE) +} + +# data_table <- rxImport(simulate_data(N, generating_coefficients, noise=NOISE), +# outFile="simdata.xdf", overwrite=TRUE) + +``` + +## Examine simulated data + +```{r examine_simdata} + +outcome <- "y" +var_names <- setdiff(names(rxGetVarInfo(data_table)), outcome) +names(var_names) <- var_names + +var_names + +knitr::kable(head(data_table, n=15)) + +``` + +## Learning curve with linear models + +Define functions and set various parameters. + +```{r global_parameters} +source("learning_curve_lib.R") + +K_FOLDS <- 3 +SALT <- 1 +NUM_TSS <- 12 # 16 +data_info <- rxGetInfo(data_table, getVarInfo=TRUE) +N <- data_info$numRows +MAX_TSS <- (1 - 1/K_FOLDS) * N # approximate number of cases available for training. +training_fractions <- get_training_set_fractions(10000, MAX_TSS, NUM_TSS) +``` + + +## Building a family of linear models with rxLinMod + +```{r build_parameter_table} + +formula_vec <- sapply(1:length(var_names), function(j){ + vars <- var_names[j:1] + paste(outcome, paste(vars, collapse="+"), sep=" ~ ") +}) +grid_dimensions <- list( model_class="rxLinMod", + training_fraction=training_fractions, + with_formula=formula_vec[4:8], + test_set_kfold_id=1, # 1:K_FOLDS, + KFOLDS=K_FOLDS, + cube=TRUE) + +parameter_table <- do.call(expand.grid, c(grid_dimensions, stringsAsFactors=FALSE)) +dim(parameter_table) +knitr::kable(head(parameter_table, n=15)) +``` + +```{r fit_and_evaluate_models} +parameter_list <- lapply(1:nrow(parameter_table), function(i) parameter_table[i,]) + +if (RUN_LOCAL){ + rxSetComputeContext("localpar") +} else { + rxSetComputeContext(RxSpark( + consoleOutput=TRUE, + numExecutors = 1, + executorCores=2, + executorMem="1g")) +} + + +t1 <- Sys.time() +training_results <- rxExec(run_training_fraction, + elemArgs = parameter_list, + execObjects = c("data_table", "SALT")) + +t2 <- Sys.time() + +sprint_difftime <- function(x){ + paste0(format(unclass(x), digits=3), + " ", attr(x, "units")) +} + +print(sprintf("Elapsed time for evaluating all parameter combinations: %s", + sprint_difftime(t2 - t1))) +``` + +```{r save_training_results} +saveRDS(training_results, "training_results.Rds") +saveRDS(parameter_table, "parameter_table.Rds") +``` + +```{r plot_learning_curves} + +library(ggplot2) +library(dplyr) +library(tidyr) + +training_results_df <- do.call("rbind", training_results) + +training_results_df %>% + gather(error_type, error_value, training:test) %>% + # filter(error_type=="test" & kfold==1) %>% + mutate(fek_grp=factor(paste0(formula, error_type, kfold))) %>% + ggplot(aes(x=log10(tss), y=error_value, linetype=error_type, + group=fek_grp, + col=formula)) + + geom_line(size=1.2) + + geom_hline(aes(yintercept=NOISE), linetype=2, size=1.5) + + ylab("RMSE") + + coord_cartesian(ylim=c(0.99, 1.02)*NOISE) + + ggtitle("Simulated data") + +``` + +```{r save_results} +results_file <- sprintf("training_results_N_%s_NOISE_%s.Rds", N, NOISE) +saveRDS(training_results, file=results_file) +``` + +```{r total_time} +t_final <- Sys.time() +print(sprintf("Total elapsed time : %s", + sprint_difftime(t_final - t0))) + +``` \ No newline at end of file diff --git a/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/learning_curve_lib.R b/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/learning_curve_lib.R new file mode 100644 index 00000000..76a7a4d8 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/learning_curve_lib.R @@ -0,0 +1,224 @@ +# Use random number seed to select the rows to be used for training or testing. +# Collect error stats for training set from the model when possible. + +# execObjects = c("data_table", "SALT") +run_training_fraction <- function(model_class, training_fraction, + with_formula, test_set_kfold_id, KFOLDS=3, ...){ + learner <- get(model_class) + + NUM_BUCKETS <- 1000 # for approximate AUC + + row_tagger <- function(data_list, start_row, num_rows, + chunk_num, prob, kfolds, kfold_id, salt){ + rowNums <- seq(from=start_row, length.out=num_rows) + set.seed(chunk_num + salt) + kfold <- sample(1:kfolds, size=num_rows, replace=TRUE) + in_test_set <- kfold == kfold_id + num_training_candidates <- sum(!in_test_set) + keepers <- sample(rowNums[!in_test_set], prob * num_training_candidates) + data_list$in_training_set <- rowNums %in% keepers + data_list$in_test_set <- in_test_set + data_list + } + + row_selection_transform <- function(data_list){ + row_tagger(data_list, .rxStartRow, .rxNumRows, .rxChunkNum, + prob, kfolds, kfold_id, salt) + } + + # Calculate RMSE (root mean squared error) for predictions made with a given model on a dataset. + # Only rows in the test set are counted. + RMSE_transform <- function(data_list){ + if (.rxChunkNum == 1){ + .rxSet("SSE", 0) + .rxSet("rowCount", 0) + } + SSE <- .rxGet("SSE") + rowCount <- .rxGet("rowCount") + + data_list <- row_tagger(data_list, .rxStartRow, .rxNumRows, .rxChunkNum, + prob, kfolds, kfold_id, salt) + + # rxPredict returns a dataframe if you give it one. # data_list$in_test_set + if (class(model)[1] == "SDCAR"){ + test_chunk <- as.data.frame(data_list)[data_list[[SET_SELECTOR]],] + outcome_var <- model$params$formulaVars[1] + residual <- rxPredict(model, test_chunk)[[1]] - test_chunk[[outcome_var]] + } else { + residual <- rxPredict(model, as.data.frame(data_list)[data_list[[SET_SELECTOR]],], + computeResiduals=TRUE, residVarNames="residual")$residual + } + + SSE <- SSE + sum(residual^2, na.rm=TRUE) + rowCount <- rowCount + sum(!is.na(residual)) + .rxSet("SSE", SSE) + .rxSet("rowCount", rowCount) + return(data_list) + } + + AUC_transform <- function(data_list){ + # NUM_BUCKETS <- 100; + if (.rxChunkNum == 1){ + # assume the first chunk gives a reasonably representative sample of score distribution + # chunk1_scores <- rxPredict(model, as.data.frame(data_list))[[1]] + # quantile_breaks <- unique(quantile(chunk1_scores, probs=0:NUM_BUCKETS/NUM_BUCKETS))) + # scores must be in range of probabilities (between 0 and 1) + .rxSet("BREAKS", (0:NUM_BUCKETS)/NUM_BUCKETS) # + .rxSet("TP", numeric(NUM_BUCKETS)) + .rxSet("FP", numeric(NUM_BUCKETS)) + } + TPR <- .rxGet("TP") + FPR <- .rxGet("FP") + BREAKS <- .rxGet("BREAKS") + + data_list <- row_tagger(data_list, .rxStartRow, .rxNumRows, .rxChunkNum, + prob, kfolds, kfold_id, salt) + + data_set <- as.data.frame(data_list)[data_list[[SET_SELECTOR]],] + labels <- data_set[[model$param$formulaVars$original$depVars]] + scores <- rxPredict(model, data_set)[[1]] # rxPredict returns a dataframe if you give it one. + bucket <- cut(scores, breaks=BREAKS, include.lowest=TRUE) + + # data.frame(labels, scores, bucket) + TP <- rev(as.vector(xtabs(labels ~ bucket))) # positive cases in each bucket, top scores first + N <- rev(as.vector(xtabs( ~ bucket))) # total cases in each bucket + FP <- N - TP + + .rxSet("TP", TP) + .rxSet("FP", FP) + return(data_list) + } + + simple_auc <- function(TPR, FPR){ + dFPR <- c(0, diff(FPR)) + sum(TPR * dFPR) - sum(diff(TPR) * diff(FPR))/2 + } + + calculate_RMSE <- function(with_model, xdfdata, set_selector){ + xformObjs <- rxDataStep(inData=xdfdata, + transformFunc=RMSE_transform, + transformVars=c(rxGetVarNames(xdfdata) ), + transformObjects=list(SSE=0, rowCount=0, SET_SELECTOR=set_selector, + model=with_model, row_tagger=row_tagger, + prob=training_fraction, kfolds=KFOLDS, + kfold_id=test_set_kfold_id, + salt=SALT), + returnTransformObjects=TRUE) + with(xformObjs, sqrt(SSE/rowCount)) + } + + calculate_AUC <- function(with_model, xdfdata, set_selector){ + # NUM_BUCKETS <- 100; kfolds=3 + xformObjs <- rxDataStep(inData=xdfdata, + transformFunc=AUC_transform, + transformVars=c( rxGetVarNames(xdfdata) ), + transformObjects=list(TP=numeric(NUM_BUCKETS), FP=numeric(NUM_BUCKETS), + SET_SELECTOR=set_selector, + model=with_model, row_tagger=row_tagger, + prob=training_fraction, kfolds=KFOLDS, + kfold_id=test_set_kfold_id, + salt=SALT), + returnTransformObjects=TRUE) + with(xformObjs, { + TPR <- cumsum(TP)/sum(TP) + FPR <- cumsum(FP)/sum(FP) + simple_auc(TPR, FPR) + }) + } + + get_training_error <- function(fit) { + switch( class(fit)[[1]], + rxLinMod = with(summary(fit)[[1]], sqrt(residual.squares/nValidObs)), + rxBTrees =, + rxDForest = if(!is.null(fit$type) && "anova" == fit$type){ + calculate_RMSE(fit, data_table, "in_training_set") + } else { + calculate_AUC(fit, data_table, "in_training_set") + }, + rxDTree = if ("anova" == fit$method){ + calculate_RMSE(fit, data_table, "in_training_set") + } else { # "class" + calculate_AUC(fit, data_table, "in_training_set") + }, + rxLogit = calculate_AUC(fit, data_table, "in_training_set"), + SDCA = calculate_AUC(fit, data_table, "in_training_set"), + #rxFastLinear, class = SDCA (BinaryClassifierTrainer) + SDCAR = calculate_RMSE(fit, data_table, "in_training_set") + # rxFastLinear, class = SDCAR (RegressorTrainer) + ) + } + + get_test_error <- function(fit) { + switch( class(fit)[[1]], + rxLinMod = calculate_RMSE(fit, data_table, "in_test_set"), + rxBTrees =, + rxDForest = if(!is.null(fit$type) && "anova" == fit$type){ + calculate_RMSE(fit, data_table, "in_test_set") + } else { # fit$type == "class" + calculate_AUC(fit, data_table, "in_test_set") + }, + rxDTree = if ("anova" == fit$method){ + calculate_RMSE(fit, data_table, "in_test_set") + } else { # "class" + calculate_AUC(fit, data_table, "in_test_set") + }, + rxLogit = calculate_AUC(fit, data_table, "in_test_set"), + SDCA = calculate_AUC(fit, data_table, "in_test_set"), + #rxFastLinear, class = SDCA (BinaryClassifierTrainer) + SDCAR = calculate_RMSE(fit, data_table, "in_test_set") + # rxFastLinear, class = SDCAR (RegressorTrainer) + ) + } + + get_tss <- function(fit){ + switch( class(fit)[[1]], + rxLinMod = , + rxLogit = fit$nValidObs, + rxDTree = fit$valid.obs, + rxBTrees =, + rxDForest =, + SDCA =, + SDCAR = training_fraction * (1 - 1/KFOLDS) * rxGetInfo(data_table)$numRows + ) + } + + train_time <- system.time( + fit <- learner(as.formula(with_formula), data_table, + rowSelection=(in_training_set == TRUE), + transformFunc=row_selection_transform, + transformObjects=list(row_tagger=row_tagger, prob=training_fraction, + kfold_id=test_set_kfold_id, kfolds=KFOLDS, + salt=SALT), + ...) + )[['elapsed']] + + e1_time <- system.time( + training_error <- get_training_error(fit) + )[['elapsed']] + + e2_time <- system.time( + test_error <- get_test_error(fit) + )[['elapsed']] + + data.frame(tss=get_tss(fit), model_class=model_class, training=training_error, test=test_error, + train_time=train_time, train_error_time=e1_time, test_error_time=e2_time, + formula=with_formula, kfold=test_set_kfold_id, ...) + +} + + +create_formula <- function(outcome, varnames, interaction_pow=1){ + vars <- paste(setdiff(varnames, outcome), collapse=" + ") + if (interaction_pow > 1) vars <- sprintf("(%s)^%d", vars, interaction_pow) + sprintf("%s ~ %s", outcome, vars) +} + +#' get_training_fractions +#' Create a vector of fractions of available training data to be used at the evaluation +#' points of a learning curve. +#' @param min_tss; target minimum training set size. +#' @param max_tss: approximate maximum training set size. This is used to calculate the +#' fraction used for the smallest point. +#' @param num_tss: number of training set sizes. +get_training_set_fractions <- function(min_tss, max_tss, num_tss) + exp(seq(log(min_tss/max_tss), log(1), length=num_tss)) diff --git a/Misc/StrataSanJose2017/Code/bigmemory/bigmemory.R b/Misc/StrataSanJose2017/Code/bigmemory/bigmemory.R new file mode 100644 index 00000000..cc522dfb --- /dev/null +++ b/Misc/StrataSanJose2017/Code/bigmemory/bigmemory.R @@ -0,0 +1,68 @@ +# +# DESCRIPTION +# +# This script shows an example of how to create and use big matrix objects +# with the "bigmemory" and its related packages. +# + +# install "bigmemory" and related packages: +if(!require("bigmemory")) install.packages('bigmemory') +if(!require("biganalytics")) install.packages('biganalytics') + + +##### Example of "bigmemory" + +# change working directory +setwd("/home/remoteuser/Data") + +# call the library +library("bigmemory") + +# read airline data into a big matrix object (20 million rows * 26 columns; 1.5GB) +# (it takes 2+ mins) +backing.file <- "airline_big.bin" +descriptor.file <- "airline_big.desc" +airline_big <- read.big.matrix("airline_20MM.csv", + header = TRUE, + type = "integer", + sep = ",", + backingfile = backing.file, + descriptorfile = descriptor.file, + shared = TRUE) +head(airline_big) + +# if descriptor.file is already exist, we can load the big matrix by attaching that file +# airline_big <- attach.big.matrix(descriptor.file) + +# size of big matrix object = 664 bytes/0.6 KB +object.size(airline_big) + +# convert big matrix object ot R matrix object +airline_matrix <- airline_big[,] + +# size of R matrix object = 2080002048 bytes/2.08 GB +object.size(airline_matrix) + +# read same data into R Data Frame (it takes 3+ mins) +airline_df <- read.csv("airline_20MM.csv", + header = TRUE, + sep = ",") + +# size of R Data Frame object = 2080003456 bytes/2.08 GB +object.size(airline_df) + + +##### Example of "biganalytics" + +# call the library +library("biganalytics") + +# perform simply data transformation on "CRSDepTime" +# round "CRSDepTime" to the nearest hour +airline_big[, "CRSDepTime"] <- floor(airline_big[, "CRSDepTime"] / 100) + +# fit a glm model +# (it takes 1.5 mins) +model_big <- bigglm.big.matrix(formula = IsArrDelayed ~ Month+DayofMonth+DayOfWeek+CRSDepTime+Distance, + data = airline_big, family = binomial()) +summary(model_big) diff --git a/Misc/StrataSanJose2017/Code/bigmemory/readme.md b/Misc/StrataSanJose2017/Code/bigmemory/readme.md new file mode 100644 index 00000000..d8b89b43 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/bigmemory/readme.md @@ -0,0 +1 @@ +This folder contains the scripts for the demo of using bigmemory and its related packages. diff --git a/Misc/StrataSanJose2017/Code/ff/ff.R b/Misc/StrataSanJose2017/Code/ff/ff.R new file mode 100644 index 00000000..078d9dfb --- /dev/null +++ b/Misc/StrataSanJose2017/Code/ff/ff.R @@ -0,0 +1,47 @@ +# +# DESCRIPTION +# +# This script shows an example of how to create and use ffdf objects +# with the "ff" and its related packages. +# + +# install "ff" and related packages: +if(!require("ff")) install.packages('ff') +if(!require("ffbase")) install.packages('ffbase') +if(!require("biglm")) install.packages('biglm') + + +##### Example of "ff" + +# change working directory +setwd("/home/remoteuser/Data") + +# call the library +library("ff") + +# read airline data into a ffdf object (20 million rows * 26 columns; 1.5GB) +# (it takes 2+ mins~) +airline_ff <- read.csv.ffdf(file = "airline_20MM.csv", + header = TRUE, + na.strings = NA) +head(airline_ff) + +# size of ffdf object in memory = 87960 bytes/88 KB +object.size(airline_ff) + + +##### Example of "ffbase" + +# call the library +library("ffbase") +library("biglm") + +# perform simply data transformation on "CRSDepTime" +# round "CRSDepTime" to the nearest hour +airline_ff[, "CRSDepTime"] <- floor(airline_ff[, "CRSDepTime"] / 100) + +# fit a glm model +# (it takes 2+ mins) +model_ff <- bigglm.ffdf(formula = IsArrDelayed ~ Month+DayofMonth+DayOfWeek+CRSDepTime+Distance, + data = airline_ff, family = binomial()) +summary(model_ff) diff --git a/Misc/StrataSanJose2017/Code/ff/readme.md b/Misc/StrataSanJose2017/Code/ff/readme.md new file mode 100644 index 00000000..a1f0eebe --- /dev/null +++ b/Misc/StrataSanJose2017/Code/ff/readme.md @@ -0,0 +1 @@ +This folder contains the scripts for the demo of using ff and its related packages. diff --git a/Misc/StrataSanJose2017/Code/sparklyr/Readme.md b/Misc/StrataSanJose2017/Code/sparklyr/Readme.md new file mode 100644 index 00000000..c6ad61a5 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/sparklyr/Readme.md @@ -0,0 +1 @@ +Contains code for featurization and modeling with sparklyr (http://spark.rstudio.com/). diff --git a/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forDSVM.Rmd b/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forDSVM.Rmd new file mode 100644 index 00000000..a90755b3 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forDSVM.Rmd @@ -0,0 +1,296 @@ +--- +title: "Using sparklyr with 2013 NYCTaxi Data: Featurization, modeling, and evaluation" +date: "`r format(Sys.time(), '%B %d, %Y')`" +author: "Algorithms and Data Science & R Server Teams, Microsoft Data Group" +output: + html_document: + fig_caption: yes + fig_height: 4 + fig_width: 4 + highlight: haddock + keep_md: yes + number_sections: yes + theme: journal + toc: yes + toc_float: yes +runtime: knit +--- + +
+#Introduction +This Markdown document shows the use of sparklyr for feature engineering and creating machine learning models. The data used for this exercise is the public NYC Taxi Trip and Fare data-set (2013, December, ~4 Gb, ~13 million rows) available from: http://www.andresmh.com/nyctaxitrips. Data for this exercise can be downloaded from the public blob (see below). The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here. + +sparklyr provides bindings to Spark’s distributed machine learning library. In particular, sparklyr allows you to access the machine learning routines provided by the spark.ml package. Together with sparklyr’s dplyr interface, you can easily create and tune machine learning workflows on Spark, orchestrated entirely within R. + +Where necessary, small amounts of data is brought to the local data frames for plotting and visualization. +
+
+ +
+ +#Creating spark connection, loading packages +```{r Load Packages, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# LOAD LIBRARIES FROM SPECIFIED PATH +########################################### +Sys.setenv(YARN_CONF_DIR="/opt/hadoop/current/etc/hadoop", HADOOP_HOME="/opt/hadoop/current", + JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64", + SPARK_HOME = "/dsvm/tools/spark/current", + PATH=paste0(Sys.getenv("PATH"),":/opt/hadoop/current/bin:/dsvm/tools/spark/current/bin")) +## *** NOTE: SPARK PATH IS DIFFERENT IN DSVM AND HDI CLUSTER + +.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) +library(rmarkdown) +library(knitr) +library(sparklyr) +library(dplyr) +library(DBI) +library(gridExtra) +library(ggplot2) + +########################################### +## CREATE SPARKLYR SPARK CONNECTION +########################################### +sp <- spark_connect(master = "yarn-client") + +########################################### +## SPECIFY BASE HDFS DIRECTORY +########################################### +fullDataDir <- "/user/RevoShare/remoteuser/Data" +## *** NOTE: FILE PATH IS DIFFERENT IN DSVM AND HDI CLUSTER +``` +
+
+
+ +#Read joined trip-fare data and cache in memory +If a data-set is large, it may need to be down-sampled for modeling in reasonable amount of time. Here we used the sample function from SparkR to down-sample the joined tax-fare data. We then save the data in HDFS for use as input into the sparklyr modeling functions. +```{r Load data in sparklyr dataframe, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# LOAD SAMPLED JOINED TAXI DATA FROM HDFS, CACHE +########################################### +starttime <- Sys.time(); + +joinedDF <- spark_read_parquet(sp, name = "joined_table", + path = file.path(fullDataDir, "NYCjoinedParquetSubset"), + memory = TRUE, overwrite = TRUE) +tbl_cache(sp, "joined_table") +head(joinedDF, 3) + +########################################### +# SHOW THE NUMBER OF OBSERVATIONS IN DATA +########################################### +count(joinedDF) + +########################################### +# FILTER AND SAMPLE TO A SMALLER DATAFRAME FOR FASTER MODEL BUILDING +########################################### +joinedDFsmall <- joinedDF %>% + dplyr::filter(trip_distance > 1) %>% + sdf_sample(fraction = 0.1, + replacement = FALSE, seed = 123) + +endtime <- Sys.time(); +print (endtime - starttime); +``` + +
+#Transform variables using sparklyr functions +Spark provides feature transformers, faciliating many common transformations of data within in a Spark DataFrame, and sparklyr exposes these within the ft_* family of functions. These routines generally take one or more input columns, and generate a new output column formed as a transformation of those columns. Here, we show the use of two such functions to bucketize (categorize) or binarize features. Payment type (CSH or CRD) is binarized using string-indexer and binerizer functions. And, traffic-time bins is bucketized using the bucketizer function. +```{r Using ft_ functions for feature transformation, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# CREATE TRANSFORMED FEATURES, BINARIZE PAYMENT-TYPE +########################################### +starttime <- Sys.time(); + +# Binarizer +joinedDF2 <- joinedDFsmall %>% + ft_string_indexer(input_col = 'payment_type', output_col = 'payment_ind') %>% + ft_binarizer(input_col = 'payment_ind', output_col = 'pay_type_bin', threshold = 0.5) + +endtime <- Sys.time(); +print (endtime - starttime); +``` + +
+#Create train-test partitions +Data can be partitioned into training and testing using the sdf_partition function. +```{r Partition data into train/test, message=FALSE, warning=FALSE, echo=TRUE} +starttime <- Sys.time(); +########################################### +# CREATE TRAIN/TEST PARTITIONS +########################################### +partitions <- joinedDF2 %>% sdf_partition(training = 0.7, test = 0.3, seed = 123) +head(joinedDF2) + +endtime <- Sys.time(); +print (endtime - starttime); +``` +
+ +#Create ML models +Spark’s machine learning library can be accessed from sparklyr through the ml_* family of functions. Here we create ML models for the prediction of tip-amount for taxi trips. + +##CreateElastic Net model +Create elastic net model using training data, and evaluate on test data-set +```{r Elastic net modeo, message=FALSE, warning=FALSE, echo=TRUE, fig.width=5, fig.height=4} +starttime <- Sys.time(); + +# Fit elastic net regression model +fit <- partitions$training %>% + ml_linear_regression(tip_amount ~ pay_type_bin + pickup_hour + + passenger_count + trip_distance + + TrafficTimeBins, + alpha = 0.5, lambda = 0.01) + +# Show summary of fitted Elastic Net model +summary(fit) + +# Predict on test data and keep predictions in Spark context +predictedVals <- sdf_predict(fit, newdata = partitions$test) +predictedValsSampled <- sdf_sample(predictedVals, fraction = 0.1, replacement = FALSE) +predictedDF <- as.data.frame(predictedValsSampled) + +# Evaluate and plot predictions (R-sqr) +Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr; + +# Sample predictions for plotting +predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),] + +# Plot actual vs. predicted tip amounts +lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled) +ggplot(predictedDFSampled, aes(tip_amount, prediction)) + + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + + geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], + intercept = summary(lm_model)$coefficients[1,1]), + color = "red") + +endtime <- Sys.time(); +print (endtime - starttime); +``` + +##Create Random Forest Model +Create a random forest model using training data, and evaluate on test data-set +```{r Random forest model, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=5} +starttime <- Sys.time(); + +# Fit Random Forest regression model +fit <- partitions$training %>% + ml_random_forest(response = "tip_amount", + features = c("pay_type_bin", "fare_amount", "pickup_hour", + "passenger_count", "trip_distance", + "TrafficTimeBins"), + max.bins = 32L, max.depth = 5L, num.trees = 25L) + +# Show summary of fitted Random Forest model +summary(fit) + +# Get feature importance of RF model +feature_importance <- ml_tree_feature_importance(sp, fit) %>% + mutate(importance = as.numeric(levels(importance))[importance]) %>% + mutate(feature = as.character(feature)); + +plot1 <- feature_importance %>% + ggplot(aes(reorder(feature, importance), importance)) + + geom_bar(stat = "identity", fill='darkgreen') + coord_flip() + xlab("") + + ggtitle("Feature Importance") + + +# Predict on test data and keep predictions in Spark context +predictedVals <- sdf_predict(fit, newdata = partitions$test) +predictedValsSampled <- sdf_sample(predictedVals, fraction = 0.1, replacement = FALSE) +predictedDF <- as.data.frame(predictedValsSampled) + +# Evaluate and plot predictions (R-sqr) +Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr; + +# Sample predictions for plotting +predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),] + +# Plot +lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled) +plot2 <- ggplot(predictedDFSampled, aes(tip_amount, prediction)) + + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + + geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], + intercept = summary(lm_model)$coefficients[1,1]), + color = "red") + +grid.arrange(plot1, plot2, ncol=2) + +endtime <- Sys.time(); +print (endtime - starttime); +``` + + +##Create Gradient Boosted Tree Model +Create a gradient boosted tree model using training data, and evaluate on test data-set +```{r Boosted tree model, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=5} +starttime <- Sys.time(); + +# Fit Gradient Boosted Tree regression model +fit <- partitions$training %>% + ml_gradient_boosted_trees(tip_amount ~ pay_type_bin + pickup_hour + + passenger_count + trip_distance + + TrafficTimeBins, + max.bins = 32L, max.depth = 5L, + type = "regression") + +# Show summary of fitted Random Forest model +summary(fit) + +# Get feature importance of GBT model +feature_importance <- ml_tree_feature_importance(sp, fit) %>% + mutate(importance = as.numeric(levels(importance))[importance]) %>% + mutate(feature = as.character(feature)); + +plot1 <- feature_importance %>% + ggplot(aes(reorder(feature, importance), importance)) + + geom_bar(stat = "identity", fill='darkgreen') + coord_flip() + xlab("") + + ggtitle("Feature Importance") + +# Predict on test data and keep predictions in Spark context +predictedVals <- sdf_predict(fit, newdata = partitions$test) +predictedValsSampled <- sdf_sample(predictedVals, fraction = 0.1, replacement = FALSE) +predictedDF <- as.data.frame(predictedValsSampled) + +# Evaluate and plot predictions (R-sqr) +Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr; + +# Sample predictions for plotting +predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),] + +# Plot +lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled) +plot2 <- ggplot(predictedDFSampled, aes(tip_amount, prediction)) + + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + + geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], + intercept = summary(lm_model)$coefficients[1,1]), + color = "red") + +grid.arrange(plot1, plot2, ncol=2) + +endtime <- Sys.time(); +print (endtime - starttime); +``` + + +#Uncache objects and disconnect from spark +```{r Uncache and disconnect, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=5} +########################################### +# UNCACHE TABLES +########################################### +tbl_uncache(sp, "joined_table") + +########################################### +# DISCONNECT SPARK CONNECTION +########################################### +spark_disconnect(sp) +``` + +
+
+
+
+ +#Summary +The examples shown here can be adopted to fit other data exploration and modeling scenarios having different data-types or prediction tasks (e.g. classification) \ No newline at end of file diff --git a/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forDSVM.html b/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forDSVM.html new file mode 100644 index 00000000..7c9421f6 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forDSVM.html @@ -0,0 +1,619 @@ + + + + + + + + + + + + + + + +Using sparklyr with 2013 NYCTaxi Data: Featurization, modeling, and evaluation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + +
+
+

1 Introduction

+

This Markdown document shows the use of sparklyr for feature engineering and creating machine learning models. The data used for this exercise is the public NYC Taxi Trip and Fare data-set (2013, December, ~4 Gb, ~13 million rows) available from: http://www.andresmh.com/nyctaxitrips. Data for this exercise can be downloaded from the public blob (see below). The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here.

+

sparklyr provides bindings to Spark’s distributed machine learning library. In particular, sparklyr allows you to access the machine learning routines provided by the spark.ml package. Together with sparklyr’s dplyr interface, you can easily create and tune machine learning workflows on Spark, orchestrated entirely within R.

+Where necessary, small amounts of data is brought to the local data frames for plotting and visualization. +
+


+


+
+
+

2 Creating spark connection, loading packages

+
###########################################
+# LOAD LIBRARIES FROM SPECIFIED PATH
+###########################################
+Sys.setenv(YARN_CONF_DIR="/opt/hadoop/current/etc/hadoop", HADOOP_HOME="/opt/hadoop/current", 
+           JAVA_HOME = "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.111-1.b15.el7_2.x86_64",
+           SPARK_HOME = "/dsvm/tools/spark/current",
+           PATH=paste0(Sys.getenv("PATH"),":/opt/hadoop/current/bin:/dsvm/tools/spark/current/bin"))
+## *** NOTE: SPARK PATH IS DIFFERENT IN DSVM AND HDI CLUSTER
+
+.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths()))
+library(rmarkdown)
+library(knitr)
+library(sparklyr)
+library(dplyr)
+library(DBI)
+library(gridExtra)
+library(ggplot2)
+
+###########################################
+## CREATE SPARKLYR SPARK CONNECTION
+###########################################
+sp <- spark_connect(master = "yarn-client")
+
+###########################################
+## SPECIFY BASE HDFS DIRECTORY
+###########################################
+fullDataDir <- "/user/RevoShare/remoteuser/Data"
+## *** NOTE: FILE PATH IS DIFFERENT IN DSVM AND HDI CLUSTER
+
+



+
+
+

3 Read joined trip-fare data and cache in memory

+

If a data-set is large, it may need to be down-sampled for modeling in reasonable amount of time. Here we used the sample function from SparkR to down-sample the joined tax-fare data. We then save the data in HDFS for use as input into the sparklyr modeling functions.

+
###########################################
+# LOAD SAMPLED JOINED TAXI DATA FROM HDFS, CACHE
+###########################################
+starttime <- Sys.time();
+
+joinedDF <- spark_read_parquet(sp, name = "joined_table", 
+                               path = file.path(fullDataDir, "NYCjoinedParquetSubset"), 
+                               memory = TRUE, overwrite = TRUE)
+tbl_cache(sp, "joined_table")
+head(joinedDF, 3)
+
## Source:   query [3 x 9]
+## Database: spark connection master=yarn-client app=sparklyr local=FALSE
+## 
+##   payment_type pickup_hour fare_amount tip_amount passenger_count
+##          <chr>       <int>       <dbl>      <dbl>           <int>
+## 1          CRD          15           8        1.7               1
+## 2          CSH          22          14        0.0               1
+## 3          CRD           4           7        1.0               4
+## # ... with 4 more variables: trip_distance <dbl>, trip_time_in_secs <int>,
+## #   TrafficTimeBins <chr>, tipped <int>
+
###########################################
+# SHOW THE NUMBER OF OBSERVATIONS IN DATA 
+###########################################
+count(joinedDF)
+
## Source:   query [1 x 1]
+## Database: spark connection master=yarn-client app=sparklyr local=FALSE
+## 
+##          n
+##      <dbl>
+## 1 10710669
+
###########################################
+# FILTER AND SAMPLE TO A SMALLER DATAFRAME FOR FASTER MODEL BUILDING
+###########################################
+joinedDFsmall <- joinedDF %>% 
+            dplyr::filter(trip_distance > 1) %>%
+            sdf_sample(fraction = 0.1, 
+                       replacement = FALSE, seed = 123)
+
+endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 38.09442 secs
+
+
+
+

4 Transform variables using sparklyr functions

+

Spark provides feature transformers, faciliating many common transformations of data within in a Spark DataFrame, and sparklyr exposes these within the ft_* family of functions. These routines generally take one or more input columns, and generate a new output column formed as a transformation of those columns. Here, we show the use of two such functions to bucketize (categorize) or binarize features. Payment type (CSH or CRD) is binarized using string-indexer and binerizer functions. And, traffic-time bins is bucketized using the bucketizer function.

+
###########################################
+# CREATE TRANSFORMED FEATURES, BINARIZE PAYMENT-TYPE
+###########################################
+starttime <- Sys.time();
+
+# Binarizer
+joinedDF2 <- joinedDFsmall %>% 
+  ft_string_indexer(input_col = 'payment_type', output_col = 'payment_ind') %>% 
+  ft_binarizer(input_col = 'payment_ind', output_col = 'pay_type_bin', threshold = 0.5)
+
+endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 2.346664 secs
+
+
+
+

5 Create train-test partitions

+

Data can be partitioned into training and testing using the sdf_partition function.

+
starttime <- Sys.time();
+###########################################
+# CREATE TRAIN/TEST PARTITIONS
+###########################################
+partitions <- joinedDF2 %>% sdf_partition(training = 0.7, test = 0.3, seed = 123)
+head(joinedDF2)
+
## Source:   query [6 x 11]
+## Database: spark connection master=yarn-client app=sparklyr local=FALSE
+## 
+##   payment_type pickup_hour fare_amount tip_amount passenger_count
+##          <chr>       <int>       <dbl>      <dbl>           <int>
+## 1          CSH          19        11.0        0.0               1
+## 2          CRD          18        10.0        2.5               6
+## 3          CSH           9         7.5        0.0               1
+## 4          CRD          13        11.0        2.2               1
+## 5          CRD           7        13.5        2.0               1
+## 6          CRD          18        10.5        2.4               1
+## # ... with 6 more variables: trip_distance <dbl>, trip_time_in_secs <int>,
+## #   TrafficTimeBins <chr>, tipped <int>, payment_ind <dbl>,
+## #   pay_type_bin <dbl>
+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 0.7353468 secs
+
+
+
+

6 Create ML models

+

Spark’s machine learning library can be accessed from sparklyr through the ml_* family of functions. Here we create ML models for the prediction of tip-amount for taxi trips.

+
+

6.1 CreateElastic Net model

+

Create elastic net model using training data, and evaluate on test data-set

+
starttime <- Sys.time();
+
+# Fit elastic net regression model
+fit <- partitions$training %>% 
+          ml_linear_regression(tip_amount ~ pay_type_bin + pickup_hour + 
+                                 passenger_count + trip_distance + 
+                                 TrafficTimeBins, 
+                               alpha = 0.5, lambda = 0.01)
+
+# Show summary of fitted Elastic Net model
+summary(fit)
+
## Call: ml_linear_regression(., tip_amount ~ pay_type_bin + pickup_hour + passenger_count + trip_distance + TrafficTimeBins, alpha = 0.5, lambda = 0.01)
+## 
+## Deviance Residuals: (approximate):
+##       Min        1Q    Median        3Q       Max 
+## -11.25150  -0.60969   0.08535   0.52259  12.07828 
+## 
+## Coefficients:
+##            (Intercept)           pay_type_bin            pickup_hour 
+##            1.794247102           -2.756836660            0.006003461 
+##        passenger_count          trip_distance TrafficTimeBins_AMRush 
+##           -0.003104117            0.282365215           -0.056687330 
+##  TrafficTimeBins_Night TrafficTimeBins_PMRush 
+##           -0.160170178            0.000000000 
+## 
+## R-Squared: 0.6138
+## Root Mean Squared Error: 1.391
+
# Predict on test data and keep predictions in Spark context
+predictedVals <- sdf_predict(fit, newdata =  partitions$test)
+predictedValsSampled <- sdf_sample(predictedVals, fraction = 0.1, replacement = FALSE)
+predictedDF <- as.data.frame(predictedValsSampled)
+
+# Evaluate and plot predictions (R-sqr)
+Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr;
+
## [1] 0.6137143
+
# Sample predictions for plotting
+predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),]
+
+# Plot actual vs. predicted tip amounts
+lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled)
+ggplot(predictedDFSampled, aes(tip_amount, prediction)) + 
+  geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + 
+  geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], 
+                  intercept = summary(lm_model)$coefficients[1,1]), 
+              color = "red")
+

+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 39.43061 secs
+
+
+

6.2 Create Random Forest Model

+

Create a random forest model using training data, and evaluate on test data-set

+
starttime <- Sys.time();
+
+# Fit Random Forest regression model
+fit <- partitions$training %>% 
+          ml_random_forest(response = "tip_amount",
+                           features = c("pay_type_bin", "fare_amount", "pickup_hour", 
+                                        "passenger_count",  "trip_distance", 
+                                        "TrafficTimeBins"), 
+                           max.bins = 32L, max.depth = 5L, num.trees = 25L)
+
+# Show summary of fitted Random Forest model
+summary(fit)
+
##                             Length Class      Mode       
+## features                     8     -none-     character  
+## response                     1     -none-     character  
+## max.bins                     1     -none-     numeric    
+## max.depth                    1     -none-     numeric    
+## num.trees                    1     -none-     numeric    
+## feature.importances          8     -none-     numeric    
+## trees                       25     -none-     list       
+## data                         2     spark_jobj environment
+## ml.options                   6     ml_options list       
+## categorical.transformations  1     -none-     environment
+## model.parameters             5     -none-     list       
+## .call                        7     -none-     call       
+## .model                       2     spark_jobj environment
+
# Get feature importance of RF model
+feature_importance <- ml_tree_feature_importance(sp, fit) %>%
+    mutate(importance = as.numeric(levels(importance))[importance]) %>%
+    mutate(feature = as.character(feature));
+
+plot1 <- feature_importance %>%
+  ggplot(aes(reorder(feature, importance), importance)) + 
+  geom_bar(stat = "identity", fill='darkgreen') + coord_flip() + xlab("") +
+  ggtitle("Feature Importance")
+
+
+# Predict on test data and keep predictions in Spark context
+predictedVals <- sdf_predict(fit, newdata =  partitions$test)
+predictedValsSampled <- sdf_sample(predictedVals, fraction = 0.1, replacement = FALSE)
+predictedDF <- as.data.frame(predictedValsSampled)
+
+# Evaluate and plot predictions (R-sqr)
+Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr;
+
## [1] 0.7731771
+
# Sample predictions for plotting
+predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),]
+
+# Plot
+lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled)
+plot2 <- ggplot(predictedDFSampled, aes(tip_amount, prediction)) + 
+  geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + 
+  geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], 
+                  intercept = summary(lm_model)$coefficients[1,1]), 
+              color = "red")
+
+grid.arrange(plot1, plot2, ncol=2)
+

+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 48.3148 secs
+
+
+

6.3 Create Gradient Boosted Tree Model

+

Create a gradient boosted tree model using training data, and evaluate on test data-set

+
starttime <- Sys.time();
+
+# Fit Gradient Boosted Tree regression model
+fit <- partitions$training %>% 
+        ml_gradient_boosted_trees(tip_amount ~ pay_type_bin + pickup_hour + 
+                                    passenger_count + trip_distance + 
+                                    TrafficTimeBins, 
+                                  max.bins = 32L, max.depth = 5L, 
+                                  type = "regression")
+
+# Show summary of fitted Random Forest model
+summary(fit)
+
##                             Length Class      Mode       
+## features                     7     -none-     character  
+## response                     1     -none-     character  
+## max.bins                     1     -none-     numeric    
+## max.depth                    1     -none-     numeric    
+## trees                       20     -none-     list       
+## data                         2     spark_jobj environment
+## ml.options                   6     ml_options list       
+## categorical.transformations  1     -none-     environment
+## model.parameters             5     -none-     list       
+## .call                        6     -none-     call       
+## .model                       2     spark_jobj environment
+
# Get feature importance of GBT model
+feature_importance <- ml_tree_feature_importance(sp, fit) %>%
+    mutate(importance = as.numeric(levels(importance))[importance]) %>%
+    mutate(feature = as.character(feature));
+
+plot1 <- feature_importance %>%
+  ggplot(aes(reorder(feature, importance), importance)) + 
+  geom_bar(stat = "identity", fill='darkgreen') + coord_flip() + xlab("") +
+  ggtitle("Feature Importance")
+
+# Predict on test data and keep predictions in Spark context
+predictedVals <- sdf_predict(fit, newdata =  partitions$test)
+predictedValsSampled <- sdf_sample(predictedVals, fraction = 0.1, replacement = FALSE)
+predictedDF <- as.data.frame(predictedValsSampled)
+
+# Evaluate and plot predictions (R-sqr)
+Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr;
+
## [1] 0.7606691
+
# Sample predictions for plotting
+predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),]
+
+# Plot
+lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled)
+plot2 <- ggplot(predictedDFSampled, aes(tip_amount, prediction)) + 
+  geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + 
+  geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], 
+                  intercept = summary(lm_model)$coefficients[1,1]), 
+              color = "red")
+
+grid.arrange(plot1, plot2, ncol=2)
+

+
endtime <- Sys.time();
+print (endtime - starttime);
+
## Time difference of 1.078493 mins
+
+
+
+

7 Uncache objects and disconnect from spark

+
###########################################
+# UNCACHE TABLES
+###########################################
+tbl_uncache(sp, "joined_table")
+
+###########################################
+# DISCONNECT SPARK CONNECTION
+###########################################
+spark_disconnect(sp)
+
+
+
+


+
+
+

8 Summary

+

The examples shown here can be adopted to fit other data exploration and modeling scenarios having different data-types or prediction tasks (e.g. classification)

+
+ + + +
+
+ +
+ + + + + + + + diff --git a/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forHDICLuster.Rmd b/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forHDICLuster.Rmd new file mode 100644 index 00000000..345cc04f --- /dev/null +++ b/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forHDICLuster.Rmd @@ -0,0 +1,319 @@ +--- +title: "Using sparklyr with 2013 NYCTaxi Data: Featurization, modeling, and evaluation" +date: "`r format(Sys.time(), '%B %d, %Y')`" +author: "Algorithms and Data Science & R Server Teams, Microsoft Data Group" +output: + html_document: + fig_caption: yes + fig_height: 4 + fig_width: 4 + highlight: haddock + keep_md: yes + number_sections: yes + theme: journal + toc: yes + toc_float: yes +runtime: knit +--- + +
+#Introduction +This Markdown document shows the use of sparklyr for feature engineering and creating machine learning models. The data used for this exercise is the public NYC Taxi Trip and Fare data-set (2013, sampled to about ~13 million rows) available from: http://www.andresmh.com/nyctaxitrips. Data for this exercise can be downloaded from the public blob (see below). The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here. + +sparklyr provides bindings to Spark’s distributed machine learning library. In particular, sparklyr allows you to access the machine learning routines provided by the spark.ml package. Together with sparklyr’s dplyr interface, you can easily create and tune machine learning workflows on Spark, orchestrated entirely within R. + +Where necessary, small amounts of data is brought to the local data frames for plotting and visualization. +
+
+ +
+ +#Creating spark connection and loading packages +```{r Load Packages, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# LOAD LIBRARIES FROM SPECIFIED PATH +########################################### +.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) +library(SparkR) +library(rmarkdown) +library(knitr) +library(sparklyr) +library(dplyr) +library(DBI) +library(gridExtra) +library(ggplot2) + +########################################### +## CREATE SPARKLYR SPARK CONNECTION +########################################### +sp <- spark_connect(master = "yarn-client") + +########################################### +## SPECIFY BASE HDFS DIRECTORY +########################################### +fullDataDir <- "/HdiSamples/HdiSamples/NYCTaxi" +``` +
+
+
+ +#Read joined trip-fare data and cache in memory +The taxi and fare files were previously joined using SparkR SQL and down-sampled to about 10% of the full dataset. +```{r Load data in sparklyr dataframe, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# LOAD SAMPLED JOINED TAXI DATA FROM HDFS, CACHE +########################################### +starttime <- Sys.time() + +joinedFilePath <- file.path(fullDataDir, "NYCjoinedParquetSubset") +joinedDF <- spark_read_parquet(sp, name = "joined_table", + path = joinedFilePath, memory = TRUE, + overwrite = TRUE) +tbl_cache(sp, "joined_table", force=TRUE) +head(joinedDF, 3) + +########################################### +# SAMPLE AND CACHE IN MEMORY +########################################### +joinedDFSampl <- sdf_sample(joinedDF, fraction = 0.1) + +########################################### +# SHOW THE NUMBER OF OBSERVATIONS IN DATA +########################################### +count(joinedDFSampl) + +endtime <- Sys.time() +print (endtime - starttime) +``` + +
+#Transformations using sparklyr functions +Spark provides feature transformers, faciliating many common transformations of data within in a Spark DataFrame, and sparklyr exposes these within the ft_* family of functions. These routines generally take one or more input columns, and generate a new output column formed as a transformation of those columns. Here, we show the use of two such functions to bucketize (categorize) or binarize features. Payment type (CSH or CRD) is binarized using string-indexer and binerizer functions. And, traffic-time bins is bucketized using the bucketizer function. +```{r Using ft_ functions for feature transformation, message=FALSE, warning=FALSE, echo=TRUE} +########################################### +# CREATE TRANSFORMED FEATURES, BINARIZE PAYMENT-TYPE +########################################### +starttime <- Sys.time() + +# Binarizer +joinedDF2 <- joinedDFSampl %>% ft_string_indexer(input_col = 'payment_type', + output_col = 'payment_ind') %>% + ft_binarizer(input_col = 'payment_ind', + output_col = 'pay_type_bin', + threshold = 0.5) +head(joinedDF2) + +endtime <- Sys.time() +print (endtime - starttime) +``` + +
+#Create train-test partitions +Data can be partitioned into training and testing using the sdf_partition function. +```{r Partition data into train/test, message=FALSE, warning=FALSE, echo=TRUE} +starttime <- Sys.time() + +########################################### +# CREATE TRAIN/TEST PARTITIONS +########################################### +partitions <- joinedDF2 %>% sdf_partition(training = 0.70, + test = 0.30, seed = 123) + +endtime <- Sys.time() +print (endtime - starttime) +``` +
+ +#Create ML models +Spark’s machine learning library can be accessed from sparklyr through the ml_* family of functions. Here we create ML models for the prediction of tip-amount for taxi trips. + +##Creating Elastic Net model +Create a elastic net model using training data, and evaluate on test data-set +```{r Elastic net modeo, message=FALSE, warning=FALSE, echo=TRUE, fig.width=5, fig.height=4} +starttime <- Sys.time() + +########################################### +# FIT ELASTIC NET REGRESSION MODEL +########################################### +fit <- partitions$training %>% + ml_linear_regression(tip_amount ~ + pay_type_bin + pickup_hour + passenger_count + + trip_distance + TrafficTimeBins, + alpha = 0.5, lambda = 0.01) + +########################################### +# SHOW MODEL SUMMARY +########################################### +summary(fit) + +########################################### +# PREDICT ON TEST DATA, AND EVALUATE +########################################### +predictedVals <- sdf_predict(fit, newdata = partitions$test) +predictedValsSampled <- sdf_sample(x=predictedVals, + fraction=0.01, replacement=FALSE, seed=123) +predictedDF <- as.data.frame(predictedValsSampled) + +Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr; + +# Sample predictions for plotting +predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),] + +# Plot actual vs. predicted tip amounts +lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled) +ggplot(predictedDFSampled, aes(tip_amount, prediction)) + + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + + geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], + intercept = summary(lm_model)$coefficients[1,1]), + color = "red") + +########################################### +# SAVE PREDICTIONS TO A CSV FILE IN HDFS +########################################### +#sparklyRPredictionsPath <- file.path(fullDataDir, "sparklyRElasticNetPredictions") +#spark_write_csv(predictedVals, sparklyRPredictionsPath) + +endtime <- Sys.time() +print (endtime - starttime) +``` + +##Creating Random Forest Model +Create a random forest model using training data, and evaluate on test data-set +```{r Random forest model, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=5} +starttime <- Sys.time() + +########################################### +# FIT RANDOM FOREST REGRESSION MODEL +########################################### +fit <- ml_random_forest(x=partitions$training, + response = "tip_amount", + features = c("pay_type_bin", "fare_amount", + "pickup_hour", "passenger_count", + "trip_distance", "TrafficTimeBins"), + max.bins = 32L, max.depth = 4L, num.trees = 10L) + +########################################### +# SHOW SUMMARY OF RANDOM FOREST MODEL +########################################### +summary(fit) + +########################################### +# PLOT FEATURE IMPORTANCE +########################################### +feature_importance <- ml_tree_feature_importance(sp, fit) %>% + mutate(importance = as.numeric(levels(importance))[importance]) %>% + mutate(feature = as.character(feature)); + +plot1 <- feature_importance %>% + ggplot(aes(reorder(feature, importance), importance)) + + geom_bar(stat = "identity", fill = 'darkgreen') + coord_flip() + xlab("") + + ggtitle("Feature Importance") + + +########################################### +# PREDICT ON TEST SET AND EVALUATE +########################################### +predictedVals <- sdf_predict(fit, newdata = partitions$test) +predictedValsSampled <- sdf_sample(x=predictedVals, + fraction=0.01, replacement=FALSE, seed=123) +predictedDF <- as.data.frame(predictedValsSampled) + +# Evaluate and plot predictions (R-sqr) +Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr; + +# Sample predictions for plotting +predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),] + +# Plot +lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled) +plot2 <- ggplot(predictedDFSampled, aes(tip_amount, prediction)) + + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + + geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], + intercept = summary(lm_model)$coefficients[1,1]), color = "red") + +grid.arrange(plot1, plot2, ncol=2) + +endtime <- Sys.time() +print (endtime - starttime) +``` + + +##Creating Gradient Boosted Tree Model +Create a gradient boosted tree model using training data, and evaluate on test data-set +```{r Boosted tree model, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=5} +starttime <- Sys.time() + +########################################### +# FIT GRADIENT BOOSTED TREE REGRESSION MODEL +########################################### +fit <- partitions$training %>% + ml_gradient_boosted_trees(tip_amount ~ + pay_type_bin + pickup_hour + passenger_count + + trip_distance + TrafficTimeBins, + max.bins = 32L, max.depth = 4L, type = "regression") + +########################################### +# SHOW SUMMARY OF MODEL +########################################### +summary(fit) + +########################################### +# PLOT FEATURE IMPORTANCE OF GBT MODEL +########################################### +feature_importance <- ml_tree_feature_importance(sp, fit) %>% + mutate(importance = as.numeric(levels(importance))[importance]) %>% + mutate(feature = as.character(feature)); + +plot1 <- feature_importance %>% + ggplot(aes(reorder(feature, importance), importance)) + + geom_bar(stat = "identity", fill = 'darkgreen') + coord_flip() + xlab("") + + ggtitle("Feature Importance") + +########################################### +# PREDICT ON TEST SET AND EVALUATE +########################################### +predictedVals <- sdf_predict(fit, newdata = partitions$test) +predictedValsSampled <- sdf_sample(x=predictedVals, + fraction=0.01, replacement=FALSE, seed=123) +predictedDF <- as.data.frame(predictedValsSampled) + +# Evaluate and plot predictions (R-sqr) +Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr; + +# Sample predictions for plotting +predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),] + +# Plot +lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled) +plot2 <- ggplot(predictedDFSampled, aes(tip_amount, prediction)) + + geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + + geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], + intercept = summary(lm_model)$coefficients[1,1]), + color = "red") + +grid.arrange(plot1, plot2, ncol=2) + +endtime <- Sys.time() +print (endtime - starttime) +``` + +#Uncache objects, and disconnect Spark session +```{r Uncache objects and disconnect Spark, message=FALSE, warning=FALSE, echo=TRUE, fig.width=10, fig.height=5} +########################################### +# UNCACHE TABLES +########################################### +tbl_uncache(sp, "joined_table") + +########################################### +# DISCONNECT SPARK CONNECTION +########################################### +spark_disconnect(sp) +``` + +
+
+
+
+ +#Summary +The examples shown here can be adopted to fit other data exploration and modeling scenarios having different data-types or prediction tasks (e.g. classification) \ No newline at end of file diff --git a/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forHDICLuster.html b/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forHDICLuster.html new file mode 100644 index 00000000..3cd4ff94 --- /dev/null +++ b/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forHDICLuster.html @@ -0,0 +1,643 @@ + + + + + + + + + + + + + + + +Using sparklyr with 2013 NYCTaxi Data: Featurization, modeling, and evaluation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+
+
+
+ +
+ + + + + + + +
+
+

1 Introduction

+

This Markdown document shows the use of sparklyr for feature engineering and creating machine learning models. The data used for this exercise is the public NYC Taxi Trip and Fare data-set (2013, sampled to about ~13 million rows) available from: http://www.andresmh.com/nyctaxitrips. Data for this exercise can be downloaded from the public blob (see below). The data can be uploaded to the blob (or other storage) attached to your HDInsight cluster (HDFS) and used as input into the scripts shown here.

+

sparklyr provides bindings to Spark’s distributed machine learning library. In particular, sparklyr allows you to access the machine learning routines provided by the spark.ml package. Together with sparklyr’s dplyr interface, you can easily create and tune machine learning workflows on Spark, orchestrated entirely within R.

+Where necessary, small amounts of data is brought to the local data frames for plotting and visualization. +
+


+


+
+
+

2 Creating spark connection and loading packages

+
###########################################
+# LOAD LIBRARIES FROM SPECIFIED PATH
+###########################################
+.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths()))
+library(SparkR)
+library(rmarkdown)
+library(knitr)
+library(sparklyr)
+library(dplyr)
+library(DBI)
+library(gridExtra)
+library(ggplot2)
+
+###########################################
+## CREATE SPARKLYR SPARK CONNECTION
+###########################################
+sp <- spark_connect(master = "yarn-client")
+
+###########################################
+## SPECIFY BASE HDFS DIRECTORY
+###########################################
+fullDataDir <- "/HdiSamples/HdiSamples/NYCTaxi"
+
+



+
+
+

3 Read joined trip-fare data and cache in memory

+

The taxi and fare files were previously joined using SparkR SQL and down-sampled to about 10% of the full dataset.

+
###########################################
+# LOAD SAMPLED JOINED TAXI DATA FROM HDFS, CACHE
+###########################################
+starttime <- Sys.time()
+
+joinedFilePath <- file.path(fullDataDir, "NYCjoinedParquetSubset")
+joinedDF <- spark_read_parquet(sp, name = "joined_table", 
+                               path = joinedFilePath, memory = TRUE, 
+                               overwrite = TRUE)
+tbl_cache(sp, "joined_table", force=TRUE)
+head(joinedDF, 3)
+
## Source:   query [3 x 9]
+## Database: spark connection master=yarn-client app=sparklyr local=FALSE
+## 
+##   payment_type pickup_hour fare_amount tip_amount passenger_count
+##          <chr>       <int>       <dbl>      <dbl>           <int>
+## 1          CSH           0         6.5       0.00               1
+## 2          CRD           8        11.5       2.40               1
+## 3          CRD           7         7.0       1.87               1
+## # ... with 4 more variables: trip_distance <dbl>, trip_time_in_secs <int>,
+## #   TrafficTimeBins <chr>, tipped <int>
+
###########################################
+# SAMPLE AND CACHE IN MEMORY
+###########################################
+joinedDFSampl <- sdf_sample(joinedDF, fraction = 0.1)
+
+###########################################
+# SHOW THE NUMBER OF OBSERVATIONS IN DATA 
+###########################################
+count(joinedDFSampl)
+
## Source:   query [1 x 1]
+## Database: spark connection master=yarn-client app=sparklyr local=FALSE
+## 
+##          n
+##      <dbl>
+## 1 13475544
+
endtime <- Sys.time()
+print (endtime - starttime)
+
## Time difference of 47.59088 secs
+
+
+
+

4 Transformations using sparklyr functions

+

Spark provides feature transformers, faciliating many common transformations of data within in a Spark DataFrame, and sparklyr exposes these within the ft_* family of functions. These routines generally take one or more input columns, and generate a new output column formed as a transformation of those columns. Here, we show the use of two such functions to bucketize (categorize) or binarize features. Payment type (CSH or CRD) is binarized using string-indexer and binerizer functions. And, traffic-time bins is bucketized using the bucketizer function.

+
###########################################
+# CREATE TRANSFORMED FEATURES, BINARIZE PAYMENT-TYPE
+###########################################
+starttime <- Sys.time()
+
+# Binarizer
+joinedDF2 <- joinedDFSampl %>% ft_string_indexer(input_col = 'payment_type', 
+                                            output_col = 'payment_ind') %>% 
+                                            ft_binarizer(input_col = 'payment_ind', 
+                                                        output_col = 'pay_type_bin', 
+                                                        threshold = 0.5)
+head(joinedDF2)
+
## Source:   query [6 x 11]
+## Database: spark connection master=yarn-client app=sparklyr local=FALSE
+## 
+##   payment_type pickup_hour fare_amount tip_amount passenger_count
+##          <chr>       <int>       <dbl>      <dbl>           <int>
+## 1          CRD           0         6.0        2.0               1
+## 2          CRD           1        10.0        3.3               1
+## 3          CRD          18         6.5        1.0               1
+## 4          CRD          19        52.0       10.4               1
+## 5          CRD           9         7.0        1.4               3
+## 6          CSH           3         8.0        0.0               1
+## # ... with 6 more variables: trip_distance <dbl>, trip_time_in_secs <int>,
+## #   TrafficTimeBins <chr>, tipped <int>, payment_ind <dbl>,
+## #   pay_type_bin <dbl>
+
endtime <- Sys.time()
+print (endtime - starttime)
+
## Time difference of 5.035935 secs
+
+
+
+

5 Create train-test partitions

+

Data can be partitioned into training and testing using the sdf_partition function.

+
starttime <- Sys.time()
+
+###########################################
+# CREATE TRAIN/TEST PARTITIONS
+###########################################
+partitions <- joinedDF2 %>% sdf_partition(training = 0.70, 
+                                          test = 0.30, seed = 123)
+
+endtime <- Sys.time()
+print (endtime - starttime)
+
## Time difference of 0.1593895 secs
+
+
+
+

6 Create ML models

+

Spark’s machine learning library can be accessed from sparklyr through the ml_* family of functions. Here we create ML models for the prediction of tip-amount for taxi trips.

+
+

6.1 Creating Elastic Net model

+

Create a elastic net model using training data, and evaluate on test data-set

+
starttime <- Sys.time()
+
+###########################################
+# FIT ELASTIC NET REGRESSION MODEL
+###########################################
+fit <- partitions$training %>% 
+      ml_linear_regression(tip_amount ~ 
+                          pay_type_bin + pickup_hour + passenger_count + 
+                          trip_distance + TrafficTimeBins, 
+                          alpha = 0.5, lambda = 0.01)
+
+###########################################
+# SHOW MODEL SUMMARY
+###########################################
+summary(fit)
+
## Call: ml_linear_regression(., tip_amount ~ pay_type_bin + pickup_hour + passenger_count + trip_distance + TrafficTimeBins, alpha = 0.5, lambda = 0.01)
+## 
+## Deviance Residuals: (approximate):
+##      Min       1Q   Median       3Q      Max 
+## -9.80195 -0.56330  0.08497  0.48761 12.27523 
+## 
+## Coefficients:
+##            (Intercept)           pay_type_bin            pickup_hour 
+##            1.713051237           -2.600603336            0.004619677 
+##        passenger_count          trip_distance TrafficTimeBins_AMRush 
+##           -0.006618037            0.272838003           -0.029416823 
+##  TrafficTimeBins_Night TrafficTimeBins_PMRush 
+##           -0.126742202            0.000000000 
+## 
+## R-Squared: 0.6139
+## Root Mean Squared Error: 1.304
+
###########################################
+# PREDICT ON TEST DATA, AND EVALUATE
+###########################################
+predictedVals <- sdf_predict(fit, newdata =  partitions$test)
+predictedValsSampled <- sdf_sample(x=predictedVals, 
+                                   fraction=0.01, replacement=FALSE, seed=123)
+predictedDF <- as.data.frame(predictedValsSampled)
+
+Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr;
+
## [1] 0.6154944
+
# Sample predictions for plotting
+predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),]
+
+# Plot actual vs. predicted tip amounts
+lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled)
+ggplot(predictedDFSampled, aes(tip_amount, prediction)) + 
+  geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + 
+  geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], 
+                  intercept = summary(lm_model)$coefficients[1,1]), 
+              color = "red")
+

+
###########################################
+# SAVE PREDICTIONS TO A CSV FILE IN HDFS
+###########################################
+#sparklyRPredictionsPath <- file.path(fullDataDir, "sparklyRElasticNetPredictions")
+#spark_write_csv(predictedVals, sparklyRPredictionsPath)
+
+endtime <- Sys.time()
+print (endtime - starttime)
+
## Time difference of 1.386464 mins
+
+
+

6.2 Creating Random Forest Model

+

Create a random forest model using training data, and evaluate on test data-set

+
starttime <- Sys.time()
+
+###########################################
+# FIT RANDOM FOREST REGRESSION MODEL
+###########################################
+fit <- ml_random_forest(x=partitions$training, 
+                        response = "tip_amount", 
+                        features = c("pay_type_bin", "fare_amount", 
+                                     "pickup_hour", "passenger_count",  
+                                     "trip_distance", "TrafficTimeBins"), 
+                        max.bins = 32L, max.depth = 4L, num.trees = 10L)
+
+###########################################
+# SHOW SUMMARY OF RANDOM FOREST MODEL
+###########################################
+summary(fit)
+
##                             Length Class      Mode       
+## features                     8     -none-     character  
+## response                     1     -none-     character  
+## max.bins                     1     -none-     numeric    
+## max.depth                    1     -none-     numeric    
+## num.trees                    1     -none-     numeric    
+## feature.importances          8     -none-     numeric    
+## trees                       10     -none-     list       
+## data                         2     spark_jobj environment
+## ml.options                   6     ml_options list       
+## categorical.transformations  1     -none-     environment
+## model.parameters             5     -none-     list       
+## .call                        7     -none-     call       
+## .model                       2     spark_jobj environment
+
###########################################
+# PLOT FEATURE IMPORTANCE
+###########################################
+feature_importance <- ml_tree_feature_importance(sp, fit) %>%
+    mutate(importance = as.numeric(levels(importance))[importance]) %>%
+    mutate(feature = as.character(feature));
+
+plot1 <- feature_importance %>%
+  ggplot(aes(reorder(feature, importance), importance)) + 
+  geom_bar(stat = "identity", fill = 'darkgreen') + coord_flip() + xlab("") +
+  ggtitle("Feature Importance")
+
+
+###########################################
+# PREDICT ON TEST SET AND EVALUATE
+###########################################
+predictedVals <- sdf_predict(fit, newdata =  partitions$test)
+predictedValsSampled <- sdf_sample(x=predictedVals, 
+                                   fraction=0.01, replacement=FALSE, seed=123)
+predictedDF <- as.data.frame(predictedValsSampled)
+
+# Evaluate and plot predictions (R-sqr)
+Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr;
+
## [1] 0.7404992
+
# Sample predictions for plotting
+predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),]
+
+# Plot
+lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled)
+plot2 <- ggplot(predictedDFSampled, aes(tip_amount, prediction)) + 
+  geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + 
+  geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], 
+                  intercept = summary(lm_model)$coefficients[1,1]), color = "red")
+
+grid.arrange(plot1, plot2, ncol=2)
+

+
endtime <- Sys.time()
+print (endtime - starttime)
+
## Time difference of 1.543247 mins
+
+
+

6.3 Creating Gradient Boosted Tree Model

+

Create a gradient boosted tree model using training data, and evaluate on test data-set

+
starttime <- Sys.time()
+
+###########################################
+# FIT GRADIENT BOOSTED TREE REGRESSION MODEL
+###########################################
+fit <- partitions$training %>% 
+       ml_gradient_boosted_trees(tip_amount ~ 
+                                pay_type_bin + pickup_hour + passenger_count + 
+                                trip_distance + TrafficTimeBins, 
+                                max.bins = 32L, max.depth = 4L, type = "regression")
+
+###########################################
+# SHOW SUMMARY OF MODEL
+###########################################
+summary(fit)
+
##                             Length Class      Mode       
+## features                     7     -none-     character  
+## response                     1     -none-     character  
+## max.bins                     1     -none-     numeric    
+## max.depth                    1     -none-     numeric    
+## trees                       20     -none-     list       
+## data                         2     spark_jobj environment
+## ml.options                   6     ml_options list       
+## categorical.transformations  1     -none-     environment
+## model.parameters             5     -none-     list       
+## .call                        6     -none-     call       
+## .model                       2     spark_jobj environment
+
###########################################
+# PLOT FEATURE IMPORTANCE OF GBT MODEL
+###########################################
+feature_importance <- ml_tree_feature_importance(sp, fit) %>%
+    mutate(importance = as.numeric(levels(importance))[importance]) %>%
+    mutate(feature = as.character(feature));
+
+plot1 <- feature_importance %>%
+  ggplot(aes(reorder(feature, importance), importance)) + 
+  geom_bar(stat = "identity", fill = 'darkgreen') + coord_flip() + xlab("") +
+  ggtitle("Feature Importance")
+
+###########################################
+# PREDICT ON TEST SET AND EVALUATE
+###########################################
+predictedVals <- sdf_predict(fit, newdata =  partitions$test)
+predictedValsSampled <- sdf_sample(x=predictedVals, 
+                                   fraction=0.01, replacement=FALSE, seed=123)
+predictedDF <- as.data.frame(predictedValsSampled)
+
+# Evaluate and plot predictions (R-sqr)
+Rsqr = cor(predictedDF$tip_amount, predictedDF$prediction)^2; Rsqr;
+
## [1] 0.7688964
+
# Sample predictions for plotting
+predictedDFSampled <- predictedDF[base::sample(1:nrow(predictedDF), 1000),]
+
+# Plot
+lm_model <- lm(prediction ~ tip_amount, data = predictedDFSampled)
+plot2 <- ggplot(predictedDFSampled, aes(tip_amount, prediction)) + 
+  geom_point(col='darkgreen', alpha=0.3, pch=19, cex=2) + 
+  geom_abline(aes(slope = summary(lm_model)$coefficients[2,1], 
+                  intercept = summary(lm_model)$coefficients[1,1]), 
+              color = "red")
+
+grid.arrange(plot1, plot2, ncol=2)
+

+
endtime <- Sys.time()
+print (endtime - starttime)
+
## Time difference of 2.464881 mins
+
+
+
+

7 Uncache objects, and disconnect Spark session

+
###########################################
+# UNCACHE TABLES
+###########################################
+tbl_uncache(sp, "joined_table")
+
+###########################################
+# DISCONNECT SPARK CONNECTION
+###########################################
+spark_disconnect(sp)
+
+
+
+


+
+
+

8 Summary

+

The examples shown here can be adopted to fit other data exploration and modeling scenarios having different data-types or prediction tasks (e.g. classification)

+
+ + + +
+
+ +
+ + + + + + + + diff --git a/Misc/StrataSanJose2017/Performance_Comparison/Readme.md b/Misc/StrataSanJose2017/Performance_Comparison/Readme.md new file mode 100644 index 00000000..c9c3ba35 --- /dev/null +++ b/Misc/StrataSanJose2017/Performance_Comparison/Readme.md @@ -0,0 +1 @@ +This folder contains the scripts for a comparison among open source glm, Microsoft R Server 9.0 on Spark, SparkR 2.0, sparklyr and h2o (rsparkling) packages. diff --git a/Misc/StrataSanJose2017/Performance_Comparison/SparkR.R b/Misc/StrataSanJose2017/Performance_Comparison/SparkR.R new file mode 100644 index 00000000..93e25bd4 --- /dev/null +++ b/Misc/StrataSanJose2017/Performance_Comparison/SparkR.R @@ -0,0 +1,105 @@ +#### execute .R scripts in command line for multiple experiments. +# Rscript SparkR.R "airline_1MM.csv" "~/results/sparkR/sparkR_1MM.csv" +# Rscript SparkR.R "airline_2MM.csv" "~/results/sparkR/sparkR_2MM.csv" +# Rscript SparkR.R "airline_5MM.csv" "~/results/sparkR/sparkR_5MM.csv" +# Rscript SparkR.R "airline_10MM.csv" "~/results/sparkR/sparkR_10MM.csv" +# Rscript SparkR.R "airline_20MM.csv" "~/results/sparkR/sparkR_20MM.csv" +# Rscript SparkR.R "airline_50MM.csv" "~/results/sparkR/sparkR_50MM.csv" +# Rscript SparkR.R "airline_100MM.csv" "~/results/sparkR/sparkR_100MM.csv" +# Rscript SparkR.R "airline_200MM.csv" "~/results/sparkR/sparkR_200MM.csv" +# Rscript SparkR.R "airline_400MM.csv" "~/results/sparkR/sparkR_400MM.csv" +# Rscript SparkR.R "airline_800MM.csv" "~/results/sparkR/sparkR_800MM.csv" +# Rscript SparkR.R "airline_1000MM.csv" "~/results/sparkR/sparkR_1000MM.csv" + + + +#!/usr/bin/env Rscript +args = commandArgs(trailingOnly=TRUE) +options(warn=-1) + +# Inputs: +# fileName <- "airline_1MM.csv" +fileName <- args[1] + + +# call libraries +.libPaths(c(file.path(Sys.getenv("SPARK_HOME"), "R", "lib"), .libPaths())) +library("SparkR") +library("dplyr") + + +# part 1: reading data +pt1 <- proc.time() +# connect R to a Spark cluster +sc <- sparkR.init(master = "yarn-client", + sparkPackages = "com.databricks:spark-csv_2.10:1.3.0", + sparkEnvir = list(spark.dynamicAllocation.enabled = "true", + spark.shuffle.service.enabled = "true", + spark.dynamicAllocation.minExecutors = "1")) +sqlContext <- sparkRSQL.init(sc) + + +# import data +inputPath <- file.path("wasb://@.blob.core.windows.net", fileName) +# define data schema +airline_sparkr <- read.df(sqlContext, path = inputPath, + header = "true", source = "com.databricks.spark.csv", + inferSchema = "true") +pt2 <- proc.time() +print("FINISH LOADING DATA...") + + + +# part 2: data transformation on CRSDepTime +airline_sparkr$CRSDepTime <- floor(airline_sparkr$CRSDepTime / 100) +pt3 <- proc.time() +print("FINISHED DATA TRANSFORMATION...") + + + +# part 3: split train/test +train <- SparkR::sample_frac(airline_sparkr, withReplacement=FALSE, fraction=0.75, seed=123) +test <- SparkR::except(airline_sparkr, train) +pt4 <- proc.time() +print("FINISHED SPLITTING DATA...") + + + +# part 4: fit model +# GLM model +model_sparkr <- glm(formula = IsArrDelayed ~ Month+DayofMonth+DayOfWeek+CRSDepTime+Distance, + data = train, family = "binomial") +pt5 <- proc.time() +print("FINISHED FITTING MODEL...") + + + +# part 5: predict on test +p <- SparkR::select(SparkR::predict(model_sparkr, test), "prediction") +outFile <- file.path("wasb://@.blob.core.windows.net", "sparkR", paste0(gsub(".csv", "", fileName), "_pred")) +# prepare for overwrite +if (rxHadoopFileExists(outFile) == TRUE) { + rxHadoopRemoveDir(outFile) +} +write.parquet(p, outFile) +pt6 <- proc.time() +print("FINISHED PREDICTION...") + +# stop Spark connection +sparkR.session.stop() + + + +# part 6: output results +results <- data.frame("number of rows" = fileName, + "load data" = (pt2-pt1)[[3]], + "tranform feature" = (pt3-pt2)[[3]], + "split data" = (pt4-pt3)[[3]], + "fit model" = (pt5-pt4)[[3]], + "prediction" = (pt6-pt5)[[3]], + "total" = (pt6-pt1)[[3]]) + + +# "~/results/sparkR/sparkR_1MM.csv" +write.csv(results, args[2], row.names = FALSE) +print("FINISHED WRITTING OUTPUTS...") diff --git a/Misc/StrataSanJose2017/Performance_Comparison/glm.R b/Misc/StrataSanJose2017/Performance_Comparison/glm.R new file mode 100644 index 00000000..03ae86b2 --- /dev/null +++ b/Misc/StrataSanJose2017/Performance_Comparison/glm.R @@ -0,0 +1,71 @@ +#### execute .R scripts in command line for multiple experiments. +# Rscript glm.R "~/datasets/airline_1MM.csv" "~/results/glm/glm_1MM.csv" +# Rscript glm.R "~/datasets/airline_2MM.csv" "~/results/glm/glm_2MM.csv" +# Rscript glm.R "~/datasets/airline_5MM.csv" "~/results/glm/glm_5MM.csv" +# Rscript glm.R "~/datasets/airline_10MM.csv" "~/results/glm/glm_10MM.csv" +# Rscript glm.R "~/datasets/airline_20MM.csv" "~/results/glm/glm_20MM.csv" +# Rscript glm.R "~/datasets/airline_50MM.csv" "~/results/glm/glm_50MM.csv" +# Rscript glm.R "~/datasets/airline_100MM.csv" "~/results/glm/glm_100MM.csv" + + + +#!/usr/bin/env Rscript +args = commandArgs(trailingOnly=TRUE) + +# Inputs: +# fileName <- "airline_1MM.csv" +fileName <- args[1] + + +# part 1: reading data +pt1 <- proc.time() +df <- read.csv(fileName, header = TRUE, sep = ",", na.strings = "NA") +pt2 <- proc.time() +print("FINISHED LOADING DATA...") + + +# part 2: data transformation on CRSDepTime +df$CRSDepTime <- floor(df$CRSDepTime / 100) +pt3 <- proc.time() +print("FINISHED DATA TRANSFORMATION...") + + +# part 3: split train/test +set.seed(123) +smp_size <- floor(0.75 * nrow(df)) +train_ind <- sample(seq_len(nrow(df)), size = smp_size) +train <- df[train_ind, ] +test <- df[-train_ind, ] +pt4 <- proc.time() +print("FINISHED SPLITTING DATA...") + + + +# part 4: fit model +# glm +model_glm <- glm(formula = IsArrDelayed ~ Month+DayofMonth+DayOfWeek+CRSDepTime+Distance, data = train, family=binomial(link='logit')) +pt5 <- proc.time() +print("FINISHED FITTING MODEL...") + + + +# part 5: predict on test +p <- predict(model_glm, newdata = test, type = "response") +pt6 <- proc.time() +print("FINISHED PREDICTION...") + + + +# part 6: output results +results <- data.frame("number of rows" = fileName, + "load data" = (pt2-pt1)[[3]], + "tranform feature" = (pt3-pt2)[[3]], + "split data" = (pt4-pt3)[[3]], + "fit model" = (pt5-pt4)[[3]], + "prediction" = (pt6-pt5)[[3]], + "total" = (pt6-pt1)[[3]]) + + +# "~/results/glm/glm_1MM.csv" +write.csv(results, args[2], row.names = FALSE) +print("FINISHED WRITTING OUTPUTS...") \ No newline at end of file diff --git a/Misc/StrataSanJose2017/Performance_Comparison/h2o.R b/Misc/StrataSanJose2017/Performance_Comparison/h2o.R new file mode 100644 index 00000000..3fe353af --- /dev/null +++ b/Misc/StrataSanJose2017/Performance_Comparison/h2o.R @@ -0,0 +1,145 @@ +# # The following two commands remove any previously installed H2O packages for R. +# if ("package:h2o" %in% search()) { detach("package:h2o", unload=TRUE) } +# if ("h2o" %in% rownames(installed.packages())) { remove.packages("h2o") } +# +# # Next, we download packages that H2O depends on. +# pkgs <- c("methods","statmod","stats","graphics","RCurl","jsonlite","tools","utils") +# for (pkg in pkgs) { +# if (! (pkg %in% rownames(installed.packages()))) { install.packages(pkg, repos = "http://cran.rstudio.com/") } +# } +# +# # Now we download, install and initialize the H2O package for R. +# install.packages("h2o", type="source", repos=(c("http://h2o-release.s3.amazonaws.com/h2o/rel-turing/10/R"))) +# install.packages("sparklyr") +# install.packages("devtools") +# library(devtools) +# devtools::install_github("h2oai/rsparkling", ref = "master") + + + +############################################################## + +#### execute .R scripts in command line for multiple experiments. +# Rscript h2o.R "airline_1MM.csv" "~/results/h2o/h2o_1MM.csv" +# Rscript h2o.R "airline_2MM.csv" "~/results/h2o/h2o_2MM.csv" +# Rscript h2o.R "airline_5MM.csv" "~/results/h2o/h2o_5MM.csv" +# Rscript h2o.R "airline_10MM.csv" "~/results/h2o/h2o_10MM.csv" +# Rscript h2o.R "airline_20MM.csv" "~/results/h2o/h2o_20MM.csv" +# Rscript h2o.R "airline_50MM.csv" "~/results/h2o/h2o_50MM.csv" +# Rscript h2o.R "airline_100MM.csv" "~/results/h2o/h2o_100MM.csv" +# Rscript h2o.R "airline_200MM.csv" "~/results/h2o/h2o_200MM.csv" +# Rscript h2o.R "airline_400MM.csv" "~/results/h2o/h2o_400MM.csv" +# Rscript h2o.R "airline_800MM.csv" "~/results/h2o/h2o_800MM.csv" +# Rscript h2o.R "airline_1000MM.csv" "~/results/h2o/h2o_1000MM.csv" + + + +#!/usr/bin/env Rscript +args = commandArgs(trailingOnly=TRUE) + +# Inputs: +# fileName <- "airline_1MM.csv" +fileName <- args[1] + + +# call libraries +library("sparklyr") +library("h2o") +options(rsparkling.sparklingwater.version = "2.0.3") # Using Sparkling Water 2.0.3 +library("rsparkling") + + +# part 1: reading data +pt1 <- proc.time() + +# connect to remote Spark clusters +config <- spark_config() +# spark config +config$spark.num.executors <- 11 +config$spark.executor.cores <- 5 +config$spark.executor.memory <- "34G" +config$spark.driver.memory <- "34G" +# sparkling water config +config$spark.ext.h2o.disable.ga <- "true" +config$spark.ext.h2o.client.log.dir <- "h2ologs" +config$spark.ext.h2o.client.verbose <- "true" +# internal h2o backend config +config$spark.ext.h2o.cloud.timeout <- 86400000 +config$spark.ext.h2o.node.log.dir <- "h2ologs" + +sc <- spark_connect(master = "yarn-client", config = config, version = "2.0.2") + +# create h2o context +h2o_context(sc, strict_version_check = FALSE) + +# import data +inputPath <- file.path("wasb://@.blob.core.windows.net", fileName) +airline_lyr <- spark_read_csv(sc, + name = gsub(".csv", "", fileName), + path = inputPath, + header = TRUE) + +# convert SparkDataFrame to h2oFrame +airline_h2o <- as_h2o_frame(sc, airline_lyr, strict_version_check = FALSE) +pt2 <- proc.time() +print("FINISH LOADING DATA...") + + + +# part 2: data transformation on CRSDepTime +airline_h2o$CRSDepTime <- floor(airline_h2o$CRSDepTime / 100) +pt3 <- proc.time() +print("FINISHED DATA TRANSFORMATION...") + + + +# part 3: split train/test +partitions <- h2o.splitFrame(airline_h2o, ratios = 0.75, seed = 123) +train <- partitions[[1]] +test <- partitions[[2]] +pt4 <- proc.time() +print("FINISHED SPLITTING DATA...") + + + +# part 4: fit model +# h2o.glm with no regularization +model_h2o <- h2o.glm(y = "IsArrDelayed", + x = c("Month", "DayofMonth", "DayOfWeek", "CRSDepTime", "Distance"), + training_frame = train, seed = 123, + family = "binomial", lambda = 0, max_iterations = 25) +pt5 <- proc.time() +print("FINISHED FITTING MODEL...") + + + +# part 5: predict on test +p <- h2o.predict(model_h2o, newdata = test) +p_sdf <- as_spark_dataframe(sc, p, strict_version_check = FALSE) +outFile <- file.path("wasb://@.blob.core.windows.net", "h2o", paste0(gsub(".csv", "", fileName), "_pred")) +spark_write_parquet(p_sdf, outFile, mode = "overwrite") +pt6 <- proc.time() +print("FINISHED PREDICTION...") + +# disconnect +h2o.shutdown(prompt = FALSE) +spark_disconnect(sc) + + + +# part 7: output results +results <- data.frame("number of rows" = fileName, + "load data" = (pt2-pt1)[[3]], + "tranform feature" = (pt3-pt2)[[3]], + "split data" = (pt4-pt3)[[3]], + "fit model" = (pt5-pt4)[[3]], + "prediction" = (pt6-pt5)[[3]], + "total" = (pt6-pt1)[[3]] +) + + + +# "~/results/h2o/h2o_1MM.csv" +utils::write.csv(results, args[2], row.names = FALSE) +print("FINISHED WRITTING OUTPUTS...") + diff --git a/Misc/StrataSanJose2017/Performance_Comparison/mrs.R b/Misc/StrataSanJose2017/Performance_Comparison/mrs.R new file mode 100644 index 00000000..3f2f9eeb --- /dev/null +++ b/Misc/StrataSanJose2017/Performance_Comparison/mrs.R @@ -0,0 +1,130 @@ +#### execute .R scripts in command line for multiple experiments. +# Rscript --default-packages=methods,utils mrs.R "airline_1MM.csv" "~/results/mrs/mrs_1MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_2MM.csv" "~/results/mrs/mrs_2MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_5MM.csv" "~/results/mrs/mrs_5MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_10MM.csv" "~/results/mrs/mrs_10MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_20MM.csv" "~/results/mrs/mrs_20MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_50MM.csv" "~/results/mrs/mrs_50MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_100MM.csv" "~/results/mrs/mrs_100MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_200MM.csv" "~/results/mrs/mrs_200MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_400MM.csv" "~/results/mrs/mrs_400MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_800MM.csv" "~/results/mrs/mrs_800MM.csv" +# Rscript --default-packages=methods,utils mrs.R "airline_1000MM.csv" "~/results/mrs/mrs_1000MM.csv" + + + +#!/usr/bin/env Rscript +args = commandArgs(trailingOnly=TRUE) + +# Inputs: +# fileName <- "airline_1MM.csv" +fileName <- args[1] + +# create a temporary directory in blob +# this code only needs to be executed once +rxHadoopMakeDir(file.path(myNameNode, bigDataDirRoot, "temp")) + + +# call libraries +library("RevoScaleR") + + + +# part 1: reading data +pt1 <- proc.time() +myNameNode <- "wasb://@.blob.core.windows.net" +myPort <- 0 +bigDataDirRoot <- "" + +# Define HDFS file system. +hdfsFS <- RxHdfsFileSystem(hostName = myNameNode, port = myPort) + +# Define Spark compute context. +mySparkCluster <- rxSparkConnect(hdfsShareDir = bigDataDirRoot, + nameNode = myNameNode, + port = myPort, + extraSparkConfig = "--conf spark.dynamicAllocation.enabled=true --conf spark.shuffle.service.enabled=true --conf spark.dynamicAllocation.minExecutors=1", + idleTimeout = 86400000, + consoleOutput = TRUE, + fileSystem = hdfsFS) + +# Specify the input file in HDFS to analyze. +inputFile <- file.path(bigDataDirRoot, fileName) +outFileName <- "temp/airlineXDF" +outFile <- file.path(bigDataDirRoot, outFileName) + +# Define the data source. +inputDS <- RxTextData(file = inputFile, + missingValueString = "M", + firstRowIsColNames = TRUE, + delimiter = ",", + fileSystem = hdfsFS) + +# Define out file +outFileXDF <- RxXdfData(file = outFile, + fileSystem = hdfsFS) + + + +# import data into XDF +# part 2: data transformation on CRSDepTime +set.seed(123) +rxImport(inData = inputDS, outFile = outFileXDF, + transforms = list(CRSDepTime = floor(CRSDepTime / 100), + testSplitVar = ( runif( .rxNumRows ) > 0.25 )), + fileSystem = hdfsFS, + overwrite = TRUE) +pt2 <- proc.time() +pt3 <- proc.time() +print("FINISHED LOADING DATA...") +print("FINISHED DATA TRANSFORMATION...") + + + +# part 3: split train/test +testXdfFile <- file.path(bigDataDirRoot, "temp/testXDF") +testDS <- RxXdfData(file = testXdfFile, fileSystem = hdfsFS) +rxDataStep( inData = outFileXDF, outFile = testDS, + rowSelection = ( testSplitVar == 0), + overwrite = TRUE) +pt4 <- proc.time() +print("FINISHED SPLITTING DATA...") + + + +# part 4: fit model +# rxLogit +model_mrs <- rxLogit(formula = IsArrDelayed ~ Month+DayofMonth+DayOfWeek+CRSDepTime+Distance, data = outFileXDF, + rowSelection = ( testSplitVar == 1)) +pt5 <- proc.time() +print("FINISHED FITTING MODEL...") + + + +# part 5: predict on test +predictXdfFile <- file.path(bigDataDirRoot, "temp/predictXDF") +predictDS <- RxXdfData(file = predictXdfFile, fileSystem = hdfsFS) +rxPredict(modelObject = model_mrs, data = testDS, outData = predictDS, + type = "response", predVarNames = "predictArrDelayed", overwrite = TRUE) +pt6 <- proc.time() +print("FINISHED PREDICTION...") + +# stop Spark connection +rxSparkDisconnect(mySparkCluster) + + + +# part 6: output results +results <- data.frame("number of rows" = fileName, + "load data" = (pt2-pt1)[[3]], + "tranform feature" = (pt3-pt2)[[3]], + "split data" = (pt4-pt3)[[3]], + "fit model" = (pt5-pt4)[[3]], + "prediction" = (pt6-pt5)[[3]], + "total" = (pt6-pt1)[[3]]) + + +# "~/results/mrs/mrs_1MM.csv" +write.csv(results, args[2], row.names = FALSE) +print("FINISHED WRITTING OUTPUTS...") + diff --git a/Misc/StrataSanJose2017/Performance_Comparison/sparklyr.R b/Misc/StrataSanJose2017/Performance_Comparison/sparklyr.R new file mode 100644 index 00000000..0a7f2b28 --- /dev/null +++ b/Misc/StrataSanJose2017/Performance_Comparison/sparklyr.R @@ -0,0 +1,105 @@ +# install "sparklyr" package: +# install.packages("sparklyr") + + +############################################################## + +#### execute .R scripts in command line for multiple experiments. +# Rscript sparklyr.R "airline_1MM.csv" "~/results/sparklyr/sparklyr_1MM.csv" +# Rscript sparklyr.R "airline_2MM.csv" "~/results/sparklyr/sparklyr_2MM.csv" +# Rscript sparklyr.R "airline_5MM.csv" "~/results/sparklyr/sparklyr_5MM.csv" +# Rscript sparklyr.R "airline_10MM.csv" "~/results/sparklyr/sparklyr_10MM.csv" +# Rscript sparklyr.R "airline_20MM.csv" "~/results/sparklyr/sparklyr_20MM.csv" +# Rscript sparklyr.R "airline_50MM.csv" "~/results/sparklyr/sparklyr_50MM.csv" +# Rscript sparklyr.R "airline_100MM.csv" "~/results/sparklyr/sparklyr_100MM.csv" +# Rscript sparklyr.R "airline_200MM.csv" "~/results/sparklyr/sparklyr_200MM.csv" +# Rscript sparklyr.R "airline_400MM.csv" "~/results/sparklyr/sparklyr_400MM.csv" +# Rscript sparklyr.R "airline_800MM.csv" "~/results/sparklyr/sparklyr_800MM.csv" +# Rscript sparklyr.R "airline_1000MM.csv" "~/results/sparklyr/sparklyr_1000MM.csv" + + + +#!/usr/bin/env Rscript +args = commandArgs(trailingOnly=TRUE) + +# Inputs: +# fileName <- "airline_1MM.csv" +fileName <- args[1] + + +# call libraries +library("sparklyr") +library("dplyr") + + +# part 1: reading data +pt1 <- proc.time() + +# connect to remote Spark clusters +config <- spark_config() +config$spark.dynamicAllocation.enabled <- "true" +config$spark.shuffle.service.enabled <- "true" +config$spark.dynamicAllocation.minExecutors <- 1 + +sc <- spark_connect(master = "yarn-client", config = config) + +# import data +inputPath <- file.path("wasb://@.blob.core.windows.net", fileName) +airline_lyr <- spark_read_csv(sc, + name = gsub(".csv", "", fileName), + path = inputPath, + header = TRUE) +pt2 <- proc.time() +print("FINISHED LOADING DATA...") + + + +# part 2: data transformation on CRSDepTime +airline_lyr <- airline_lyr %>% mutate(CRSDepTime = floor(CRSDepTime / 100)) +pt3 <- proc.time() +print("FINISHED DATA TRANSFORMATION...") + + + +# part 3: split train/test +partitions <- airline_lyr %>% sdf_partition(train = 0.75, test = 0.25, seed = 123) +pt4 <- proc.time() +print("FINISHED SPLITTING DATA...") + + + +# part 4: fit model +# ml_logistic_regression +ml_model <- partitions$train %>% ml_logistic_regression(response = "IsArrDelayed", + features = c("Month", "DayofMonth", "DayOfWeek", "CRSDepTime", "Distance"), + iter.max = 25) +pt5 <- proc.time() +print("FINISHED FITTING MODEL...") + + + +# part 5: predict on test +p <- sdf_predict(ml_model, partitions$test) # %>% select(prediction) +outFile <- file.path("wasb://@.blob.core.windows.net", "sparklyr", paste0(gsub(".csv", "", fileName), "_pred")) +spark_write_parquet(p, outFile, mode = "overwrite") +pt6 <- proc.time() +print("FINISHED PREDICTION...") + +# disconnect +spark_disconnect(sc) + + + +# part 6: output results +results <- data.frame("number of rows" = fileName, + "load data" = (pt2-pt1)[[3]], + "tranform feature" = (pt3-pt2)[[3]], + "split data" = (pt4-pt3)[[3]], + "fit model" = (pt5-pt4)[[3]], + "prediction" = (pt6-pt5)[[3]], + "total" = (pt6-pt1)[[3]]) + + +# "~/results/sparklyr/sparklyr_1MM.csv" +write.csv(results, args[2], row.names = FALSE) +print("FINISHED WRITTING OUTPUTS...") \ No newline at end of file diff --git a/Misc/StrataSanJose2017/Presentations_and_Docs/Readme.md b/Misc/StrataSanJose2017/Presentations_and_Docs/Readme.md new file mode 100644 index 00000000..0f0463a8 --- /dev/null +++ b/Misc/StrataSanJose2017/Presentations_and_Docs/Readme.md @@ -0,0 +1 @@ +This folder contains documents and presentations for the tutorial. diff --git a/Misc/StrataSanJose2017/Presentations_and_Docs/Using R for scalable data analytics-From single machines to Hadoop Spark clusters.pdf b/Misc/StrataSanJose2017/Presentations_and_Docs/Using R for scalable data analytics-From single machines to Hadoop Spark clusters.pdf new file mode 100644 index 00000000..c7079624 Binary files /dev/null and b/Misc/StrataSanJose2017/Presentations_and_Docs/Using R for scalable data analytics-From single machines to Hadoop Spark clusters.pdf differ diff --git a/Misc/StrataSanJose2017/Presentations_and_Docs/Using R for scalable data analytics-From single machines to Hadoop Spark clusters.pptx b/Misc/StrataSanJose2017/Presentations_and_Docs/Using R for scalable data analytics-From single machines to Hadoop Spark clusters.pptx new file mode 100644 index 00000000..7e8fcedd Binary files /dev/null and b/Misc/StrataSanJose2017/Presentations_and_Docs/Using R for scalable data analytics-From single machines to Hadoop Spark clusters.pptx differ diff --git a/Misc/StrataSanJose2017/Readme.md b/Misc/StrataSanJose2017/Readme.md new file mode 100644 index 00000000..3d5898ed --- /dev/null +++ b/Misc/StrataSanJose2017/Readme.md @@ -0,0 +1,103 @@ +# This repository has content for the Strata San Jose 2017 (March 14, 9AM - 12:30PM) tutorial "Scalable Data Science with R, from Single Nodes to Spark Clusters". + +## Tutorial link (Strata San Jose, March 2017) +https://conferences.oreilly.com/strata/strata-ca/public/schedule/detail/55806 + +## General Instructions +#### Setup +You will need to provision a [Linux data science virtual machine (DSVM)](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/microsoft-ads.linux-data-science-vm?tab=Overview). After the machine is provisioned, you will need to login (ssh) into your machine using a client software such as Plink (see below), download the [setup shell script](https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Scripts/DSVM_Customization_Script.sh), and run it using the following commands: +chmod +x DSVM_Customization_Script.sh +./DSVM_Customization_Script.sh + +The above steps will setup your DSVM. After which you can following the remaining steps of the tutorial. + +#### Running R scripts on Linux DSVM +As explained below, you can login (using web-browser) to the R-studio server on the DSVM and run the R scripts which are provided [here](https://github.com/Azure/Azure-MachineLearning-DataScience/tree/master/Misc/StrataSanJose2017/Code). These R-scripts, along with the necessary data will be already loaded on your machine by running the setup shell script above. If R scripts are in markdown files, then you can click on "Knit" near the top of your R-studio browser window. + + +## REQUIRED - Tutorial Prerequisites +* Please bring a wireless enabled laptop. +* Make sure your machine has an ssh client with port-forwarding capability. On Mac or Linux, simply run the ssh command in a terminal window. +On Windows, download [plink.exe](https://the.earth.li/~sgtatham/putty/latest/x86/plink.exe) +from http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html. + +## Connecting to the Data Science Virtual Machine (with Spark 2.0.2) on Microsoft Azure +We will provide Azure Data Science Virtual Machines (running Spark 2.0.2) for attendees to use during the tutorial. You will use your laptop to connect to your allocated virtual machine. + +* Command line to connect with ssh (Linux, Mac) - replace XXX with the DNS address of your Data Science Virtual Machine [e.g. strataABC.westus.cloudapp.azure.com] +```bash +ssh -L localhost:8787:localhost:8787 -L localhost:8088:localhost:8088 remoteuser@XXX +``` +* Command line to connect with plink.exe (Windows) - run the following commands in a Windows command prompt window - replace XXX with the DNS address of your Data Science Virtual Machine [e.g. strataABC.westus.cloudapp.azure.com] +```bash +cd directory-containing-plink.exe +.\plink.exe -L localhost:8787:localhost:8787 -L localhost:8088:localhost:8088 remoteuser@XXX +``` +* After connecting via the above command lines, open [http://localhost:8787/](http://localhost:8787/) in your web browser to connect to RStudio Server on your Data Science Virtual Machine
+NOTE: During the tutorial, all attendees will use RStudio Server on their Data Science Virtual Machines. + +
+ + +## Tutorial slides + +Slide deck:
+https://github.com/Azure/Azure-MachineLearning-DataScience/blob/master/Misc/StrataSanJose2017/Presentations_and_Docs/Using%20R%20for%20scalable%20data%20analytics-From%20single%20machines%20to%20Hadoop%20Spark%20clusters.pdf + +## Suggested Reading prior to tutorial date + +### SparkR (Spark 2.0.2):
+SparkR general information: http://spark.apache.org/docs/latest/sparkr.html +
+SparkR 2.0.2 functions: https://spark.apache.org/docs/2.0.2/api/R/index.html + + + +### sparklyr:
+sparklyr general information: http://spark.rstudio.com/ +
+sparklyr MLlib functions: sparklyr MLlib functions: http://spark.rstudio.com/mllib.html + +### RevoScaleR:
+RevoScaleR functions: https://msdn.microsoft.com/en-us/microsoft-r/scaler/scaler + +### Microsoft R Server:
+Microsoft R Server general information: https://msdn.microsoft.com/en-us/microsoft-r/rserver.
+Microsoft R Servers are installed on both Azure Linux DSVMs and HDInsight clusters (see below), and will be used to run R code in the tutorial. + +### R-Server Operationalization service:
+Microsoft R Server operationalization service general information: https://msdn.microsoft.com/en-us/microsoft-r/operationalize/about +
+Configuring operationalization: https://msdn.microsoft.com/en-us/microsoft-r/operationalize/configuration-initial + +
+
+ +## Datasets used in this tutorial + +### The 2013 New York City Taxi and Fare dataset (used in SparkR and sparklyr samples) +The NYC Taxi Trip data is about 20 GB of compressed comma-separated values (CSV) files (~48 GB uncompressed), comprising more than 173 million individual trips and the fares paid for each trip. Each trip record includes the pick up and drop-off location and time, anonymized hack (driver's) license number and medallion (taxi’s unique id) number. The data covers all trips in the year 2013 and is provided in the following two datasets for each month: +* The 'trip_data' CSV files contain trip details, such as number of passengers, pick up and dropoff points, trip duration, and trip length. +* The 'trip_fare' CSV files contain details of the fare paid for each trip, such as payment type, fare amount, surcharge and taxes, tips and tolls, and the total amount paid. + +For hands-on exercises, attendees will use the data from 1 month of 2013, namely December (about 1/10th of the full 2013 data) + +The learning problem: To predict the amount of tip paid for a taxi trip (target), based on features such as trip distance, fare amount, number of passengers, time of pickup etc. + +Link for further details: https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-spark-overview#the-nyc-2013-taxi-data +
+
+ +## Platforms & services for hands-on exercises or demos +### Azure Linux DSVM (Data Science Virtual Machine) +Information on Linux DSVM: https://azuremarketplace.microsoft.com/en-us/marketplace/apps/microsoft-ads.linux-data-science-vm
+The Linux DSVM has Spark (2.0.2) installed, as well as Yarn for job management, as well as HDFS. So, you can use the DSVM to run regular R code as well as code that run on Spark (e.g. using SparkR package). You will use DSVM as a single node Spark machine for hands-on exercises. We will provision these machines and assign them to you at the beginning of the tutorial.
+ +### Azure HDInsight Spark clusters +Information about HDInsight Spark clusters: https://docs.microsoft.com/en-us/azure/hdinsight/hdinsight-apache-spark-overview
+For this tutorial, we will show how to scale the code you develop in DSVM using HDInsight clusters. + +
+
+## Video Record of an earlier version of this tutorial (presented at the KDD conference in August 2016) +http://videolectures.net/kdd2016_tutorial_scalable_r_on_spark/?q=Spark diff --git a/Misc/StrataSanJose2017/Scripts/DSVM_Customization_Script.sh b/Misc/StrataSanJose2017/Scripts/DSVM_Customization_Script.sh new file mode 100644 index 00000000..b043d800 --- /dev/null +++ b/Misc/StrataSanJose2017/Scripts/DSVM_Customization_Script.sh @@ -0,0 +1,178 @@ +####################################################################################################################################### +####################################################################################################################################### +## THIS SCRIPT CUSTOMIZES THE DSVM BY ADDING HADOOP AND YARN, INSTALLING R-PACKAGES, AND DOWNLOADING DATA-SETS FOR STRATA +## SAN JOSE 2017 EXERCISES. +####################################################################################################################################### +####################################################################################################################################### + +#!/bin/bash +source /etc/profile.d/hadoop.sh +#PATH=paste0(Sys.getenv("PATH"),":/opt/hadoop/current/bin:/dsvm/tools/spark/current/bin") + +####################################################################################################################################### +## Setup autossh for hadoop service account +####################################################################################################################################### +#cat /opt/hadoop/.ssh/id_rsa.pub >> /opt/hadoop/.ssh/authorized_keys +#chmod 0600 /opt/hadoop/.ssh/authorized_keys +#chown hadoop /opt/hadoop/.ssh/authorized_keys +echo -e 'y\n' | ssh-keygen -t rsa -P '' -f ~hadoop/.ssh/id_rsa +cat ~hadoop/.ssh/id_rsa.pub >> ~hadoop/.ssh/authorized_keys +chmod 0600 ~hadoop/.ssh/authorized_keys +chown hadoop:hadoop ~hadoop/.ssh/id_rsa +chown hadoop:hadoop ~hadoop/.ssh/id_rsa.pub +chown hadoop:hadoop ~hadoop/.ssh/authorized_keys + +####################################################################################################################################### +## Start up several services, yarn, hadoop, rstudio server +####################################################################################################################################### +systemctl start hadoop-namenode hadoop-datanode hadoop-yarn rstudio-server + +####################################################################################################################################### +## MRS Deploy Setup +####################################################################################################################################### +cd /home/remoteuser +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Scripts/backend_appsettings.json +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Scripts/webapi_appsettings.json + +mv backend_appsettings.json /usr/lib64/microsoft-deployr/9.0.1/Microsoft.DeployR.Server.BackEnd/appsettings.json +mv webapi_appsettings.json /usr/lib64/microsoft-deployr/9.0.1/Microsoft.DeployR.Server.WebAPI/appsettings.json + +cp /usr/lib64/microsoft-deployr/9.0.1/Microsoft.DeployR.Server.WebAPI/autoStartScriptsLinux/* /etc/systemd/system/. +cp /usr/lib64/microsoft-deployr/9.0.1/Microsoft.DeployR.Server.BackEnd/autoStartScriptsLinux/* /etc/systemd/system/. +systemctl enable frontend +systemctl enable rserve +systemctl enable backend +systemctl start frontend +systemctl start rserve +systemctl start backend + +####################################################################################################################################### +# Copy data and code to VM +####################################################################################################################################### + +# Copy Spark configuration files & shell script +cd /home/remoteuser +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Scripts/spark-defaults.conf +mv spark-defaults.conf /dsvm/tools/spark/current/conf +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Scripts/log4j.properties +mv log4j.properties /dsvm/tools/spark/current/conf +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Scripts/DSVM_Customization_Script.sh + +## DOWNLOAD ALL CODE FILES +cd /home/remoteuser +mkdir Data Code +mkdir Code/MRS Code/sparklyr Code/SparkR Code/bigmemory Code/ff Code/UseCaseHTS Code/UseCaseLearningCurves + +cd /home/remoteuser +cd Code/MRS +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/MRS/1-Clean-Join-Subset.r +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/MRS/2-Train-Test-Subset.r +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/MRS/3-Deploy-Score-mrsdeploy.r +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/MRS/SetComputeContext.r +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/MRS/logitModelSubset.RData + +cd /home/remoteuser +cd Code/SparkR +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forDSVM.Rmd +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/SparkR/SparkR_NYCTaxi_forDSVM.html +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/SparkR/Operationalization_RemoteAccessAPI_forDSVM.R +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/SparkR/SparkR_GLM_Operationalization_forDSVM.R + + +cd /home/remoteuser +cd Code/sparklyr +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forDSVM.Rmd +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/sparklyr/sparklyr_NYCTaxi_forDSVM.html + +cd /home/remoteuser +cd Code/bigmemory +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/bigmemory/bigmemory.R + +cd /home/remoteuser +cd Code/ff +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/ff/ff.R + +cd /home/remoteuser +cd Code/UseCaseHTS +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.R +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/UseCaseHTS/sample_demo.Rmd +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/UseCaseHTS/aust_hierarchy.png + + +cd /home/remoteuser +cd Code/UseCaseLearningCurves +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/high_cardinality_learning_curves_demo.Rmd +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Code/UseCaseLearningCurves/learning_curve_lib.R + + +## DOWNLOAD ALL DATA FILES +# NYC Taxi data +cd /home/remoteuser +cd Data +wget http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/KDD2016/trip_fare_12.csv +wget http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/KDD2016/trip_data_12.csv +wget http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/KDD2016/JoinedParquetSampledFile.tar.gz +gunzip JoinedParquetSampledFile.tar.gz +tar -xvf JoinedParquetSampledFile.tar +mv JoinedParquetSampledFile NYCjoinedParquetSubset +rm JoinedParquetSampledFile.tar + +wget http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/FareData2013DecParquet.tar +wget http://cdspsparksamples.blob.core.windows.net/data/NYCTaxi/TripData2013DecParquet.tar +tar -xvf FareData2013DecParquet.tar +tar -xvf TripData2013DecParquet.tar +rm FareData2013DecParquet.tar +rm TripData2013DecParquet.tar + + +# Airline data +wget http://cdspsparksamples.blob.core.windows.net/data/Airline/WeatherSubsetCsv.tar.gz +wget http://cdspsparksamples.blob.core.windows.net/data/Airline/AirlineSubsetCsv.tar.gz +gunzip WeatherSubsetCsv.tar.gz +gunzip AirlineSubsetCsv.tar.gz +tar -xvf WeatherSubsetCsv.tar +tar -xvf AirlineSubsetCsv.tar +rm WeatherSubsetCsv.tar AirlineSubsetCsv.tar + +cd /home/remoteuser +cd Data +wget http://strata2017r.blob.core.windows.net/airline/airline_20MM.csv + +## Copy data to HDFS +cd /home/remoteuser +cd Data + +# Make hdfs directories and copy things over to HDFS +/opt/hadoop/current/bin/hadoop fs -mkdir /user/RevoShare/rserve2 +/opt/hadoop/current/bin/hadoop fs -mkdir /user/RevoShare/rserve2/Predictions +/opt/hadoop/current/bin/hadoop fs -chmod -R 777 /user/RevoShare/rserve2 + +/opt/hadoop/current/bin/hadoop fs -mkdir /user/RevoShare/remoteuser +/opt/hadoop/current/bin/hadoop fs -mkdir /user/RevoShare/remoteuser/Data +/opt/hadoop/current/bin/hadoop fs -mkdir /user/RevoShare/remoteuser/Models +/opt/hadoop/current/bin/hadoop fs -copyFromLocal * /user/RevoShare/remoteuser/Data + + +####################################################################################################################################### +####################################################################################################################################### +# Install R packages +cd /home/remoteuser +wget https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Scripts/InstallPackages.R + +cd /usr/bin +Revo64-9.0 --vanilla --quiet < /home/remoteuser/InstallPackages.R + +####################################################################################################################################### +####################################################################################################################################### +## Change ownership of some of directories +cd /home/remoteuser +chown -R remoteuser Code Data + +su hadoop -c "/opt/hadoop/current/bin/hadoop fs -chown -R remoteuser /user/RevoShare/rserve2" +su hadoop -c "/opt/hadoop/current/bin/hadoop fs -chown -R remoteuser /user/RevoShare/remoteuser" + +####################################################################################################################################### +####################################################################################################################################### +## END +####################################################################################################################################### +####################################################################################################################################### diff --git a/Misc/StrataSanJose2017/Scripts/InstallPackages.R b/Misc/StrataSanJose2017/Scripts/InstallPackages.R new file mode 100644 index 00000000..396c387d --- /dev/null +++ b/Misc/StrataSanJose2017/Scripts/InstallPackages.R @@ -0,0 +1,16 @@ +install.packages("sparklyr", repos='http://cran.us.r-project.org') +install.packages("ggplot2", repos='http://cran.us.r-project.org') +install.packages("gridExtra", repos='http://cran.us.r-project.org') +install.packages("rmarkdown", repos='http://cran.us.r-project.org') +install.packages("knitr", repos='http://cran.us.r-project.org') +install.packages("formatR", repos='http://cran.us.r-project.org') +install.packages("tidyr", repos='http://cran.us.r-project.org') +install.packages("bigmemory", repos='http://cran.us.r-project.org') +install.packages("biganalytics", repos='http://cran.us.r-project.org') +install.packages("ff", repos='http://cran.us.r-project.org') +install.packages("ffbase", repos='http://cran.us.r-project.org') +install.packages("biglm", repos='http://cran.us.r-project.org') +install.packages("hts", repos='https://mran.revolutionanalytics.com/snapshot/2016-11-01') +install.packages("fpp", repos='https://mran.revolutionanalytics.com/snapshot/2016-11-01') +#install.packages("ggmap", repos='http://cran.us.r-project.org', dependencies=TRUE) + diff --git a/Misc/StrataSanJose2017/Scripts/Readme.md b/Misc/StrataSanJose2017/Scripts/Readme.md new file mode 100644 index 00000000..59237930 --- /dev/null +++ b/Misc/StrataSanJose2017/Scripts/Readme.md @@ -0,0 +1,2 @@ +Folder contains scripts for provisioning DSVMs, installing software and packages using script actions, copying files +from a public blob to the DSVMs. diff --git a/Misc/StrataSanJose2017/Scripts/azuredeployDSVM.json b/Misc/StrataSanJose2017/Scripts/azuredeployDSVM.json new file mode 100644 index 00000000..d0b6093b --- /dev/null +++ b/Misc/StrataSanJose2017/Scripts/azuredeployDSVM.json @@ -0,0 +1,312 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "adminUsername": { + "type": "string", + "metadata": { + "description": "remoteuser" + } + }, + "adminPassword": { + "type": "securestring", + "metadata": { + "description": "" + } + }, + "vmName": { + "type": "string", + "metadata": { + "description": "StrataR" + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_DS12_v2", + "metadata": { + "description": "Standard_DS12_v2" + } + } + }, + "variables": { + "location": "[resourceGroup().location]", + "imagePublisher": "microsoft-ads", + "imageOffer": "linux-data-science-vm", + "OSDiskName": "osdiskforlinuxsimple", + "DataDiskName": "datadiskforlinuxsimple", + "sku": "linuxdsvm", + "nicName": "[parameters('vmName')]", + "addressPrefix": "10.0.0.0/16", + "subnetName": "Subnet", + "subnetPrefix": "10.0.0.0/24", + "storageAccountType": "Standard_LRS", + "storageAccountName": "[concat(parameters('vmName'),'sjcstorage')]", + "publicIPAddressType": "Dynamic", + "publicIPAddressName": "[parameters('vmName')]", + "vmStorageAccountContainerName": "vhds", + "vmName": "[parameters('vmName')]", + "vmSize": "[parameters('vmSize')]", + "virtualNetworkName": "[parameters('vmName')]", + "networkSecurityGroupName": "[concat(parameters('vmName'),'_NSG')]", + "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", + "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", + "fileUris": "https://raw.githubusercontent.com/Azure/Azure-MachineLearning-DataScience/master/Misc/StrataSanJose2017/Scripts/DSVM_Customization_Script.sh", + "commandToExecute": "dos2unix < DSVM_Customization_Script.sh > runscript.sh; bash runscript.sh", + "apiVersion": "2015-06-15", + "version": "latest" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "apiVersion": "2015-05-01-preview", + "location": "[variables('location')]", + "properties": { + "accountType": "[variables('storageAccountType')]" + } + }, + { + "apiVersion": "2015-05-01-preview", + "type": "Microsoft.Network/publicIPAddresses", + "name": "[variables('publicIPAddressName')]", + "location": "[variables('location')]", + "properties": { + "publicIPAllocationMethod": "[variables('publicIPAddressType')]", + "dnsSettings": { + "domainNameLabel": "[variables('publicIPAddressName')]" + } + } + }, + { + "apiVersion": "2015-05-01-preview", + "type": "Microsoft.Network/virtualNetworks", + "name": "[variables('virtualNetworkName')]", + "location": "[variables('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[variables('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetPrefix')]" + } + } + ] + } + }, + { + "type": "Microsoft.Network/networkSecurityGroups", + "name": "[variables('networkSecurityGroupName')]", + "apiVersion": "2015-05-01-preview", + "location": "[variables('location')]", + "properties": { + "securityRules": [ + { + "name": "yarndashboard", + "properties": { + "priority": 1010, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "8088", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "jupyterhub", + "properties": { + "priority": 1020, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "8000", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "Jupyter", + "properties": { + "priority": 1030, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "9999", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "RStudioServer", + "properties": { + "priority": 1040, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "8787", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "mrsdeploy", + "properties": { + "priority": 1050, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "12800", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "default-allow-ssh", + "properties": { + "priority": 1060, + "sourceAddressPrefix": "*", + "protocol": "Tcp", + "destinationPortRange": "22", + "access": "Allow", + "direction": "inbound", + "sourcePortRange": "*", + "destinationAddressPrefix": "*" + } + } + ] + } + }, + { + "apiVersion": "2015-05-01-preview", + "type": "Microsoft.Network/networkInterfaces", + "name": "[variables('nicName')]", + "location": "[variables('location')]", + "dependsOn": [ + "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]", + "[concat('Microsoft.Network/networkSecurityGroups/', variables('networkSecurityGroupName'))]" + ], + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" + }, + "subnet": { + "id": "[variables('subnetRef')]" + } + } + } + ], + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('networkSecurityGroupName'))]" + } + } + }, + { + "apiVersion": "2015-06-15", + "type": "Microsoft.Compute/virtualMachines", + "name": "[variables('vmName')]", + "location": "[variables('location')]", + "plan": { + "name": "linuxdsvm", + "publisher": "microsoft-ads", + "product": "linux-data-science-vm" + }, + "tags": { + "Application": "DataScience" + }, + "dependsOn": [ + "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", + "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "[variables('vmSize')]" + }, + "osProfile": { + "computerName": "[variables('vmName')]", + "adminUsername": "[parameters('adminUsername')]", + "adminPassword": "[parameters('adminPassword')]" + }, + "storageProfile": { + "imageReference": { + "publisher": "microsoft-ads", + "offer": "linux-data-science-vm", + "sku": "linuxdsvm", + "version": "latest" + }, + "osDisk": { + "name": "osdisk", + "vhd": { + "uri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'), variables('vmName'), '.vhd')]" + }, + "caching": "ReadWrite", + "createOption": "FromImage" + }, + "dataDisks": [ + { + "name": "data-0", + "vhd": { + "uri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('DataDiskName'), variables('vmName'), '.vhd')]" + }, + "caching": "None", + "createOption": "FromImage", + "lun": 0 + } + ] + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": "true", + "storageUri": "[concat('http://',variables('storageAccountName'),'.blob.core.windows.net')]" + } + } + }, + "resources": [ + { + "type": "extensions", + "name": "[variables('vmName')]", + "apiVersion": "[variables('apiVersion')]", + "location": "[variables('location')]", + "dependsOn": [ + "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'))]" + ], + "properties": { + "publisher": "Microsoft.Azure.Extensions", + "type": "CustomScript", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": { + "fileUris": ["[variables('fileUris')]"], + "commandToExecute": "[variables('commandToExecute')]" + } + } + } + ] + } + ], + "outputs": { + "DataScienceVmUrl": { "type": "string", "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'))]" } + } +} diff --git a/Misc/StrataSanJose2017/Scripts/backend_appsettings.json b/Misc/StrataSanJose2017/Scripts/backend_appsettings.json new file mode 100644 index 00000000..4b55ac48 --- /dev/null +++ b/Misc/StrataSanJose2017/Scripts/backend_appsettings.json @@ -0,0 +1,43 @@ +{ + "Kestrel": { + "Port": 12805, + "HttpsEnabled": false, + "HttpsCertificate": { + "Enabled": false, + "Description": "Enable this section if you want to enable SSL on Kestrel", + "StoreName": "My", + "StoreLocation": "LocalMachine", + "SubjectName": "" + } + }, + "Logging": { + "IncludeScopes": false, + "LogsPath": "", + "MaxLogsFilesNumber": 10, + "LogLevel": { + "Default": "Warning", + "System": "Warning", + "Microsoft": "Warning", + "AllowedLogLevels": { + "Verbose": "Verbose is the noisiest level, rarely (if ever) enabled for a production app.", + "Debug": "Debug is used for internal system events that are not necessarily observable from the outside, but useful when determining how something happened.", + "Information": "Information events describe things happening in the system that correspond to its responsibilities and functions", + "Warning": "When service is degraded, endangered, or may be behaving outside of its expected parameters, Warning level events are used.", + "Error": "When functionality is unavailable or expectations broken, an Error event is used.", + "Fatal": "The most critical level, Fatal events demand immediate attention." + } + } + }, + "ClientCertificate": { + "Enabled": false, + "Description": "Enable this section if you want to enforce certificate authentication for front-end to back-end", + "Issuer": "", + "Subject": "" + }, + "Pool": { + "InitialSize": 5, + "MaxSize": 80 + }, + "RServePort": "9054", + "configured": "configured" +} \ No newline at end of file diff --git a/Misc/StrataSanJose2017/Scripts/log4j.properties b/Misc/StrataSanJose2017/Scripts/log4j.properties new file mode 100644 index 00000000..302c6867 --- /dev/null +++ b/Misc/StrataSanJose2017/Scripts/log4j.properties @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Set everything to be logged to the console +log4j.rootCategory=ERROR, console +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.target=System.err +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n + +# Set the default spark-shell log level to WARN. When running the spark-shell, the +# log level for this class is used to overwrite the root logger's log level, so that +# the user can have different defaults for the shell and regular Spark apps. +log4j.logger.org.apache.spark.repl.Main=ERROR + +# Settings to quiet third party logs that are too verbose +log4j.logger.org.spark_project.jetty=ERROR +log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR +log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=ERROR +log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=ERROR +log4j.logger.org.apache.parquet=ERROR +log4j.logger.parquet=ERROR + +# SPARK-9183: Settings to avoid annoying messages when looking up nonexistent UDFs in SparkSQL with Hive support +log4j.logger.org.apache.hadoop.hive.metastore.RetryingHMSHandler=FATAL +log4j.logger.org.apache.hadoop.hive.ql.exec.FunctionRegistry=ERROR diff --git a/Misc/StrataSanJose2017/Scripts/spark-defaults.conf b/Misc/StrataSanJose2017/Scripts/spark-defaults.conf new file mode 100644 index 00000000..ea9eb143 --- /dev/null +++ b/Misc/StrataSanJose2017/Scripts/spark-defaults.conf @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Default system properties included when running spark-submit. +# This is useful for setting default environmental settings. + +# Example: +# spark.master spark://master:7077 +# spark.eventLog.enabled true +# spark.eventLog.dir hdfs://namenode:8021/directory +# spark.serializer org.apache.spark.serializer.KryoSerializer +spark.driver.memory 6g +# spark.executor.extraJavaOptions -XX:+PrintGCDetails -Dkey=value -Dnumbers="one two three" +spark.executor.instances 3 +spark.executor.memory 4096m +spark.yarn.driver.memoryOverhead 2048 +spark.yarn.executor.memoryOverhead 2048 +spark.executor.cores 1 diff --git a/Misc/StrataSanJose2017/Scripts/spark-env.sh b/Misc/StrataSanJose2017/Scripts/spark-env.sh new file mode 100644 index 00000000..36232e3e --- /dev/null +++ b/Misc/StrataSanJose2017/Scripts/spark-env.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# This file is sourced when running various Spark programs. +# Copy it as spark-env.sh and edit that to configure Spark for your site. + +# Options read when launching programs locally with +# ./bin/run-example or ./bin/spark-submit +HADOOP_CONF_DIR=/opt/hadoop/hadoop-2.7.3/etc/hadoop +# - HADOOP_CONF_DIR, to point Spark towards Hadoop configuration files +# - SPARK_LOCAL_IP, to set the IP address Spark binds to on this node +# - SPARK_PUBLIC_DNS, to set the public dns name of the driver program +# - SPARK_CLASSPATH, default classpath entries to append + +# Options read by executors and drivers running inside the cluster +# - SPARK_LOCAL_IP, to set the IP address Spark binds to on this node +# - SPARK_PUBLIC_DNS, to set the public DNS name of the driver program +# - SPARK_CLASSPATH, default classpath entries to append +# - SPARK_LOCAL_DIRS, storage directories to use on this node for shuffle and RDD data +# - MESOS_NATIVE_JAVA_LIBRARY, to point to your libmesos.so if you use Mesos + +# Options read in YARN client mode +HADOOP_CONF_DIR=/opt/hadoop/hadoop-2.7.3/etc/hadoop +SPARK_EXECUTOR_INSTANCES=3 +SPARK_EXECUTOR_CORES=1 +SPARK_EXECUTOR_MEMORY=2G +SPARK_DRIVER_MEMORY=6G + +# Options for the daemons used in the standalone deploy mode +# - SPARK_MASTER_HOST, to bind the master to a different IP address or hostname +# - SPARK_MASTER_PORT / SPARK_MASTER_WEBUI_PORT, to use non-default ports for the master +# - SPARK_MASTER_OPTS, to set config properties only for the master (e.g. "-Dx=y") +# - SPARK_WORKER_CORES, to set the number of cores to use on this machine +# - SPARK_WORKER_MEMORY, to set how much total memory workers have to give executors (e.g. 1000m, 2g) +# - SPARK_WORKER_PORT / SPARK_WORKER_WEBUI_PORT, to use non-default ports for the worker +# - SPARK_WORKER_INSTANCES, to set the number of worker processes per node +# - SPARK_WORKER_DIR, to set the working directory of worker processes +# - SPARK_WORKER_OPTS, to set config properties only for the worker (e.g. "-Dx=y") +# - SPARK_DAEMON_MEMORY, to allocate to the master, worker and history server themselves (default: 1g). +# - SPARK_HISTORY_OPTS, to set config properties only for the history server (e.g. "-Dx=y") +# - SPARK_SHUFFLE_OPTS, to set config properties only for the external shuffle service (e.g. "-Dx=y") +# - SPARK_DAEMON_JAVA_OPTS, to set config properties for all daemons (e.g. "-Dx=y") +# - SPARK_PUBLIC_DNS, to set the public dns name of the master or workers + +# Generic options for the daemons used in the standalone deploy mode +# - SPARK_CONF_DIR Alternate conf dir. (Default: ${SPARK_HOME}/conf) +# - SPARK_LOG_DIR Where log files are stored. (Default: ${SPARK_HOME}/logs) +# - SPARK_PID_DIR Where the pid file is stored. (Default: /tmp) +# - SPARK_IDENT_STRING A string representing this instance of spark. (Default: $USER) +# - SPARK_NICENESS The scheduling priority for daemons. (Default: 0) diff --git a/Misc/StrataSanJose2017/Scripts/webapi_appsettings.json b/Misc/StrataSanJose2017/Scripts/webapi_appsettings.json new file mode 100644 index 00000000..94aca53c --- /dev/null +++ b/Misc/StrataSanJose2017/Scripts/webapi_appsettings.json @@ -0,0 +1,106 @@ +{ + "Kestrel": { + "Port": 12800, + "Host": "localhost", + "HttpsEnabled": false, + "HttpsCertificate": { + "Enabled": false, + "Description": "Enable this section if you want to enable SSL on Kestrel", + "StoreName": "My", + "StoreLocation": "LocalMachine", + "SubjectName": "" + } + }, + "Logging": { + "IncludeScopes": false, + "LogsPath": "", + "MaxLogsFilesNumber": 10, + "LogLevel": { + "Default": "Warning", + "System": "Warning", + "Microsoft": "Warning", + "Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware": "Warning", + "AllowedLogLevels": { + "Verbose": "Verbose is the noisiest level, rarely (if ever) enabled for a production app.", + "Debug": "Debug is used for internal system events that are not necessarily observable from the outside, but useful when determining how something happened.", + "Information": "Information events describe things happening in the system that correspond to its responsibilities and functions", + "Warning": "When service is degraded, endangered, or may be behaving outside of its expected parameters, Warning level events are used.", + "Error": "When functionality is unavailable or expectations broken, an Error event is used.", + "Fatal": "The most critical level, Fatal events demand immediate attention." + } + } + }, + "MaxNumberOfThreadsPerBatchExecution": 100, + "Authentication": { + "AdminPassword": "Y5XlDPBNk6IioDpoFrFQQEEBHpbqJEazeDY40Rwhs+jmdfsWGAJbzRXdhzUXVOBt", + "AzureActiveDirectory": { + "Enabled": false, + "Description": "Enable this section if you want to enable authentication via Azure AD", + "Values": [ + { + "Authority": "https://login.windows.net/", + "Audience": "" + } + ] + }, + "LDAP": { + "Enabled": false, + "Description": "Enable this section if you want to enable authentication via LDAP", + "Values": [ + { + "Host": "", + "UseLDAPS": "True", + "BindFilter": "CN={0},DC=YOURDOMAIN,DC=COM", + "QueryUserDn": "CN=,DC=YOURDOMAIN,DC=COM", + "QueryUserPassword": "", + "SearchBase": "DC=YOURDOMAIN,DC=COM", + "SearchFilter": "cn={0}" + } + ] + }, + "JWTKey": "eyJEIjoienY5OGFFOThmSmpsUm4vaktRS3ZucTY4eE10ZnNSek14ZDdhOXBIZU8zSHJLTDR0V2FCMUluaFhybU1lWXdyOTJVcGtnVm8wSkNnQlBSUEJ1RTRFWGpDYUZ6eGxZYjVOQkc5YlRGeDA4U0ZpRG1aVUk3Tmh3aTNQQVBhNjByRHRnNUQ2aWxDTHJQbW9PMXQ4eVdWcjBzVkNsYTlyemh5a0tCMWN4RWRqL0NMK1RvQnh2VTJCcDB3cEE0WkhPZHNOblBFV0IwcVZXbXNKemh3QTR0ZFZuemppallFSDgwN0hCRTViWWx1azdxUnVLQzRnZzlxMGg4aUZYWS9UcUs2eDVINzdobkZVZnRONEtsQnVQVURYbW1vTWRsMmhyTUkrU3VMSEtDRUd6ang0MjlSYlBHSVZveEd4QW1renVQTFVrM3kvRFN0T0JPMXpGd3I3M0p4cVFRPT0iLCJEUCI6Ikh5Q3hxSVNkVWVWbnFVZkpUT1ZRZlA3cmVVWkliR0RoV1dVSkJ6aWw1U0R1bWE5RTJhWmJOZFB0N3hVK3NBdDRLZ25PNG9SSHF1ejVPeEd1UTVJZ2RrWXp1bFE4cnJEbFA5aFVWYWxFK2JWMVBHOHE0cnF1YzM1MkZmRkZ5c2JZbUNwZDFoMm56dVg1aWdPbmtKV3VuWVczQWtvV25ZRlViWlpPL1k4VStzRT0iLCJEUSI6IkVrRnZMOUtpaUVocWJQUUpHL2s0SnlPY0pGeTdxTnZxZkNWK3JWWUJCQk9iRklZVVQ2bGJ1Y2Q2Vy9hWk9qOUJkREs3ajNROEpnekl0SzF5bHRIKzhxMzR2cHI5YklLWGI2b0hyMk0xWTFQTldLd2c1NmF1Um9NK1QreUE5azYzcisvZTlXOWFKUEhvNHhEemc2azYyVWx2NS8zSGRDTlVUQ3JwRXhXVlpyYz0iLCJFeHBvbmVudCI6IkFRQUIiLCJJbnZlcnNlUSI6IkpzM05kc05Tc2x3UE1iM2VsOHJSeDk5bUU4cUNET2NkbGhTMnBtcTJXa3l1KzUvSWJEcFBrMEYyelY1S3RYV3JjUXpSb2w0c2NYbnpMRHF6NEtuRjdEL0FZcUhwRnJFdEI5VEhVOHFkbGEvMm9aTDJoYVBIbXAwUmtTK2hBQUxRTmxTcVE1QVQxcVdyS0ZQdU4yL0dhOHk2Rkc1SGFwa1ZwR1FWVFJrRWJ0QT0iLCJNb2R1bHVzIjoiMjJiQ01ZaVU5M0dWUjBSTjhKYmZsRWZLS0lKQkduTXdpT2lGRzFBQ2NKMk9QS2JrMXRIOUIvalNTVStET01iZVJETC9yeTFNNzRXOFlzcGUyRm9UL1cvakptcE93YVdCYTBSbFZlcmZvVHNpejNIWjBaZ1Y4eVdaTHlnRzdhcDYxY05nL3JBWjlSZUJTOFB6VFNaNTdFT0dXdVVrNjdzdWpsMWJwOGU2RDVwK0dBWWhTcFREN254TW9vaGpxY3RsQXNMd1pZTklJbWpEdWRGbUU1YTNDTlM2UjVxYWk3cUhpUENRTEZ3QmNmNTgvOEpmdjJraEs1MlRSVU1Ra3oxOEUyYnBGV2dsd2NOK1JwOW5RQm1mK3B5dm94bDFSbzc3REtqdGcwWkFjaHQwbUJFbU5vMVJscngxODlnc1RsdndKSHhRcWx6a0NJWHZ4b3NwS2hZQWt3PT0iLCJQIjoiL240OGx2QlltMnloOENSY292VXZtRUNuV2Z6OUJPVmJTRUIxNnBlckcwekVDeXhvaGp2d2pIM1NHN0cveVNPRkI3cEg3amVDeVBac2xZM1d4MHMrR0wxaG5SL25rUXBZYkVBQmVBRFp4WkliSkd1S0JrODlRbFFiT1BRY2ZSVU95V3QvUDdwdzBPdFJ3Wkl3cmFVRk5rMU5hYzY0TWRHUUQ2d1hIaFlObVgwPSIsIlEiOiIzTE5VV3BFV2thN08vOGtoQW9TMGFXUnBOcFd6U2hMejlmelQ0cENGZFVrMFNHYk1YMGd0R3UxYWFEaUpMWC9TRXAxdXY1L1ZhdWxTTU81WjFRM1RXN1JxTDY3M0pVS2hWQlNQRDN2L3ZmRmtkNlBreWVqREwxZTd4UUpDNkRzMzVZaXBIMnNoZnVTT0dGd2RIbVpudWsrczlDSEpOMjR4ajZvY2hkejduMDg9In0=", + "JWTSigningCertificate": { + "Enabled": false, + "Description": "Enable this section if you want to sign the access token with a certificate instead of a randomly generated key", + "StoreName": "My", + "StoreLocation": "LocalMachine", + "SubjectName": "" + } + }, + "ConnectionStrings": { + "Description": "If both sqlserver and postgresql connection strings are provided, DeployR will use sqlserver connection. If no connection string is provided, the system will throw ConfigurationException.", + "sqlserver": { + "Enabled": false, + "Connection": "" + }, + "postgresql": { + "Enabled": false, + "Connection": "" + }, + "defaultDb": { + "Enabled": true, + "Connection": "./deployrdb_9.0.0.db" + } + }, + "BackEndConfiguration": { + "ClientCertificate": { + "Enabled": false, + "Description": "Enable this section if your backend(s) require certificate authentication", + "StoreName": "My", + "StoreLocation": "LocalMachine", + "SubjectName": "" + }, + "Uris": { + "Description": "Update 'Values' section to point to your backend machines. Using HTTPS is highly recommended", + "Values": [ + "http://localhost:12805" + ] + } + }, + "CORS": { + "Enabled": false, + "Origins": [] + }, + "AdminPassword": "Y5XlDPBNk6IioDpoFrFQQEEBHpbqJEazeDY40Rwhs+jmdfsWGAJbzRXdhzUXVOBt", + "configured": "configured" +} \ No newline at end of file diff --git a/NOTICE.TXT b/NOTICE.TXT new file mode 100644 index 00000000..61db75f2 --- /dev/null +++ b/NOTICE.TXT @@ -0,0 +1,15 @@ +##Legal Notices +Microsoft and any contributors grant you a license to the Microsoft documentation and other content +in this repository under the [Creative Commons Attribution 4.0 International Public License](https://creativecommons.org/licenses/by/4.0/legalcode), +see the LICENSE file, and grant you a license to any code in the repository under the [MIT License](https://opensource.org/licenses/MIT), see the +LICENSE-CODE file. + +Microsoft, Windows, Microsoft Azure and/or other Microsoft products and services referenced in the documentation +may be either trademarks or registered trademarks of Microsoft in the United States and/or other countries. +The licenses for this project do not grant you rights to use any Microsoft names, logos, or trademarks. +Microsoft's general trademark guidelines can be found at http://go.microsoft.com/fwlink/?LinkID=254653. + +Privacy information can be found at https://privacy.microsoft.com/ + +Microsoft and any contributors reserve all others rights, whether under their respective copyrights, patents, +or trademarks, whether by implication, estoppel or otherwise. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..77c7eb14 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Azure-MachineLearning-DataScience + +> **NOTE** This content is no longer maintained. Visit the [Azure Machine Learning Notebook](https://github.com/Azure/MachineLearningNotebooks) project for sample Jupyter notebooks for ML and deep learning with Azure Machine Learning. + +This repository contains walkthroughs, templates and documentation related to Machine Learning & Data Science services and platforms on Azure. Services and platforms include Data Science Virtual Machine, Azure ML, HDInsight, Microsoft R Server, SQL-Server, Azure Data Lake etc. It also hosts materials related to Team Data Science Process (TDSP, https:aka.ms/tdsp) + +There are also materials from tutorials we have delivered at various conferences including KDD, Strata etc., using the above services and platforms. + +For walkthroughs and templates, the primary documentation is on Microsoft documentation sites, with links back to this GitHub repository for the templates and code etc. + + +## NOTE: +Any screenshots of [RStudio](https://www.rstudio.com/) are from the Open Source Edition. diff --git a/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Santa-Clara/Readme.md b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Santa-Clara/Readme.md new file mode 100644 index 00000000..a52b6f83 --- /dev/null +++ b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Santa-Clara/Readme.md @@ -0,0 +1,2 @@ +Contains presentation shared at 2018 Global AI Conference, Santa Clara, Jan 18, 2018 +http://www.globalbigdataconference.com/santa-clara/global-artificial-intelligence-conference/event-97.html diff --git a/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Santa-Clara/TDSP - GAIC Jan 2018.pdf b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Santa-Clara/TDSP - GAIC Jan 2018.pdf new file mode 100644 index 00000000..af241277 Binary files /dev/null and b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Santa-Clara/TDSP - GAIC Jan 2018.pdf differ diff --git a/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Santa-Clara/TDSP - GAIC Jan 2018.pptx b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Santa-Clara/TDSP - GAIC Jan 2018.pptx new file mode 100644 index 00000000..de0cf0ef Binary files /dev/null and b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Santa-Clara/TDSP - GAIC Jan 2018.pptx differ diff --git a/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Seattle/GuhaThakurta-GDBC-AI-Seattle-April-28-2018.pdf b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Seattle/GuhaThakurta-GDBC-AI-Seattle-April-28-2018.pdf new file mode 100644 index 00000000..a2591ddf Binary files /dev/null and b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Seattle/GuhaThakurta-GDBC-AI-Seattle-April-28-2018.pdf differ diff --git a/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Seattle/Readme.md b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Seattle/Readme.md new file mode 100644 index 00000000..708a5b24 --- /dev/null +++ b/Team-Data-Science-Process/Presentations/2018-Global-AI-Conference-Seattle/Readme.md @@ -0,0 +1,19 @@ +This folder contains material presented at the GLobal AI Conference, Seattle, April 27-29, 2018 + +## Global AI Conference Agenda +http://www.globalbigdataconference.com/seattle/global-artificial-intelligence-conference/schedule-103.html + + +## Related information + +### Biomedical Entity Extractor demo web-service on Azure +http://medicalentitydetector.azurewebsites.net/ + +### Team Data Science Process (TDSP) Information +https://aka.ms/tdsp + +### Twitter sentiment classification sample documentation on Microsoft Docs +https://docs.microsoft.com/en-us/azure/machine-learning/team-data-science-process/predict-twitter-sentiment + +### Biomedical Entity Extraction from PubMed documentation on Microsoft Docs +https://docs.microsoft.com/en-us/azure/machine-learning/desktop-workbench/scenario-tdsp-biomedical-recognition?toc=%2Fen-us%2Fazure%2Fmachine-learning%2Fteam-data-science-process%2Ftoc.json&bc=%2Fen-us%2Fazure%2Fbread%2Ftoc.json diff --git a/Team-Data-Science-Process/Presentations/Readme.md b/Team-Data-Science-Process/Presentations/Readme.md new file mode 100644 index 00000000..307bb28d --- /dev/null +++ b/Team-Data-Science-Process/Presentations/Readme.md @@ -0,0 +1,6 @@ + +# This folder contains presentations on TDSP shared at external conferences. + +## Materials for each conference are in respective folders. + +## For more information on Team Data Science Process (TDSP), refer to: https://aka.ms/tdsp diff --git a/Team-Data-Science-Process/Project-Planning-and-Governance/Advanced Analytics Microsoft Project Plan.mpp b/Team-Data-Science-Process/Project-Planning-and-Governance/Advanced Analytics Microsoft Project Plan.mpp new file mode 100644 index 00000000..38245f8b Binary files /dev/null and b/Team-Data-Science-Process/Project-Planning-and-Governance/Advanced Analytics Microsoft Project Plan.mpp differ diff --git a/Team-Data-Science-Process/Project-Planning-and-Governance/Advanced Analytics Microsoft Project Plan.xlsx b/Team-Data-Science-Process/Project-Planning-and-Governance/Advanced Analytics Microsoft Project Plan.xlsx new file mode 100644 index 00000000..eadb9b5e Binary files /dev/null and b/Team-Data-Science-Process/Project-Planning-and-Governance/Advanced Analytics Microsoft Project Plan.xlsx differ